netty权威指南学习笔记三——TCP粘包/拆包之粘包现象
TCP是个流协议,流没有一定界限。TCP底层不了解业务,他会根据TCP缓冲区的实际情况进行包划分,在业务上,一个业务完整的包,可能会被TCP底层拆分为多个包进行发送,也可能多个小包组合成一个大的数据包进行发送,这就是TCP的拆包和粘包。
产生问题的原因
- 应用程序write写入的字节大小大于套接字缓冲区大小
- 进行MSS大小的TCP分割;
- 以太网帧的payload大于MTU进行IP分片
关于MSS和MTU的分段问题可以参考https://www.cnblogs.com/yuyutianxia/p/8120741.html学习。
解决策略
由于底层的TCP无法理解上层的业务数据,所以在底层无法保证数据包不被拆分和重组,要解决这个问题就需要应用层进行人为干预,主要方案有:
- 消息定长,例如每个报文大小为固定长度200字节,如果不够,空位补空格;
- 在包尾增加回车换行符进行分割,例如FTP协议;
- 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常涉及思路为消息头的第一个字段使用int32来表示消息的总长度;
- 其他更复杂的应用层协议。
下面模拟粘包的场景,即在服务端去掉分隔符,而客户端发送大量信息过来
TimeServer 服务端代码
package com.StickyUnpack; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; public class TimeServer {
public void bind(int port) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();//创建接收请求的线程组
NioEventLoopGroup workGroup = new NioEventLoopGroup();//创建处理IO的线程组
try {
ServerBootstrap b = new ServerBootstrap();//创建NIO服务启动辅助类
b.group(bossGroup,workGroup)//将两个线程组传递到NIO服务辅助启动类中
.channel(NioServerSocketChannel.class)//通过反射创建1一个severSocketChannel对象
.option(ChannelOption.SO_BACKLOG,1024)//BACKLOG用于构造服务端套接字ServerSocket对象,
// 标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
// 如果未设置或所设置的值小于1,Java将使用默认值50。
.childHandler(new ChildHandler());//设置绑定的IO处理类,用来处理网络IO事件
// 绑定端口,同步等待成功,返回ChannelFutrue,用于异步操作通知回调
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
}finally {
// 优雅的退出,释放线程池资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
} }
// IO处理类的初始化
private class ChildHandler extends ChannelInitializer {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new TimeServerHandler());
}
} public static void main(String[] args) throws InterruptedException {
int port = 8080;
try {
if(args.length>0&&args[0]!=null){
System.out.println(args[0]);
port = Integer.valueOf(args[0]);
}
} catch (Exception e) {
port = 8080;
}
new TimeServer().bind(port);
}
}
服务端处理IO代码
package com.StickyUnpack; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; public class TimeServerHandler extends ChannelInboundHandlerAdapter {
int count;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;//将请求转为ByteBuf缓冲区
byte[] req = new byte[byteBuf.readableBytes()];//获取byteBuf的可读字节数
byteBuf.readBytes(req);//将缓冲区字节数组复制到req数组中
String body = new String(req,"utf-8")//转换为字符串
//改造去掉客户端传递过来的换行符号,模拟故障造成粘包问题
.substring(0,req.length-System.lineSeparator().length());
System.out.println("the time server receive order:"+body+"the count is:"+ ++count);
// 处理IO内容
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
?new Date(System.currentTimeMillis()).toString():"BAD ORDER";
currentTime = currentTime+System.getProperty("line.separator");
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());//返回客户端的消息转化为ByteBuf对象
ctx.write(resp);//将待应答消息放入缓冲数组中
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();//将应答消息写入SocketChannel中
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客户发送请求端代码
package com.StickyUnpack; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; public class TimeClient {
public void connect(String host,int port){
NioEventLoopGroup workGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
try {
b.group(workGroup).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ClientChildHandler());
ChannelFuture f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
workGroup.shutdownGracefully();
}
}
public class ClientChildHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
}
public static void main(String[] args) {
int port = 8080;
if (args.length>0&&args!=null){
try {
port = Integer.parseInt(args[0]);
} catch (Exception e) {
e.printStackTrace();
}
}
new TimeClient().connect("127.0.0.1",port);
}
}
客户端处理IO代码
package com.StickyUnpack; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; public class TimeClientHandler extends ChannelInboundHandlerAdapter {
int count;
private byte[] req;
// private final ByteBuf firstMessage;
public TimeClientHandler() {
/*byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);*/
// 改造代码
req = ("QUERY TIME ORDER"+System.getProperty("line.separator")).getBytes();
} @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf message = null;
for(int i=0;i<100;i++){
message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
byte[] req = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(req);
String body = new String(req,"utf-8");
System.out.println("Now is:"+body+";The client count is:"+ ++count);
} @Override
public void exceptionCaught(ChannelHandlerContext chc, Throwable throwable) throws Exception {
chc.close();
}
}
运行结果
客户端运行结果
10:38:35.073 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
10:38:35.073 [nioEventLoopGroup-2-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
Now is:BAD ORDER
BAD ORDER
;The client count is:1
服务端运行结果
10:38:35.129 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6751e055
the time server receive order:QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDthe count is:1
the time server receive order:
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDER
QUERY TIME ORDERthe count is:2
从服务端运行结果来看我们期待服务端接收到100条信息,但是运行结果显示只接收到了2条信息,发生了粘包现象,同样客户端也应该接收到100条相应信息,但因为服务端只接收到2条信息,故而只返回两条相应均为BAD ORDER,但是从服务端运行结果看,却只收到1条应答,说明在服务端返回客户端的两条信息也发生了粘包现象。
netty权威指南学习笔记三——TCP粘包/拆包之粘包现象的更多相关文章
- netty权威指南学习笔记四——TCP粘包/拆包之粘包问题解决
发生了粘包,我们需要将其清晰的进行拆包处理,这里采用LineBasedFrameDecoder来解决 LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看 ...
- Hadoop权威指南学习笔记三
HDFS简单介绍 声明:本文是本人基于Hadoop权威指南学习的一些个人理解和笔记,仅供学习參考.有什么不到之处还望指出,一起学习一起进步. 转载请注明:http://blog.csdn.net/my ...
- netty权威指南学习笔记六——编解码技术之MessagePack
编解码技术主要应用在网络传输中,将对象比如BOJO进行编解码以利于网络中进行传输.平常我们也会将编解码说成是序列化/反序列化 定义:当进行远程跨进程服务调用时,需要把被传输的java对象编码为字节数组 ...
- netty权威指南学习笔记二——netty入门应用
经过了前面的NIO基础知识准备,我们已经对NIO有了较大了解,现在就进入netty的实际应用中来看看吧.重点体会整个过程. 按照权威指南写程序的过程中,发现一些问题:当我们在定义handler继承Ch ...
- netty权威指南学习笔记五——分隔符和定长解码器的应用
TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,通常采用以下4中方式: 消息长度固定,累计读取到长度综合为定长LEN的报文后,就认为读取到了一个完整的消息,将计数器置位,重新开始读取下一 ...
- netty权威指南学习笔记八——编解码技术之JBoss Marshalling
JBoss Marshalling 是一个java序列化包,对JDK默认的序列化框架进行了优化,但又保持跟java.io.Serializable接口的兼容,同时增加了一些可调参数和附加特性,这些参数 ...
- netty权威指南学习笔记七——编解码技术之GoogleProtobuf
首先我们来看一下protobuf的优点: 谷歌长期使用成熟度高: 跨语言支持多种语言如:C++,java,Python: 编码后消息更小,更利于存储传输: 编解码性能高: 支持不同协议版本的兼容性: ...
- netty权威指南学习笔记一——NIO入门(3)NIO
经过前面的铺垫,在这一节我们进入NIO编程,NIO弥补了原来同步阻塞IO的不足,他提供了高速的.面向块的I/O,NIO中加入的Buffer缓冲区,体现了与原I/O的一个重要区别.在面向流的I/O中,可 ...
- netty权威指南学习笔记一——NIO入门(1)BIO
公司的一些项目采用了netty框架,为了加速适应公司开发,本博主认真学习netty框架,前一段时间主要看了看书,发现编程这东西,不上手还是觉得差点什么,于是为了加深理解,深入学习,本博主还是决定多动手 ...
随机推荐
- Systemverilog for design 笔记(四)
转载请标明出处 数组.结构体和联合体 1. 结构体(struct) 1.1. 结构体声明 结构体默认是变量,也可以声明为线网 var struct { // 通过var进行结构体变量声明 logic ...
- Python内置模块-logging
一.初识logging模块 import logging logging.debug("debug message") #级别最低,只有在诊断问题时才有兴趣的详细信息. loggi ...
- pytorch神经网络解决回归问题(非常易懂)
对于pytorch的深度学习框架,在建立人工神经网络时整体的步骤主要有以下四步: 1.载入原始数据 2.构建具体神经网络 3.进行数据的训练 4.数据测试和验证 pytorch神经网络的数据载入,以M ...
- Ajax请求回调地狱及解决方案(promise、async和await)
谈及回调地狱发生得情况和解决办法,就必须追溯到原生ajax请求. 先列出服务器提供的数据接口: // 服务器端接口 app.get('/data1', (req, res) => { res.s ...
- java中常用的数据结构--Collection接口及其子类
java中有几种常用的数据结构,主要分为Collection和map两个主要接口(接口只提供方法,并不提供实现),而程序中最终使用的数据结构是继承自这些接口的数据结构类. 一.集合和数组的区别 二.C ...
- 谁说5G网络无敌?第六代Wi-Fi表示不服
导读 随着第五代移动通信技术(5G)正式商用,同属第五代的Wi-Fi技术(802.11ac)的处境就非常尴尬了,除了不存流量费用外,无论是网速.设备连接数还是网络延迟,5G都拥有秒杀802.11ac的 ...
- [经验] 使用 jQuery+JSON 实现国际化
技术选型关键词: [spring boot] [jQuery] [JSON] [JSP] 前言: 关于国际化, 我在一开始的时候就有一个误解, 我认为所谓国际化就是编写一段高技术含量的代码, 然后这 ...
- Kubernetes 二进制部署(二)集群部署(多 Master 节点通过 Nginx 负载均衡)
0. 前言 紧接上一篇,本篇文章我们尝试学习多节点部署 kubernetes 集群 并通过 haproxy+keepalived 实现 Master 节点的负载均衡 1. 实验环境 实验环境主要为 5 ...
- Mybatis入门(五)属性名和字段名不一致解决
在学Mybatis的时候都需要创建一个实体类,但创建实体类的变量必须和数据库的一样,这章就来解决这个有趣的问题 目录: 问题是这样: 输出的结果是: password为空,这就很难受: 解决方法: 第 ...
- Day3-M-Cable master POJ1064
Inhabitants of the Wonderland have decided to hold a regional programming contest. The Judging Commi ...