版权声明:士,不可以不弘毅,任重而道远 https://blog.csdn.net/superbeyone/article/details/83898458
文章目录
Netty权威指南_札记04_TCP粘包/拆包问题解决
1. TCP粘包/拆包
概念:
TCP是个“流”协议,是没有界限的一串数据。因为TCP底层不了解上层业务数据的具体含义,它会根据TCP换从去的实际情况进行包的拆分,所以在业务上人为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送。
1.1 TCP粘包/拆包问题说明
假设客户端分别发送两个数据包D1和D2给服务器,由于服务器一次读取的字节数是不确定的,故可能存在以下四种情况:
- 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
- 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
- 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2的部分内容,第二次读取到了D2包的部分内容,被称为TCP拆包;
- 服务器分两次读取到了两个数据包,第一次读取到了D1包的部分内容,第二次读取到了D1的剩余内容和D2整包。
注:其实就是类似于滑块问题
1.2 TCP粘包/拆包发生的原因
- 应用程序write写入的子节大小大于套接口发送缓冲区大小;
- 进行MSS大小的TCP分段;
- 以太网帧的payload大于MTU进行IP分片。
1.3 粘包问题解决策略
- 消息定长;
- 在包尾增加回车换行符进行分割,例如FTP协议;
- 将消息分为消息头和消息体,消息头中包含消息总长度或者消息体长度;
- 更复杂的应用层协议。
2. 利用LineBasedFrameDecoder解决TCP粘包问题
2.1 服务端
Netty时间服务器服务端 TimeServer
代码改造:
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//增加部分 start
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
//增加部分 end
socketChannel.pipeline().addLast(new TimeServerHandler());
}
});
2.2 客户端
Netty时间服务器客户端 TimeClient
代码改造:
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//增加部分 start
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
//增加部分 end
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});