上篇文章提到,读写处理逻辑是在 Bootstrap
的handler()
方法指定的,上节课写的如下代码:
.handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) { // 指定数据读写处理逻辑 channel.pipeline().addLast(new StringEncoder()); } });
现在,我们自定义一段处理逻辑给它,如下:
// 设置线程组 bootstrap.group(group) // 设置线程模型 .channel(NioSocketChannel.class) // 设置连接读写处理逻辑 .handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) { // channel.pipeline() 责任链模式 返回和这条连接相关的逻辑处理链 // addLast 添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了 channel.pipeline().addLast(new CustomizeHandler()); } });
上述代码中的
channel.pipeline()
责任链模式 返回和这条连接相关的逻辑处理链addLast()
添加一个逻辑处理器 也就是我们自定义的读写处理逻辑了其中,CustomizeHandler.java
代码如下:
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.Charset; import java.util.Date; /** * Created by zhoudl on 2018/10/4. */ public class CustomizeHandler 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); } 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; } }
ChannelInboundHandlerAdapter
的channelActive()
方法会在连接成功之后自动回调;ByteBuf
格式的二进制数据,这个结构是Netty对二进制数据做的抽象;ctx.alloc()
学过C/C++ 的人肯定知道 alloc 和内存相关,所以这行代码的意思是获取ByteBuff
的内存管理器,而这个内存管理器的作用就是分配一个ByteBuff
出来;ByteBuff
中,这样就达到了Netty传输数据的要求;ctx.channel().writeAndFlush(buffer);
将数据写出到服务端。上述代码和传统的Java Socket编程不同的一点就是写出的数据格式不同,Netty是自己对二进制数据做了一层抽象,定义了一个ByteBuff
的结构出来,无论数据读还是写,Netty都只需要着这样的格式才行,下面开始学习服务端如何读取到这端数据。
同理,服务端的读写处理逻辑处理还是在ServerBootstrap
的childHandler()
方法中,这里除了单词不同之外,其他和客户端同理,这就是Netty API 友好的体现方式之一,学了客户端,服务端猜也能猜个大概,所谓大胆猜测,小心验证。
.childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) throws Exception { channel.pipeline().addLast(new StringDecoder()); channel.pipeline().addLast(new SimpleChannelInboundHandler<String>() { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println(msg); } }); } })
一样的,接下来我们自定义一个处理器出来,代码如下:
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.Charset; import java.util.Date; /** * Created by zhoudl on 2018/10/4. */ public class CustomizeServerHandler 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"))); } }
同样的,你会发现,它继承了ChannelInboundHandlerAdapter
,不同的在于这里是读数据,所以覆盖的方法变了,换成了read()
方法,当客户端连接成功并发送数据之后这个方法被自动回调。
接下来开始学习服务端向客户端回应数据的过程,学完上边这俩之后,现在应该已经没什么难度了。
此处写数据和客户端写数据过程类似,我就不再赘述了,直接上代码,简单直接又明了,代码如下:
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.nio.charset.Charset; import java.util.Date; /** * Created by zhoudl on 2018/10/4. */ public class CustomizeServerHandler 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; } }
紧接着,客户端需要读取服务端发过来的数据,而读取数据的过程和上述服务端读取客户端数据的代码无异,将以下代码添加到CustomizeHandler
中,便能实现客户端读数据的逻辑,代码如下:
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { ByteBuf byteBuf = (ByteBuf) msg; System.out.println(new Date() + ": 客户端读到数据 -> " + byteBuf.toString(Charset.forName("utf-8"))); }
Github地址:https://github.com/Bylant/LeetCode