天天看点

Netty服务端与客户端双向通信

作者:技术探究猿
Netty服务端与客户端双向通信

要了解Netty服务端与客户端的双向通信,我们首先要了解服务端与客户端的启动过程,然后再了解服务端发送/接收数据的过程以及客户端发送/接收数据的过程。本文将通过代码示例的方式让大家对Netty有一个初步的认识。

服务端启动过程

流程:

服务端引导类 ServerBootstrap

1.创建两个NioEventLoopGroup

bossGroup: 监听端口

workerGroup: 处理每一条连接的数据读写的线程组

2.配置两大线程组,通过.group(bossGroup, workerGroup)给引导类配置两大线程组,即指定线程模型

3.指定服务端的IO模型,通过.channel(NioServerSocketChannel.class)来指定 IO 模型

4.调用childHandler()方法,给引导类创建一个ChannelInitializer,定义连接数据读写处理逻辑

代码:

public static void main(String[] args) {
        // 创建一个引导类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 1.创建两个NioEventLoopGroup
        // bossGroup 监听端口
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // workerGroup 处理每一条连接的数据读写的线程组
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        serverBootstrap
            // 2.配置两大线程组
            .group(bossGroup, workerGroup)
            // 3.指定服务端的IO模型
            .channel(NioServerSocketChannel.class)
            // 4.调用childHandler()方法,给引导类创建一个ChannelInitializer,定义连接数据读写处理逻辑
            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                protected void initChannel(NioSocketChannel ch) {
                }
            }).bind(8000);
    }
           

总结:要启动一个Netty服务端,必须要指定三类属性,分别是线程模型、IO 模型、连接读写处理逻辑,然后再调用bind(8000),我们就可以在本地绑定一个 8000 端口启动起来。

客户端启动流程

流程:

客户端启动的引导类是 Bootstrap

  1. 与服务端的启动一样,我们需要给它指定线程模型,驱动着连接的数据读写
  2. 指定 IO 模型为 NioSocketChannel,表示 IO 模型为 NIO
  3. 给引导类指定一个 handler,这里主要就是定义连接的业务处理逻辑
  4. 配置完线程模型、IO 模型、业务处理逻辑之后,调用 connect 方法进行连接,由于 connect 方法返回的是一个 Future,是一个异步方法,可以通过 addListener 方法可以监听到连接是否成功,从而打印出连接信息

代码:

public static void main(String[] args) throws InterruptedException {
 // 客户端引导类
 Bootstrap bootstrap = new Bootstrap();
 NioEventLoopGroup group = new NioEventLoopGroup();
 bootstrap
  // 1.指定线程模型
  .group(group)
  // 2.指定IO类型为NIO
  .channel(NioSocketChannel.class)
  // IO 处理逻辑
  .handler(new ChannelInitializer<Channel>() {
   @Override
   protected void initChannel(Channel ch) {
    ch.pipeline().addLast(new StringEncoder());
   }
  });
 bootstrap.connect("127.0.0.1", 8000).addListener(future -> {
  if (future.isSuccess()) {
   System.out.println("连接成功!");
  } else {
   System.err.println("连接失败!");
  }
 });
}
           

客户端与服务端双向通信

服务端读/写数据

通过服务端引导类 ServerBootstrap 的childHandler()方法指定服务端相关数据的读写逻辑,即 在 initChannel() 方法里面给服务端添加一个逻辑处理器,负责向客户端写数据和读客户端的数据

.childHandler(new ChannelInitializer<NioSocketChannel>() {
      protected void initChannel(NioSocketChannel ch) {
          ch.pipeline().addLast(new FirstServerHandler());
      }
  }).bind(8000);
           

FirstServerHandler

writeAndFlush():写数据至客户端 channelRead():从客户端读数据

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 服务端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
        // 回复数据到客户端
        System.out.println(new Date() + ": 服务端写出数据");
        ByteBuf out = getByteBuf(ctx);
        ctx.channel().writeAndFlush(out);
    }
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "你好,欢迎关注我的微信公众号,《技术探究猿‍》!".getBytes(Charset.forName("utf-8"));
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(bytes);
        return buffer;
    }
}
           

客户端读/写数据

通过客户端引导类 Bootstrap 的 handler() 方法指定客户端相关的数据读写逻辑,即在 initChannel() 方法里面给客户端添加一个逻辑处理器,用来负责向服务端写数据和读服务端的数据

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new FirstClientHandler());
    }
});
           

FirstClientHandler

writeAndFlush():写数据至服务端

channelRead():从服务端读数据

public class FirstClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(new Date() + ": 客户端写出数据");
        // 1. 获取数据
        ByteBuf buffer = getByteBuf(ctx);
        // 2. 写数据
        ctx.channel().writeAndFlush(buffer);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 客户端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1. 获取二进制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2. 准备数据,指定字符串的字符集为 utf-8
        byte[] bytes = "你好,技术猿!".getBytes(Charset.forName("utf-8"));
        // 3. 填充数据到 ByteBuf
        buffer.writeBytes(bytes);
        return buffer;
    }
}
           

写在最后

一张图总结一下知识点:

Netty服务端与客户端双向通信

总的来说,Netty的客户端和服务端的启动过程还是比较简单的,感兴趣的小伙伴可以跑跑代码,对Netty有一个初步的印象。接下来将会带大家了解一下Netty的核心组件Channel、EventLoop、ChannelHandler、EventLoopGroup等

继续阅读