前提:
当我们设计了一个通信协议后,那么基于TCP的服务端想要准确接收每一个信息会遇到粘包和拆包的问题。
原因:
- 粘包会导致接收后信息报文解析失败,不完整或者有额外其他包的信息。
- TCP协议需要粘包拆包是因为TCP协议数据传输没有边界概念,传输数据有限制和网络优化。数据过小发送时间间隔很短时为了提高效率会合并发送数据,数据过大就要拆包多个进行发送,前面数据过大拆的包和后一个数据一起发送了。
- UDP没有因为有个消息保护边界。
如何识别每一个信息:
- 定长 发送接收的数据长度固定识别 短了补齐(FixedLengthFrameDecoder
- 特殊分隔符进行信息识别 回车换行(LineBasedFrameDecoder与DelimiterBasedFrameDecoder
- 消息长度 可明确读取消息的数据(LengthFieldBasedFrameDecoder与LengthFieldPrepender
- 自定义随心控制(MessageToByteEncoder和ByteToMessageDecoder
- 常用的http请求使用了特殊字符+长度
http请求报文格式
- 1)请求行:以\r\n结束;
- 2)请求头:以\r\n结束;
- 3)POST请求 Content-Length: 14 (get没有
- 4)\r\n;
- 5)post数据; (get没有
http响应报文格式
- 1)响应行:以\r\n结束;
- 2)响应头:以\r\n结束;
- 3)Content-Length: 14
- 4)\r\n;
- 5)数据;
数据传输的方式
- 直接socket处理数据 效率和处理各种网络情况。
- nio 效率提高了但是复杂搞不好。
- 基于nio的netty省事直接业务处理。
- 很久前的一个项目原有的数据传输用的socket压测的时候各种情况(丢包响应都不行其他问题)后面改为nio一样,最后用了netty测试搞完不用加班系统还稳定。
下面说下netty的一个tcp的自定义解码拆包demo
- 这个例子前6个字节是xml报文长度
- 剩下主要是ByteBuf的一些操作,有些位置的处理。
java
public class StringLenDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws UnsupportedEncodingException {
// 头部长度6字节,说明消息长度还没满,直接返回
if (in.readableBytes() < 6){
return;
}
// 设置回滚点
in.markReaderIndex();
// 消息长度,同时会使readerIndex向前移动6个指针
byte[] lenBytes = new byte[6];
in.readBytes(lenBytes);
String xmlLen=new String(lenBytes);
//接收报文为空或不为数字
if(null==xmlLen ||"".equals(xmlLen.trim()) || !xmlLen.matches("^[0-9]{6}$")){
in.resetReaderIndex();
return;
}
int len=Integer.valueOf(xmlLen);
//"重置读指针,并返回
if (in.readableBytes() < len){
in.resetReaderIndex();
return;
}
byte msgbytter[]=new byte[len];
in.readBytes(msgbytter);
//得到报文
String msg=xmlLen+new String(msgbytter,Charset.forName("GBK"));
//log
out.add(msg);
}
}