1 客户端数据读取

客户端相关的数据读写逻辑是通过 Bootstrap 的 handler() 方法指定

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        // 指定连接数据读写逻辑
		
		// 返回的是和这条连接相关的逻辑处理链,采用了责任链模式
		// 然后再调用 addLast() 方法 添加一个逻辑处理器
		// 这个逻辑处理器为的就是在客户端建立连接成功之后,向服务端写数据
		ch.pipeline().addLast(new FirstClientHandler());
    }
});

下面看下FirstClientHandler的实现

public class FirstClientHandler extends ChannelInboundHandlerAdapter {
	//  channelActive()方法会在客户端连接建立成功之后被调用
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println(new Date() + ": 客户端写出数据");
		// 写数据的逻辑分为两步:
        // 1. 获取数据
        ByteBuf buffer = getByteBuf(ctx);

        // 2. 写数据
        ctx.channel().writeAndFlush(buffer);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1. 获取二进制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        
        // 2. 准备数据,指定字符串的字符集为 utf-8
        byte[] bytes = "hello word".getBytes(Charset.forName("utf-8"));

        // 3. 填充数据到 ByteBuf
        buffer.writeBytes(bytes);

        return buffer;
    }
}

内存管理器的作用就是分配一个 ByteBuf,然后把字符串的二进制数据填充到 ByteBuf,这样就获取到了 Netty 需要的数据格式,最后调用 ctx.channel().writeAndFlush()把数据写到服务端


2 服务端读取客户端数据

服务端相关的数据处理逻辑是通过 ServerBootstrap 的 childHandler() 方法指定

.childHandler(new ChannelInitializer<NioSocketChannel>() {
   protected void initChannel(NioSocketChannel ch) {
        // 指定连接数据读写逻辑
		// 添加一个逻辑处理器
		ch.pipeline().addLast(new FirstServerHandler());
   }
});

下面看下FirstClientHandler的实现

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
	
	// 方法在接收到客户端发来的数据之后被回调
    @Override
    public void channelRead(ChannelHandlerContext ctx
    , Object msg // msg 参数指的就是 Netty 里面数据读写的载体
    ) {
        ByteBuf byteBuf = (ByteBuf) msg;

        System.out.println(new Date() + ": 服务端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
}

先运行服务端,再运行客户端,便可以接收到消息。


3 服务端返回数据给客户端

服务端向客户端写数据逻辑与客户端侧的写数据逻辑一样:

  1. 先创建一个 ByteBuf
  2. 然后填充二进制数据
  3. 最后调用 writeAndFlush() 方法写出去

代码如下:

public class FirstServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // ... 收数据逻辑省略

        // 回复数据到客户端
        System.out.println(new Date() + ": 服务端写出数据");
        ByteBuf out = getByteBuf(ctx);
        ctx.channel().writeAndFlush(out);
    }

    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
       
        ByteBuf buffer = ctx.alloc().buffer();

        buffer.writeBytes(bytes);

        return buffer;
    }
}

此时需要客户端读取数据了,客户端的逻辑和服务端读取数据的逻辑一样,同样是覆盖 ChannelRead() 方法

public class FirstClientHandler extends ChannelInboundHandlerAdapter {

    // 写数据相关的逻辑省略

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf byteBuf = (ByteBuf) msg;

        System.out.println(new Date() + ": 客户端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
}

4 小结

  1. 客户端和服务端的逻辑处理是均是在启动的时候给逻辑处理链 pipeline 添加逻辑处理器,来编写数据的读写逻辑;
  2. 客户端连接成功之后会回调到逻辑处器的 channelActive() 方法,而不管是服务端还是客户端,收到数据之后都会调用到 channelRead 方法
  3. 写数据调用writeAndFlush()方法,客户端与服务端交互的二进制数据载体为 ByteBuf
  1. ByteBuf 通过连接的内存管理器创建
  2. 字节数据填充到 ByteBuf 之后才能写到对端;