生活小妙招之Netty编写最简单服务端与客户端完美流程分析

150 阅读4分钟

下面是大致的流程,配合底下两端的代码注释就会让你醍醐灌顶啦。建议把它们拷到你的IDEA中去运行一下,看得也更清楚一些

image.png

先是服务端,类注释是上图的解释

/**
 * @author fiji
 * @date 2022/3/18 13:56
 *
 * 在客户端尝试和服务端建立连接后,服务端在启动之后会监听80端口
 * 客户端发送连接请求,此时EventLoopGroup选择器正在监听ACCEPT事件,
 * 监听到了ACCEPT事件后,就会找一个处理器类去处理这个ACCEPT事件。而处理ACCEPT
 * 事件的处理器类是Netty帮助我们实现的,我们现在看不到实现,结果上就是
 * 那个处理ACCEPT事件的处理器会去调用我们自己在下面写的初始化器中的initChannel方法
 *
 * 当然对于客户端也是一样,也是在客户端连接后会去调用客户端的initChannel方法
 *
 * 我们说回客户端:
 * sync方法会在连接之后才会往下运行,它是一个同步方法,或者成为阻塞方法。连接建立之前会阻塞sync下面的代码
 * 也就是说sync方法唯一的作用就是等待连接建立好,然后放行下面的方法。等连接建立好之后下面就可以拿到
 * channel对象(即执行channel()方法),那么这里的channel指的就是SocketChannel,netty对它进行了
 * 封装,称作NioSocketChannel,我们就可以用这个对象的一些方法去读写数据,现在我们想向服务端写数据
 * 就调用channel的writeAndFlush方法
 *
 * 下面是netty与NIO不同的地方:不论是发数据还是接收数据,它们都会走到handler。
 * 因此当你调用writeAndFlush方法发送一个“hello world”之后,接下来就走到编码器对应的handler里面了
 * 即客户端的24行中的StringEncoder()方法(注意不是addLast方法,这是添加处理器的方法,添加方法在连接
 * 建立后就完成了)
 * StringEncoder干的事情很简单,就是把“hello world”这个字符串转换为ByteBuf(ByteBuf是Netty中对ByteBuffer的增强)
 * 在channel之间传递数据,最终的数据格式都是ByteBuf
 *
 * 这时候服务端有一个接收READ的另一个EventLoop就会接受到事件。把数据读到ByteBuf后接下来就会走到服务器端的这些处理器上,按照
 * 添加的顺序依次对读到的数据进行处理
 */
public class HelloServer {
    public static void main(String[] args) {
        //ServerBootstrap:服务器端的启动器,专门完成服务器端启动的
        //它的功能就是将下面这些Netty提供的各种组件组合到一起作为一个整体的服务进行启动(组装Netty组件)
        new ServerBootstrap()
                //许多BossEventLoop或WorkerEventLoop
                //通过调用group方法,向ServerBootstrap加入了EventLoop组,里面有的EventLoop是BossEventLoop,
                //用来处理连接事件,有的EventLoop是WorkerEventLoop,用来处理可读事件
                //每一个EventLoop你可以理解为都包含一个线程和一个选择器
                .group(new NioEventLoopGroup()) // 1
                //选择 服务器的 ServerSocketChannel的实现,Netty在NIO 的基础上又对channel进行了分装
                .channel(NioServerSocketChannel.class) // 2
                //告诉那些将来作为Worker的EventLoop将来要做哪些事。比如编解码啊、业务处理啊等等
                //其中一种操作称为一个handler,有的handler是做编解码的、有的handler是专门对读到的数据
                //进行业务处理
                .childHandler(
                        //下面解释:ChannelInitializer
                        //channel:代表和客户端进行数据读写的通道
                        //Initializer:初始化器,对于这个channel中有哪些handler做一个初始化
                        //ChannelInitializer本身也是一个特殊的handler,不过它的职责就是去添加别的handler
                        //具体是如何添加的呢:即去实现initChannel方法进行添加
                        new ChannelInitializer<NioSocketChannel>() { // 3
                    @Override
                    //initChannel添加两个handler但并不会立即执行初始化器的内容
                    //只有在accept事件发生后才回去执行下面的方法
                    protected void initChannel(NioSocketChannel ch) {
                        //我们添加了如下的两个handler

                        //对数据进行解码。因为数据传输过来都是字节型的,在Netty中都装在了一个叫ByteBuf的地方
                        //而StringDecoder就是在将这个ByteBuf转换成字符串
                        ch.pipeline().addLast(new StringDecoder()); // 5
                        //自定义handler。打印上一步转换好的字符串
                        ch.pipeline().addLast(new SimpleChannelInboundHandler() { // 6
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
                                System.out.println(msg);
                            }
                        });
                    }
                })
                .bind(8080); // 4
    }
}

接下来是客户端

public class HelloClient {
    public static void main(String[] args) throws Exception{
        new Bootstrap()
                .group(new NioEventLoopGroup()) // 1
                .channel(NioSocketChannel.class) // 2
                .handler(new ChannelInitializer<Channel>() { // 3
                    @Override
                    //在连接建立后才会执行
                    protected void initChannel(Channel ch) {
                        ch.pipeline().addLast(new StringEncoder()); // 8
                    }
                })
                .connect("127.0.0.1", 8080) // 4
                .sync() // 5
                .channel() // 6
                .writeAndFlush(new Date() + ": hello world!"); // 7
    }
}