client 连续发送 server 的数据包,server 接收到数据会出现数据包粘在一起的情况
比如 client发送了 数据”123456”和”78910”
server收到却是: “12345” 和 “678910”
1.2 为何出现粘包?
TCP报文格式 如下,
在 TCP首部并未指明数据包的长度
- TCP首部中有20bytes的固定长度;
- 可于第1个报文中指明: 最大报文段长度MSS(Maximum Segment Size); 但是它是选项 部分, 非必有的;
1.3 如何解决粘包问题
一般解决粘包问题的四种方案:
1.3.1 固定发包长度
客户端发送数据包时, 固定长度, 比如 1024字节, 如果某次发包不足 1024字节, 空格补足;
1.3.2 使用固定分隔符
客户端发包时, 每个包末尾使用固定分隔符, 比如”\r\n”;
如果数据包粘包了, 拆包时, 就等下一个数据包直到拿到”\r\n”;
拆后的头部部分与前一个包的剩余部分合并; 这样就得到一个完整的包.
1.3.3 消息头部保存消息长度len字段
消息分为 头部 和 消息体; 然后在头部增设一个 消息长度的字段; 接收时, 读到够len长度的数据, 才算读完完整数据!
1.3.4 自定义协议
自己定制自己的发包协议; 指定数据长度和分拆合并逻辑;
1.4 Netty如何解决粘包问题
1.4.1 固定发包长度
FixedLengthFrameDecoder:
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
...
}
将接收到的字节按固定字节数分割。例如,如果你收到以下四个片段化的数据包:
第1个packet | 第2个packet | 第3个packet | 第4个packet |
---|---|---|---|
A | BC | DEFG | HI |
FixedLengthFrameDecoder(3)
将其解码为以下3个固定长度的数据包:
第1个packet | 第2个packet | 第3个packet |
---|---|---|
ABC | DEF | GHI |
发包前编码:
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class FixedLengthFrameEncoder extends MessageToByteEncoder<String> {
private int len;
public FixedLengthFrameEncoder(int len) {
this.len = len;
}
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
// 超长直接抛出异常
if (msg.length() > len) {
throw new UnsupportedOperationException("message too large, limited " + len);
}
// 不足长补全
if (msg.length() < len) {
msg = appendSpace(msg);
}
ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.getBytes()));
} // 进行空格补全
/**
* 补空格
* @param msg
* @return
*/
private String appendSpace(String msg) {
StringBuilder builder = new StringBuilder(msg);
for (int i = 0; i < len - msg.length(); i++) {
builder.append(" ");
}
return builder.toString();
}
}
1.4.2 固定分隔符
1.4.2.1 回车换行符-LineBasedFrameDecoder
public class LineBasedFrameDecoder extends ByteToMessageDecoder {}
1.4.2.2 自定义分隔符-DelimiterBasedFrameDecoder
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {}
1.4.3 消息保存长度len字段
LengthFieldBasedFrameDecoder & LengthFieldPrepender
增加长度字段, 标明数据长度;
maxFrameLength:指定包所传递的最大数据包大小;
lengthFieldOffset:指定length字段在字节码中的偏移量;
lengthFieldLength:指定length字段所占用的字节长度;
lengthAdjustment:对含消息头和消息体的, 我们有时需进行消息头的长度调整,方便只取消息体: 此字段就是消息头长;
initialBytesToStrip:对于length字段在消息头中间的情况,可以通过此字段, 忽略消息头及length字段所占的字节。
收包后解码:
public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder {
private final ByteOrder byteOrder;
private final int maxFrameLength;
private final int lengthFieldOffset;
private final int lengthFieldLength;
private final int lengthFieldEndOffset;
private final int lengthAdjustment;
private final int initialBytesToStrip;
private final boolean failFast;
private boolean discardingTooLongFrame;
private long tooLongFrameLength;
private long bytesToDiscard;
...
}
发包前编码:
public class LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf> {
private final ByteOrder byteOrder;
private final int lengthFieldLength;
private final boolean lengthIncludesLengthFieldLength;
private final int lengthAdjustment;
...
}
1.4.4 自定义粘包拆包器
1.4.4.1 继承 LengthFieldBasedFrameDecoder和LengthFieldPrepender
继承这两个类, 然后复写自己的逻辑
1.4.4.2 自己扩展MessageToByteEncoder和ByteToMessageDecoder
public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter{}
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {
1.5 小结
1. 发包固定长度
2. 发包指定分隔符
3. 发包头部加数据长度
4. 自定义协议
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hi@niewj.com