TankWar网络

一、netty基础
1.netty初始化:
创建两个负责接客的boss组和四个负责服务的工人组;
创建一个Server启动辅助类;
将接客组和工人组传入辅助类(注意接客组排最前面);
规定工作方式为基于事件处理的异步全双工;

childHandler:netty内部处理了accept的过程,客户端连上来之后,写一个callBack对客户端进行处理;接收进来的客户端都被认为是服务端的child;每接收一个端,都需要进行childHandler处理;
(也就是观察者模式)
当一个客户端连到服务器上面的时候,netty会自动的调用accept方法将它迎接进来,然后会自动地调用initChannel方法,把socketChannel给初始化;

绑定在8888端口号上;

public class NettyServer {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup);
        /*异步全双工*/
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childHandler(new MyChildInitializer());
        serverBootstrap.bind(9999).sync();
    }
}

class MyChildInitializer extends ChannelInitializer<SocketChannel>{
    
    
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
        System.out.println("A client connected ~");
    }
}

下面对initChannel里的处理进行说明. . . . . .

算了,直接分析一整大段代码好了(从childHandler开始):

(PS:childHandler主要用于处理网络I/O事件,例如记录日志、对消息进行编解码等(摘自《Netty权威指南》))
创建了一个机器人儿(处理器),当有一个客户端连接进来时,netty帮我连接好(accept)后,会调用initChannel方法创建socketChannel连接,在初始化的方法中,通过pipeline在连接上面部署一个处理器;

这个处理器继承于ChannelInboundHandlerAdapter,用于处理连接上来的socket;
当有一个数据写过来的时候,它将环境封装在了ctx里,将数据封装在了msg里,可以直接使用;

通过ctx将数据给他写回去,他那边呢,又通过readline读到并显示了出来;

服务器:

public class NettyServer {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        EventLoopGroup bossGroup = new NioEventLoopGroup(2);
        EventLoopGroup workerGroup = new NioEventLoopGroup(4);
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup);
        /*异步全双工*/
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childHandler(new MyChildInitializer());
        serverBootstrap.bind(8888).sync();
    }
}
	class MyChildInitializer extends ChannelInitializer<SocketChannel>{
    
    
    	@Override
    	protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
        	socketChannel.pipeline().addLast(new MyChildHandler());
    }
}
     class MyChildHandler extends ChannelInboundHandlerAdapter {
    
    
         @Override
         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
             super.exceptionCaught(ctx, cause);
         }
         @Override
         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
             ByteBuf buf = (ByteBuf) msg;
             System.out.println(buf.toString());
             ctx.writeAndFlush(msg);
         }
     }

客户端:

public class Client {
    
    
    public static void main(String[] args) throws IOException {
    
    
        Socket socket = new Socket("localhost", 8888);
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bufferedWriter.write("Faker No.1 !");
        bufferedWriter.newLine();
        bufferedWriter.flush();

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String str = bufferedReader.readLine();
        System.out.println(str);

        bufferedWriter.close();
    }
}

netty的客户端:
创建了一个干活的线程,交给辅助启动类,指定连接的类型;
当连接建立好(也就是accept完成)以后,会自动调用initChannel方法,这个方法里加了一个处理器MyHandler,当通道建立起来的时候,会调用channelActive方法,写一个数据给服务器,而服务器呢,接收到之后又把他写了回来,netty客户端通过ChannelInboundHandlerAdapter进行接收,并打印出来;
对8888端口进行连接,sync()是阻塞的,必须要连接上了,才可以进行执行下面的语句;
System.in.read()也是阻塞,如果不阻塞的话,程序向下运行,优雅地关闭了,啥也没有了;

public class NettyClient {
    
    
    public static void main(String[] args)throws Exception {
    
    
        EventLoopGroup workerLoop = new NioEventLoopGroup(1);
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerLoop);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    
    
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
    
    
                socketChannel.pipeline().addLast(new MyHandler());
            }
        });
        bootstrap.connect("localhost", 8888).sync();
        System.in.read();
        workerLoop.shutdownGracefully();
    }
}
     class MyHandler extends ChannelInboundHandlerAdapter {
    
    
         @Override
         public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    
    
             ByteBuf buf = (ByteBuf) msg;
             System.out.println(buf);
         }
         @Override
         public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
    
             ByteBuf buf = Unpooled.copiedBuffer("Faker Faker Faker !".getBytes());
             ctx.writeAndFlush(buf);
         }
         @Override
         public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    
    
         }
     }

阻塞方式:
1.Future阻塞:
编写一个阻塞函数Task,他继承了Callable接口,并且定义它的泛型为Long类型;
重写call方法,通过一定手段对这个方法进行延时;
编写Future方法,new一个弹性的线程池出来,需要一个线程就new一个线程,new一个Task对象,将对象提交给线程池,将一个未决结果交给future,而future.get()是阻塞的,当任务执行完毕之后,打印出future.get()的值,阻塞过程完成;

public class CallableAndFuture {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        ExecutorService service = Executors.newCachedThreadPool();
        Future<Long> future = service.submit(new MyTask());
        long l = future.get();
        System.out.println(l);
        System.out.println("go on ! ");
    }
}
public class MyTask implements Callable<Long> {
    
    
    @Override
    public Long call() throws Exception {
    
    
        long r = 0L;
        for (long i = 0; i < 10L; i++) {
    
    
            r += i;
            Thread.sleep(1000);
            System.out.println(i + "added !");
        }
        return r;
    }
}

2.FutureTask
它继承了RunnableFuture接口,而RunnableFuture接口继承了Runnable和Future接口,意味着他可以在创建线程的同时,通过get方法进行数值的返回;

二、netty聊天室NeChat
1.创建一个框框,在框框里调用封装了的Client客户端,也就是将它new出来,然后调用它的connect方法,添加动作监听器,默认是回车之后就会执行它内部的语句;
把field里面的东西读给Client;在保存了Area里的东西的前提下,把Field里面的东西读给Area;Field内容清空;

2.buf的显示
将可读取到的buf字节数传给一个字节数组bytes,将这个字节数组和buf的起始指针传给buf,让它给我读! 读完之后再用字符串类型来显示;

注意( !!!):如果占用的是JVM里面的内存的话,可以被JVM的垃圾回收机制自动回收,而buf占用的是是操作系统里面的内存,所以在没有引用指向它之后需要手动释放;虽然可以获取到它的引用数,但是这些引用可能是别的地方对buf的引用,是不可以随意释放的,因为别的地方可能还在读取呢,所以用完一个就释放一个;而且是不论如何(发生bug)都要将它释放,所以需要使用try finally,但是当buf是空的时候就不释放,所以释放之前需要判空(不然会报错);

3.完成基本网络信息传递
打开服务端和客户端,在testField里面写一段话,按下回车,会调用Client的add方法将文本信息传递到Client里,Client将信息打包成buf类型传递到服务器,服务器通过重写的channelRead将信息在命令行里显示出来;

4.将收到的信息转发给其他人
(1)通过ChannelGroup保存所有的通道,往里面传一个默认的线程,去处理和维护ChannelGroup;
(2)确定Channel能用
在MyChildHandler里面重写channelActive方法,当这个通道成功建立起来之后,就把它加到ChannelGroup里面;
(3)在channelRead方法里面通过ChannelGroup封装的writeAndFlush方法,把信息进行广播;每个客户端收到之后就在channelRead方法里边把信息显示出来;
(4)异常客户端关闭
某个客户端抽风了,就需要把它关闭,在exceptionCaught里面抛出异常,并把通道在通道集中移除,关闭该通道;
(5)挥手告别
当某个客户端要关闭的时候,输入一串特定的字符串,传给客户端,客户端把它移除掉;

(已上传:基于netty的聊天室)

5.客户端关闭
1.链式编程
在《netty权威指南》里面有这么一堆东西,可以点点点的原因是从第二个点开始的前一堆东西的返回值就是那个东西,所以可以一直点下去;
在这里插入图片描述
那我也来~(最喜欢套娃了,学JavaSE的时候套地飞起)

ServerBootstrap serverBootstrap;
serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new MyChildInitializer())
.bind(8888).sync()
.channel().closeFuture().sync();

(这个好像套多了,要把 . closeFuture那一堆另起一个语句,不然不能阻塞)

对客户端进行封装,把原来的main方法改为serverStart;创建了一个ServerFrame(只是把原本的命令行显示改为IU显示而已)

三、坦克信息的传递(演练)
1.传递坐标的编码器
创建一个信息类TankMsg;
创建一个编码器MsgEncoder,泛型指定为TankMsg类型,重写了encode方法,将TankMsg的x和y坐标作为ByteBuf类型打包到buf里面,将它放在通道上;
在客户端重写channelActive方法,将TankMsg对象写出去,这时会碰到Encoder,它将信息转换为ByteBuf类型,然后再写出去;

四、坦克信息的传递(正式)
客户端、服务端IU、服务端都不变;
写一个坦克加入类,需要加入坦克的坐标啊方向啊id啊啥的,具体的实现是这样的:创建一个字节流,让数据流往字符流里面写啊写,写完之后传给一个字节数组,再把他俩关闭;
又写了一个编码器,获取刚才那个字节数组,将它的长度和它都写出去;

Guess you like

Origin blog.csdn.net/dgytjhe/article/details/116174429
Recommended