Skip to content

Netty - 01 入门案例

本文介绍Netty的入门案例以及流程,入门案例主要使用Netty实现服务端监听8080端口,客户端连接到服务端,并且发送"hello,world",服务端接收到消息并打印到控制台。

1. 服务端代码

java
@Slf4j
public class HelloServer {
    public static void main(String[] args) {
        // 1. 启动器,负责组装netty组件,启动服务器
        new ServerBootstrap()
                // 2. 指定一个线程组,该线程组用于处理连接事件、读写事件以及其他普通事件
                .group(new NioEventLoopGroup())
                // 3. 选择服务器的ServerSocketChannel实现
                .channel(NioServerSocketChannel.class)
                // 4. 指定一个SocketChannel初始化器,当有新的连接建立时,会执行该初始化器的initChannel方法,在该连接上添加处理器
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 6. 有新连接建立时,首先添加一个StringDecoder处理器,该处理器的作用是将ByteBuf转换为String
                        ch.pipeline().addLast(new StringDecoder());
                        // 然后添加自定义入栈处理器,该处理器监听读事件,输出消息
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                log.info(msg.toString());
                            }
                        });
                    }
                })
                // 5. 绑定8080端口
                .bind(8080);
    }
}

2. 客户端代码

java
public class HelloClient {
    public static void main(String[] args) throws InterruptedException {
        //1.创建启动对象 new Bootstrap()
        ChannelFuture channelFuture = new Bootstrap()
                // 2. 指定线程组
                .group(new NioEventLoopGroup())
                // 3. 选择客户端Channel实现
                .channel(NioSocketChannel.class)
                // 4. 指定一个SocketChannel初始化器,当成功建立连接后,会在SocketChannel上应用该初始化器(调用initChannel方法)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        // 7. 连接建立完成后,应用该初始化器,在连接上添加StringEncoder处理器,该处理器的作用是将String转换为ByteBuf
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                // 5. 建立连接,异步方法返回Future对象
                .connect(new InetSocketAddress("127.0.0.1", 8080));

        // 6. 阻塞,等待连接建立完成
        channelFuture.sync();

        // 8. 获取连接
        Channel channel = channelFuture.channel();

        // 9. 发送数据
        channel.writeAndFlush("hello world");
    }
}

3. 流程分析与组件分析

3.1 流程分析

接下来我们分别从个服务端与客户端的初始化、客户端与服务端建立连接的流程进行分析:

服务端的初始化

  1. 首先创建服务端的启动器ServerBootstrap,用于管理各个组件,启动服务器;
  2. 然后指定线程组NioEventLoopGroup(),在服务端分为主线程(用于接收请求创建连接)和工作线程(用于处理读写事件),在Netty中,这些线程以EventLoop表示,将这些线程放在同一个组里,称为EventLoopGroupNioEventLoopGroup()是一个实现;
  3. 然后指定服务端的NioServerSocketChannel,即NIO中的ServerSocketChannel,用于接收请求获取连接;
  4. 指定新连接的初始化器childHandler中的ChannelInitializer
  5. 监听端口8080;

客户端的初始化

  1. 首先创建客户端的启动器Bootstrap(),用于管理客户端的各个组件;
  2. 然后指定线程组NioEventLoopGroup(),在客户端也要分线程,一个线程发送数据(主线程),一个线程接收数据(EventLoop);
  3. 指定新连接的初始化器handler中的ChannelInitializer
  4. 调用connect()连接到服务端,该方法是异步方法,返回Future对象;
  5. 调用Future对象的sync()方法,阻塞等待连接建立;
  6. 调用Futurechannel()方法,获取与服务端的连接;

客户端与服务端的交互

  1. 首先客户端向服务端请求建立连接;
  2. 服务端接收到请求后,通过childHandler中的ChannelInitializer初始化连接,在服务端为该连接增加两个处理器Handler
    1. StringDecoderStringDecoder的作用是将ByteBuf(字节缓冲区,是Netty对NIO中ByteBuffer的包装)解码为字符串;
    2. ChannelInboundHandlerAdapter自定义处理器,接收上一步处理器的结果msg,并接着处理;
  3. 连接建立后,客户端也通过handler中的ChannelInitializer初始化连接,在客户端为该连接增加一个处理器——StringEncoderStringEncoder的作用是将字符串编码为ByteBuf
  4. 客户端调用channel.writeAndFlush()发送消息到服务端,字符串消息会经过StringEncoder处理器,将字符串编码为ByteBuf
  5. 服务端接收到消息后,首先经过第一个处理器StringDecoder将字节解码为字符串,然后将字符串传递给下一个自定义处理器,即打印出消息;

3.2 组件分析

  • EventLoop与EventLoopGroup;
  • Pipeline与Handler,Handler分为InBound与OutBound;
  • Channel与Future;
  • ByteBuf;

参考资料

[1] https://www.bilibili.com/video/BV1py4y1E7oA