TCP 粘包和拆包
1.基本介绍
1. TCP是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发给接收端的包,更有效的发给对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样做虽然提高了效率,但是接收端就难于分辨出完整的数据包了,因为面向流的通信是无消息保护边界的
2. 由于TCP无消息保护边界, 需要在接收端处理消息边界问题,也就是我们所说的粘包、拆包问题
2.TCP粘包、拆包图解
3. TCP 粘包和拆包解决方案
1. 使用自定义协议 + 编解码器 来解决
2. 关键就是要解决 服务器端每次读取数据长度的问题, 这个问题解决,就不会出现服务器多读或少读数据的问题,从而避免的TCP 粘包、拆包 。
4.解决详情代码
1. 服务端
public class MyServer { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup worker = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(boss,worker) .channel(NioServerSocketChannel.class) .childHandler(new ServerInitalizer()); ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); //监视关闭 channelFuture.channel().closeFuture().sync(); }finally { boss.shutdownGracefully(); worker.shutdownGracefully(); } } }
2.服务端初始化类
//channel初始化类 public class ServerInitalizer extends ChannelInitializer<SocketChannel> { /** */ @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //需要添加处理类 pipeline.addLast(new ServerHandler()); } }
3.服务端channel处理类
//服务端channel处理类 public class ServerHandler extends SimpleChannelInboundHandler<ByteBuf> { //统计服务器接收数据次数,展示粘包效果 private int num = 0; /** * 接收客户端消息 * 模拟粘包拆包,可能一次读取完成,也有可能多次读取 */ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //创建msg数组 byte[] bytes = new byte[msg.readableBytes()]; //msg读取到数组 msg.readBytes(bytes); //转为字符串 String s = new String(bytes, Charset.forName("utf-8")); System.out.println("服务器第" + (++this.num) + "次接收数据为:" + s); //回送消息给客户端 ByteBuf byteBuf = Unpooled.copiedBuffer("服务端回送消息....", Charset.forName("utf-8")); ctx.writeAndFlush(byteBuf); } }
4.客户端
public class MyClient { public static void main(String[] args) throws InterruptedException { NioEventLoopGroup eventExecutors = new NioEventLoopGroup(); try{ Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors) .channel(NioSocketChannel.class) .handler(new ClientInitalizer()); //建立链接 ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7000).sync(); //监视关闭 channelFuture.channel().closeFuture().sync(); }finally { eventExecutors.shutdownGracefully(); } } }
5.客户端初始化类
public class ClientInitalizer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ClientHandler()); } }
6.客户端channel处理类
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf>{ //统计服务器接收数据次数,展示粘包效果 private int num=0; /** *客户端接收消息 */ @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { //创建msg数组 byte[] buffer = new byte[msg.readableBytes()]; msg.readBytes(buffer); //转为字符串 String s = new String(buffer, Charset.forName("utf-8")); System.out.println("服务端发来消息:"+s+",消息次数:"+(++this.num)); } /** *客户端发送消息 * @param ctx */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { /* 模拟粘包拆包,可能一次发送完成,也有可能多次发送 */ for (int i = 0; i <10 ; ++i) { //模拟粘包 ByteBuf buffer = Unpooled.copiedBuffer("hello,server " + i, Charset.forName("utf-8")); ctx.writeAndFlush(buffer); }; } }