三、解决 Netty 粘包问题
在 Netty 中,解决粘包问题的常用方案有以下 3 种:
- 设置固定大小的消息长度,如果长度不足则使用空字符弥补,它的缺点比较明显,比较消耗网络流量,因此不建议使用;
- 使用分隔符来确定消息的边界,从而避免粘包和半包问题的产生;
- 将消息分为消息头和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息。
接下来我们分别来看后两种推荐的解决方案。
1.使用分隔符解决粘包问题
在 Netty 中提供了 DelimiterBasedFrameDecoder 类用来以特殊符号作为消息的结束符,从而解决粘包和半包的问题。
它的核心实现代码是在初始化通道(Channel)时,通过设置
DelimiterBasedFrameDecoder 来分隔消息,需要在客户端和服务器端都进行设置,具体实现代码如下。
服务器端核心实现代码如下:
/** * 服务端通道初始化 */ static class ServerInitializer extends ChannelInitializer<SocketChannel> { // 字符串编码器和解码器 private static final StringDecoder DECODER = new StringDecoder(); private static final StringEncoder ENCODER = new StringEncoder(); // 服务器端连接之后的执行器(自定义的类) private static final ServerHandler SERVER_HANDLER = new ServerHandler(); /** * 初始化通道的具体执行方法 */ @Override public void initChannel(SocketChannel ch) { // 通道 Channel 设置 ChannelPipeline pipeline = ch.pipeline(); // 19 行:设置结尾分隔符【核心代码】(参数1:为消息的最大长度,可自定义;参数2:分隔符[此处以换行符为分隔符]) pipeline.addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); // 设置(字符串)编码器和解码器 pipeline.addLast(DECODER); pipeline.addLast(ENCODER); // 服务器端连接之后的执行器,接收到消息之后的业务处理 pipeline.addLast(SERVER_HANDLER); } }
核心代码为第 19 行,代码中已经备注了方法的含义,这里就不再赘述。
客户端的核心实现代码如下:
/** * 客户端通道初始化类 */ static class ClientInitializer extends ChannelInitializer<SocketChannel> { // 字符串编码器和解码器 private static final StringDecoder DECODER = new StringDecoder(); private static final StringEncoder ENCODER = new StringEncoder(); // 客户端连接成功之后业务处理 private static final ClientHandler CLIENT_HANDLER = new ClientHandler(); /** * 初始化客户端通道 */ @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); // 17 行:设置结尾分隔符【核心代码】(参数1:为消息的最大长度,可自定义;参数2:分隔符[此处以换行符为分隔符]) pipeline.addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); // 设置(字符串)编码器和解码器 pipeline.addLast(DECODER); pipeline.addLast(ENCODER); // 客户端连接成功之后的业务处理 pipeline.addLast(CLIENT_HANDLER); } }