在netty进行消息的发送和读取时,都会进行一次数据格式的转化。在发送消息时,会把消息转化成字节,便于网络传输;读取时,再将字节转化成相应的对象,而这种数据的转化便是通过编码器和解码器来实现的。
1.编码器
Netty主要提供了两种类型的编码器:
MessageToMessageEncoder 将消息对象编码成消息对象
MessageToByteEncoder 将消息对象编码成字节码(网络传输时常用)
这两中编码器都只需要继承相应的父类,并通过泛型来指定想要转化的消息类型
具体代码实现:
MessageToMessageEncoder
/**
* 自定义编码器,将Integet转化成String类型
*
* @author shuchang
* @version 1.0
* @date 2020/8/30 23:37
*/
public class Encoder extends MessageToMessageEncoder<Integer> {
@Override
protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
out.add(String.valueOf(msg));
}
}
MessageToByteEncoder
/**
* 自定义编码器,将按照自定义的协议封装好的消息转化成二进制进行网络传输
*
* @author shuchang
* @version 1.0
* @date 2020/8/30 23:37
*/
public class MyMessageEncoder extends MessageToByteEncoder<MyMessageProtocal> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, MyMessageProtocal myMessageProtocal, ByteBuf byteBuf) throws Exception {
byteBuf.writeInt(myMessageProtocal.getLength());
byteBuf.writeBytes(myMessageProtocal.getContent());
}
}
自定义的编码器可以通过pipeline.addFirst()或addLast()方法添加到管道的处理器集合中,进行业务处理。
2.解码器
Netty同样也提供了两种解码器类型
ByteToMessageDecoder 解码字节到消息
MessageToMessageDecoder 解码消息到消息Na
解码器主要负责从通道另一端进行读取数据时,对数据进行解析的工作。
ByteToMessageDecoder
继承这个类后,我们主要是需要重写他的decode方法,根据我们自定义的协议,对获取的byte[]进行解析(编码和解码过程必须遵循一致的协议,否则无法解析成功)
具体代码实现:
public class MyMessageDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//获取文本长度
int length = byteBuf.readInt();
//声明一个长度为length的byte数组,并读取响应长度的数据
byte[] content = new byte[length];
byteBuf.readBytes(content);
//解析完毕后,重新封装进自定义的协议中
MyMessageProtocal messageProtocal = new MyMessageProtocal();
messageProtocal.setLength(length);
messageProtocal.setContent(content);
//将封装好的协议添加到list中,传递到下一个handler进行处理
list.add(messageProtocal);
}
}
由于在配置启动类时,没有配置TCP_NODELAY属性,TCP协议会基于Nagle算法将多个数据块合并发送,就会导致TCP粘包和拆包的问题。
什么是TCP粘包/拆包?
1.D1和D2正好满足缓冲区大小,服务端收到两个独立的数据包,无粘包拆包问题
2.基于tcp协议发送数据包时D1和D2合并成一个数据块发送到服务端,出现TCP粘包
3.服务端第一次收到D1和部分D2,第二次收到剩余部分D2,出现TCP拆包,这是因为TCP进行合并发送时,缓冲区大小不足以承载两个数据包,因此D2被截断成两部分发送
4.服务端第一次收到部分D1,第二次收到剩余D1和全部D2,这是因为D1文件大小过大,缓冲区放不下,D1分段发送
处理TCP粘包拆包问题
解决方案:
1.客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度;
2.客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;(通过DelimiterBasedFrameDecoder处理分隔符)
3.将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息;
4.通过自定义协议进行粘包和拆包的处理。