目录

一、什么是粘包/半包问题

二、TCP粘包/半包发生的原因

三、粘包/半包解决办法

四、Netty中粘包/半包解决示例

1. 采用固定长度数据包编解码方式

2. 采用特殊字符作为边界字符编解码方式

3. 基于长度解码器

五、Netty常用编解码器


一、什么是粘包/半包问题

在客户端发送数据时,实际是把数据写入到了TCP发送缓存里面的; 如图:

1. 如果发送的包的大小比TCP发送缓存的容量大那么这个数据包就会被分成多个包,通过socket多次发送到服务端,服务端第一次从接受缓存里面获取的数据,实际是整个包的一部分,这时候就产生了半包现象,半包不是说只收到了全包的一半,是说收到了全包的一部分。

2. 如果发送的包的大小比TCP发送缓存容量小,并且TCP缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从接受缓存就可能一下读取了多个包,这时候就出现了粘包现象。

服务端从接受缓存读取数据后一般都是进行解码操作,也就是会把byte流转换了pojo对象,如果出现了粘包或者半包现象,则进行转换时候就会出现异常。出现粘包和半包的原因是TCP层不知道上层业务的包的概念,它只是简单的传递流,所以需要上层应用层协议来识别读取的数据是不是一个完整的包


示例:

假设客户端分别发送了两个数据包Data1和Data2给服务端, 由于服务端一次读取到的字节数是不确定的, 故可能存在以下4种情况:
    (1) 服务端分两次读取到了两个独立的数据包,分别是Data1和Data2,没有粘包和拆包


  (2) 服务端一次接收到了两个数据包,Data1和Data2粘合在一起,被称为TCP粘包


  (3) 服务端分两次读取到了两个数据包,第一次读取到了完整的Data1包和Data2包的前一部分内容Data2_1,第二次读取到了Data2包的剩余内容Data2_2,这被称为TCP拆包


  (4) 服务端分两次读取到了两个数据包,第一次读取到了Data1包的部分内容Data1_1,第二次读取到了Data1包的剩余内容Data1_2和Data2包的整包

如果此时服务端TCP接收缓冲区非常小,而数据包Data1和Data2比较大,很有可能会发生第五种可能,即服务端分多次才能将Data1和Data2包接收完全,期间发生多次拆包

二、TCP粘包/半包发生的原因

1. 客户端发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包

2. 客户端待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包

3. 客户端发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包

4. 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包

三、粘包/半包解决办法

1. 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开

2. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充), 例如每个报文的大小为固定长度200字节,这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来

3. 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了


四、Netty中粘包/半包解决示例

服务器端定义:

package com.netty2.sticky;
public class StickyDemoServer {public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {// 采用默认值}}new StickyDemoServer().bind(port);}public void bind(int port) throws Exception {// 第一步:// 配置服务端的NIO线程组// 主线程组, 用于接受客户端的连接,但是不做任何具体业务处理,像老板一样,负责接待客户,不具体服务客户EventLoopGroup bossGroup = new NioEventLoopGroup(1);// 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 类ServerBootstrap用于配置Server相关参数,并启动ServerServerBootstrap b = new ServerBootstrap();// 链式调用// 配置parentGroup和childGroupb.group(bossGroup, workerGroup)// 配置Server通道.channel(NioServerSocketChannel.class)// 配置通道的ChannelPipeline.childHandler(new ChildChannelHandler());// 绑定端口,并启动server,同时设置启动方式为同步ChannelFuture f = b.bind(port).sync();System.out.println(StickyDemoServer.class.getName() + " 启动成功,在地址[" + f.channel().localAddress() + "]上等待客户请求......");// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new StickyDemoServerHandler());}}
}

服务器端处理器定义:

package com.netty2.sticky;public class StickyDemoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;System.out.println("服务器接收到消息:" + in.toString(CharsetUtil.UTF_8));//服务器将收到的信息返回到浏览器端ctx.write(in);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx)throws Exception {ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {cause.printStackTrace();ctx.close();}
}

客户端定义:

package com.netty2.sticky;
public class StickyDemoClient {public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {}}new StickyDemoClient().connect(port, "127.0.0.1");}public void connect(int port, String host) throws Exception {// 工作线程组 EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new StickyDemoClientHandler());}});// 发起异步连接操作ChannelFuture f = b.connect(host, port).sync();// 等待客户端链路关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源group.shutdownGracefully();}}
}

客户端处理器定义:

package com.netty2.sticky;
public class StickyDemoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private static String[] alphabets = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"};@Overridepublic void channelActive(ChannelHandlerContext ctx) {for(int i=0; i<10; i++) {StringBuilder builder = new StringBuilder();builder.append("这是第");builder.append(i);builder.append("条消息, 内容是:");for(int j=0; j<100; j++) {builder.append(alphabets[i]);}builder.append("......");System.out.println("客户端发送消息["+i+"]长度:"+builder.toString().getBytes().length);ctx.writeAndFlush(Unpooled.copiedBuffer(builder.toString(), CharsetUtil.UTF_8));}}@Overridepublic void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {System.out.println("客户端接收到消息: " + in.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

启动后控制台输出:

服务器端:

客户端:

由控制台输出的信息可以看出, 由于没有对粘包/半包进行处理, 客户端在发送第一个数据包时把0-6和第7个消息的一部分作为一个数据包发送到客户端, 客户端没有进行拆包而是直接输出以及发送到客户端; 所以就造成了数据获取不完整;

1. 采用固定长度数据包编解码方式

(1) 固定长度数据包编解码处理器定义:

package com.netty2.sticky;
public class StickyDemoDecodeHandler extends ChannelInboundHandlerAdapter {//存放待拆包数据的缓冲区private ByteBuf cache;private int frameLength;public StickyDemoDecodeHandler(int length) {this.frameLength = length;}static ByteBuf expandCache(ByteBufAllocator alloc, ByteBuf cache, int readable) {ByteBuf oldCache = cache;cache = alloc.buffer(oldCache.readableBytes() + readable);cache.writeBytes(oldCache);oldCache.release();return cache;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf data = (ByteBuf) msg;try {//读取每一个消息,创建缓冲区if (cache == null) {cache = ctx.alloc().buffer(1024);} else {//如果现有的缓冲区容量太小,无法容纳原有数据+新读入的数据,就扩容(重新创建一个大的,并把数据拷贝过去)if (data.readableBytes() > cache.maxCapacity() - cache.writerIndex() ) {cache = expandCache(ctx.alloc(), cache, data.readableBytes());}}//把新的数据读入缓冲区cache.writeBytes(data);/*** 每次读取frameLength(定长)的数据,做为一个包,存储到集合中, 后面将遍历每个包进行发送*/List<ByteBuf> output = new ArrayList<>();while (cache.readableBytes() >= frameLength) {output.add(cache.readBytes(frameLength));}/*** 如果缓冲区还存在部分数据(不是一个完整的数据包), 则调用discardReadBytes()清理缓冲区,将未读数据前移*/if (cache.isReadable()) {cache.discardReadBytes();}for (int i = 0; i < output.size(); i++) {/*** 遍历数据包集合,发送所有数据包*/ctx.fireChannelRead(output.get(i));}} finally {data.release();}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

(2) 重写服务器端的ChildChannelHandler类, 将该处理器加入到pipeline中

private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {/*** 方案1: 将数据包按照固定长度进行解析*/ch.pipeline().addLast("framer", new StickyDemoDecodeHandler(139));  //自定义//ch.pipeline().addLast("framer", new FixedLengthFrameDecoder(139)); //netty封装ch.pipeline().addLast(new StickyDemoServerHandler());}
}

(3) 重写客户端的handler()方法传入的ChannelInitializer匿名内部类, 将该处理器加入到pipeline中

new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {/*** 方案1: 将数据包按照固定长度进行解析*/ch.pipeline().addLast("framer", new StickyDemoDecodeHandler(139));  //自定义//ch.pipeline().addLast("framer", new FixedLengthFrameDecoder(139)); //netty封装ch.pipeline().addLast(new StickyDemoClientHandler());}
}

启动后控制台输出:

服务器端:

客户端:

2. 采用特殊字符作为边界字符编解码方式

(1) 特殊字符编解码处理器定义

package com.netty2.sticky;
public class StickyDemoDecodeHandlerV2 extends ChannelInboundHandlerAdapter {private ByteBuf cache;private byte delimiter; //包分隔符public StickyDemoDecodeHandlerV2(ByteBuf delimiter) {if (delimiter == null) {throw new NullPointerException("delimiter");}if (!delimiter.isReadable()) {throw new IllegalArgumentException("empty delimiter");}this.delimiter =  delimiter.readByte();}static ByteBuf expandCache(ByteBufAllocator alloc, ByteBuf cache, int readable) {ByteBuf oldCache = cache;cache = alloc.buffer(oldCache.readableBytes() + readable);cache.writeBytes(oldCache);oldCache.release();return cache;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf data = (ByteBuf) msg;try {if (cache == null) {//读取每一个消息,创建缓冲区cache = ctx.alloc().buffer(1024);} else {//如果现有的缓冲区容量太小,无法容纳原有数据+新读入的数据,就扩容(重新创建一个大的,并把数据拷贝过去)if (data.readableBytes() > cache.maxCapacity() - cache.writerIndex()) {cache = expandCache(ctx.alloc(), cache, data.readableBytes());}}//把新的数据读入缓冲区cache.writeBytes(data);List<ByteBuf> output = new ArrayList<>();int frameIndex = 0;int frameEndIndex = 0;int length = cache.readableBytes();while (frameIndex <= length) {//获取缓冲区中边界字符的下标位置; frameIndex:开始读取的下标位置frameEndIndex = cache.indexOf(frameIndex + 1, length, delimiter);if (frameEndIndex == -1) {//如果缓冲区没有边界字符, 说明缓冲区中未读范围中是一个不完整的数据包, 则清理缓冲区cache.discardReadBytes();break;}//如果存在边界字符, 则读取到边界字符的位置output.add(cache.readBytes(frameEndIndex - frameIndex));//将指针跳过边界字符所占的下标位置cache.skipBytes(1);//更新读取位置frameIndex = frameEndIndex + 1;}if (cache.isReadable()) {cache.discardReadBytes();}for (int i = 0; i < output.size(); i++) {//遍历数据包集合,发送所有数据包ctx.fireChannelRead(output.get(i));}} finally {data.release();}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

(2) 重写服务器端的ChildChannelHandler类, 将该处理器加入到pipeline中

private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {/*** 方案2: 使用特殊字符作为边界()自定义*/ch.pipeline().addLast("framer", new StickyDemoDecodeHandlerV2(Unpooled.wrappedBuffer(new byte[] { '#' }))); //自定义//ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Unpooled.wrappedBuffer(new byte[] { '#' }))); //netty封装ch.pipeline().addLast(new StickyDemoServerHandler());}
}

(3) 修改StickyDemoServerHandler的channelRead()方法, 在服务器端回传数据时需要加上边界字符

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;System.out.println("服务器接收到消息:" + in.toString(CharsetUtil.UTF_8));//服务器将收到的信息返回到浏览器端ctx.write(in);/*** 服务器端发送数据时需要给每个包最后添加一个边界字符*/ctx.write(Unpooled.copiedBuffer("#", CharsetUtil.UTF_8));
}

(4) 重写客户端的handler()方法传入的ChannelInitializer匿名内部类, 将该处理器加入到pipeline中

new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {/*** 方案2: 使用特殊字符作为边界(netty封装)*/ch.pipeline().addLast("framer", new StickyDemoDecodeHandlerV2(Unpooled.wrappedBuffer(new byte[] { '#' }))); //自定义//ch.pipeline().addLast("framer", new DelimiterBasedFrameDecoder(8192, Unpooled.wrappedBuffer(new byte[] { '#' }))); //netty封装ch.pipeline().addLast(new StickyDemoClientHandler());}
}

(5) 修改StickyDemoClientHandler的channelActive()方法, 在发送的信息后添加边界字符

@Override
public void channelActive(ChannelHandlerContext ctx) {for(int i=0; i<10; i++) {StringBuilder builder = new StringBuilder();builder.append("这是第");builder.append(i);builder.append("条消息, 内容是:");for(int j=0; j<100; j++) {builder.append(alphabets[i]);}builder.append("......");/*** 添加边界字符, 可以在读取时按照边界字符进行编解码*/builder.append("#");System.out.println("客户端发送消息["+i+"]长度:"+builder.toString().getBytes().length);ctx.writeAndFlush(Unpooled.copiedBuffer(builder.toString(), CharsetUtil.UTF_8));}
}

启动后控制台输出:

服务器端:

客户端:

3. 基于长度解码器

a. 基于数据头不固定长度的解码器:LengthFieldBasedFrameDecoder

参数说明:

  • maxFrameLength:包的最大长度
  • lengthFieldOffset:长度属性的起始位(偏移位),包中存放长度属性字段的起始位置
  • lengthFieldLength:长度属性的长度
  • lengthAdjustment:长度调节值,在总长被定义为包含包头长度时,修正信息长度
  • initialBytesToStrip:跳过的字节数,根据需要跳过lengthFieldLength个字节,以便接收端直接接受到不含“长度属性”的内容

b. LengthFieldPrepender 编码器
参数说明:

  • lengthFieldLength:长度属性的字节长度
  • lengthIncludesLengthFieldLength:false,长度字节不算在总长度中; true,算到总长度中

(1)服务器端定义:

package com.netty2.lf;
public class LengthFieldDecodeDemoServer {public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {// 采用默认值}}new LengthFieldDecodeDemoServer().bind(port);}public void bind(int port) throws Exception {// 第一步:// 配置服务端的NIO线程组// 主线程组, 用于接受客户端的连接,但是不做任何具体业务处理,像老板一样,负责接待客户,不具体服务客户EventLoopGroup bossGroup = new NioEventLoopGroup(1);// 工作线程组, 老板线程组会把任务丢给他,让手下线程组去做任务,服务客户EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 类ServerBootstrap用于配置Server相关参数,并启动ServerServerBootstrap b = new ServerBootstrap();// 链式调用// 配置parentGroup和childGroupb.group(bossGroup, workerGroup)// 配置Server通道.channel(NioServerSocketChannel.class)// 配置通道的ChannelPipeline.childHandler(new ChildChannelHandler());// 绑定端口,并启动server,同时设置启动方式为同步ChannelFuture f = b.bind(port).sync();System.out.println(LengthFieldDecodeDemoServer.class.getName() + " 启动成功,在地址[" + f.channel().localAddress() + "]上等待客户请求......");// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast("addLength", new LengthFieldPrepender(4, false));ch.pipeline().addLast("framer", new LengthFieldBasedFrameDecoder(1024, 0, 4, 0,4));ch.pipeline().addLast(new LengthFieldDecodeDemoServerHandler());}}
}

(2) 服务器端处理器定义:

package com.netty2.lf;
public class LengthFieldDecodeDemoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;System.out.println("服务器接收到消息:" + in.toString(CharsetUtil.UTF_8));ctx.write(in);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {cause.printStackTrace();ctx.close();}
}

(3) 客户端定义:

package com.netty2.lf;
public class LengthFieldDecodeDemoClient {public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.valueOf(args[0]);} catch (NumberFormatException e) {}}new LengthFieldDecodeDemoClient().connect(port, "127.0.0.1");}public void connect(int port, String host) throws Exception {// 工作线程组 EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast("framer", new LengthFieldBasedFrameDecoder(1024, 0, 4, 0,4));ch.pipeline().addLast("addLength", new LengthFieldPrepender(4, false));ch.pipeline().addLast(new LengthFieldDecodeDemoClientHandler());}});// 发起异步连接操作ChannelFuture f = b.connect(host, port).sync();// 等待客户端链路关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源group.shutdownGracefully();}}
}

(4) 客户端处理器定义:

package com.netty2.lf;
public class LengthFieldDecodeDemoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private Random rand = new Random();private static String[] alphabets = {"A", "B", "C", "D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O", "P"};@Overridepublic void channelActive(ChannelHandlerContext ctx) {for(int i=0; i<10; i++) {StringBuilder builder = new StringBuilder();builder.append("这是第");builder.append(i);builder.append("条消息, 内容是:");for(int j=0; j<rand.nextInt(20); j++) {builder.append(alphabets[i]);}builder.append("......");System.out.println(builder.toString().getBytes().length);ctx.writeAndFlush(Unpooled.copiedBuffer(builder.toString(),CharsetUtil.UTF_8));}}@Overridepublic void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {System.out.println("客户端接收到消息: " + in.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

启动后控制台输出:

服务器端:

客户端:

客户端处理器V2定义(加入头部信息)

package com.netty2.lf;
public class LengthFieldDecodeDemoClientHandlerV2 extends SimpleChannelInboundHandler<ByteBuf> {private Random rand = new Random();private static String[] alphabets = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P" };@Overridepublic void channelActive(ChannelHandlerContext ctx) {for (int i = 0; i < 10; i++) {StringBuilder builder = new StringBuilder();builder.append("这是第");builder.append(i);builder.append("条消息, 内容是:");for (int j = 0; j < rand.nextInt(20); j++) {builder.append(alphabets[i]);}builder.append("......");ByteBuf header = Unpooled.copiedBuffer("Header", CharsetUtil.UTF_8);ByteBuf body = Unpooled.copiedBuffer(builder.toString(), CharsetUtil.UTF_8);CompositeByteBuf cbf = ctx.alloc().compositeBuffer();cbf.addComponents(true, header, body);ctx.writeAndFlush(cbf);}}@Overridepublic void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {System.out.println("客户端接收到消息: " + in.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

启动后控制台输出:

服务器端:

客户端:


五、Netty常用编解码器

LineBasedFrameDecoder

  • 回车换行解码器
  • 配合StringDecoder

DelimiterBasedFrameDecoder

  • 分隔符解码器

FixedLengthFrameDecoder

  • 固定长度解码器

LengthFieldBasedFrameDecoder

  • 基于'长度'解码器(私有协议最常用)

Netty粘包/半包问题解析相关推荐

  1. 三、Netty的粘包半包问题解决

    一.定义 TCP 传输中,客户端发送数据,实际是把数据写入到了 TCP 的缓存中,粘包和半包也就会在此时产生.客户端给服务端发送了两条消息ABC和DEF,服务端这边的接收会有多少种情况呢?有可能是一次 ...

  2. Netty框架之TCP粘包/半包解决方案

    Netty框架之TCP粘包/半包解决方案 一.TCP粘包 二.TCP半包 三.TCP粘包/半包解决方案 1.FixedLengthFrameDecoder定长解析器 2.LineBasedFrameD ...

  3. TCP 粘包半包 netty 编解码 三者关系

    1 何为粘包 / 半包? 对方一次性接收了多条消息这种现象,我们就称之为 粘包现象. 对方多次接收了不完整消息这种现象,我们就称之为 半包现象. 粘包的原因: 发送方发送的消息 < 缓冲区大小 ...

  4. Netty如何解决粘包半包问题

    何为粘包 / 半包? 比如,我们发送两条消息:ABC 和 DEF,那么对方收到的就一定是 ABC 和 DEF 吗? 不一定,对方可能一次就把两条消息接收完了,即 ABCDEF:也可能分成了好多次,比如 ...

  5. netty——黏包半包的解决方案、滑动窗口的概念

    黏包半包 滑动窗口 在深入理解黏包半包问题之前,先了解TCP的一个知识点--滑动窗口 我们都指定tcp是一种可靠的传输协议,这主要是因为在tcp中客户端给服务器端发送一条消息,要等待服务器端的应答,如 ...

  6. 网络:什么是TCP粘包/半包?怎么解决这个问题

    在socket网络编程中,都是端到端通信,由客户端端口+服务端端口+客户端IP+服务端IP+传输协议组成的五元组可以明确的标识一条连接.在TCP的socket编程中,发送端和接收端都有成对的socke ...

  7. websocket是否需要处理粘包半包问题分析

    结论: ​ 不需要. 背景: ​ 公司通信涉及到websocket相关,我们都知道websocket是基于tcp的,而tcp是面向字节流的,是需要处理粘包半包问题的.那么websocket是否需要处理 ...

  8. 04、Netty学习笔记—(黏包半包及协议设计解析)

    文章目录 一.粘包与半包 1.1.现象分析 1.1.1.粘包.半包情况分析 1.1.2.滑动窗口.MSS限制.Nagle算法介绍 1.2.粘包.半包现象复现 1.2.1.粘包复现 1.2.2.半包复现 ...

  9. Netty处理TCP半包和粘包问题

    Netty在RPC中充当着重要的核心角色,封装了对JDK NIO的复杂操作. TCP连接中存在半包和粘包问题,其历史原因不在追究,Netty在对问题处理上提供了现有的模板方法,用户需要自己定义编码和解 ...

最新文章

  1. 双目图像重叠的视差计算_双目视觉(stereo vision)
  2. 再学 GDI+[68]: 路径画刷(8) - SetBlendTriangularShaped、SetBlendBellShape
  3. 你不知道Linux的10个最危险的命令
  4. 鲸鱼网络连接_登陆鲸鱼:在网络上读书,第1部分
  5. [Vue warn]: Unknown custom element: <Top> - did you register the component correctly?
  6. c语言分号应用,问什么C程序里总是提示缺少分号;,而明明有分号?
  7. 免费智能AI文章生成器-只需要输入关键词自动生成文章的软件
  8. 平面设计的核心本质是什么
  9. 《研发企业管理——思想、方法、流程和工具》——第1章 企业管理基本理念 1.1 企业的根本目标及其内涵...
  10. 单位可不可以起诉来解除劳动关系
  11. 7. 全概率公式与贝叶斯公式
  12. guid主分区表损坏如何处理_电脑GUID格式GPT硬盘的引导如何修复|GUID的GPT硬盘引导损坏了怎么办...
  13. Atitit 职业资格证书分类等级 目录 1. 等级 :初级(五级)、中级(四级)、高级(三级)、技师(二级)和高级技师(一级)。 1 2. 折叠分类 2 2.1. 生产、运输设备操作人员 2 2
  14. final修饰的变量就是常量?final修饰局部变量在栈还是堆还是常量池中?
  15. 10个顶尖响应式HTML5网页
  16. 将Word文档转换为eReader或iBooks的ePub格式
  17. 轧钢测径仪软件分析图 钢材质量直观体现
  18. 三维扫描在崇明花博会异形天桥及旋转楼梯测绘中的应用
  19. 基于stm32单片机农业智能温室大棚温湿度光照测量报警系统Proteus仿真
  20. JAVA中int 是什么意思,java中int和Integer什么区别

热门文章

  1. Linux中Realsense i435做输入端运行ORB_SLAM3
  2. Word文档排版有哪些实用的技巧?新手能快速掌握吗?
  3. 蓝湖某个项目上传过多图片,导致特别卡
  4. 20189216 《网络攻防技术》第六周作业
  5. element-ui 多选表格 隐藏表头勾选框
  6. 理想论坛通达信接口的抽象类
  7. “男怕入错行”行业选择有多重要?选Java我没后悔
  8. 【CCNP网络工程师工作内容是什么?】
  9. 2016服务器文件夹权限设置,Server 2016特定用户权限划分,只显示有权限的文件夹,无法权限文件夹无法看到...
  10. 条件运算符? : 与if-else语句的对比及用法详解