-
2021-10-09 15:16:12
服务端接收消息解析老是出现消息不全的问题
解码编码器
@Component public class SimpleChatChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) { ChannelPipeline pipeline = socketChannel.pipeline(); ByteBuf bufAck = Unpooled.copiedBuffer("</ack>".getBytes()); ByteBuf bufReply = Unpooled.copiedBuffer("</reply>".getBytes()); pipeline.addLast(new DelimiterBasedFrameDecoder(5 * 1024 * 1024,bufAck,bufReply)); pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8)); pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new SimpleChatServerHandler()); } }
pipeline.addLast(new DelimiterBasedFrameDecoder(5 * 1024 * 1024,bufAck,bufReply));
参数5 * 1024 * 1024 缓存数据大小,bufAck,bufReply 以“</ack>”或“</reply>”结尾
结合字符串解码器pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));一起使用。
handler即可使用string处理
public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> {}
更多相关内容 -
Netty学习——Netty解决TCP粘包与拆包问题
2022-03-11 14:22:33它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小包封装成一个大的数据包进行发送,这就是TCP的粘包和拆包问题。 TCP 粘包与拆包问题...TCP 粘包与拆包
TCP是一个“流”协议,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小包封装成一个大的数据包进行发送,这就是TCP的粘包和拆包问题。
TCP 粘包与拆包问题说明
假设客户端分别发送了俩个数据包D1和D2到服务端。但服务端每次读到的字节数是不确定的,所以可能存在下面四种情况:- 服务端分两次读取到了D1和D2数据包,也就是理想的正常情况,没有发生粘包和拆包。
- 服务端一次接收收到了两个数据包,此时,D1和D2粘到了一起,这种情况被称为TCP粘包。
- 服务端分两次读取到了两个数据包,第一次读取到了D1完整的数据包和D2部分的数据,第二次读取到了D2剩余的数据,这种情况被称为TCP拆包。
- 服务端分两次读取到了两个数据包,第一次读取到了D2部分数据,第二次读取到了D1剩余的数据和D2完整的数据包,这种情况和第三种情况一样是TCP拆包。
- 第五种情况要假设TCP接收滑窗非常小,而数据包D1和D2比较大,这样就有可能发生服务端多次接收才能完成接收,期间会发生多次拆包。
TCP 粘包与拆包发生的原因
问题产生的原因有三点:
- 程序写入的字节大小大于套接口发送缓冲去大小。
- 进行MSS大小的TCP分段。
- 以太网帧的payload大于MTU进行IP分片。
Netty中解决TCP粘包与拆包问题
TCP粘包与拆包异常案例
我们通过一个简单的Netty服务端和客户端例子来复现一下粘包和拆包的异常情况。这个例子中客户端会向服务端发送一个指定的字符串来查询当前时间,服务端接收客户端发送的字符串,如果是合法的查询字符串则返回当前的系统实现,否则返回错误提示。
服务端TimeServer.java
package problem; import java.util.logging.Logger; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import problem.handler.TimeServerHandler; public class TimeServer { private static final Logger logger = Logger.getLogger(TimeServer.class.getName()); public void run(int port) { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // TODO Auto-generated method stub ch.pipeline().addLast(new TimeServerHandler()); } }); ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { logger.warning(e.getLocalizedMessage()); } finally { bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } } public static void main(String[] args) { new TimeServer().run(8080); } }
服务端处理类TimeServerHandler.java
package problem.handler; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.logging.Logger; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class TimeServerHandler extends ChannelInboundHandlerAdapter { private static final Logger logger = Logger.getLogger(TimeServerHandler.class.getName()); private int counter; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, StandardCharsets.UTF_8).substring(0, req.length - System.getProperty("line.separator").length()); System.out.println("The time server receive order : " + body + "; This counter is : " + (++counter)); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() + System.getProperty("line.separator") : "BAD ORDERE"; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.warning(cause.getLocalizedMessage()); ctx.close(); } }
客户端TimeClient.java
package problem; import java.util.logging.Logger; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import problem.handler.TimeClientHandler; public class TimeClient { private static final Logger logger = Logger.getLogger(TimeClient.class.getName()); public void run(String host, int port) { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // TODO Auto-generated method stub ch.pipeline().addLast(new TimeClientHandler()); } }); ChannelFuture future = b.connect(host, port).sync(); future.channel().closeFuture().await(5000L); } catch (InterruptedException e) { logger.warning(e.getLocalizedMessage()); } finally { group.shutdownGracefully(); } } public static void main(String[] args) { new TimeClient().run("127.0.0.1", 8080); } }
客户端处理类TimeClientHandler.java
package problem.handler; import java.nio.charset.StandardCharsets; import java.util.logging.Logger; 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 { private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName()); private byte[] req; private int counter; public TimeClientHandler() { req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("client connect server successful!"); 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 { System.out.println("读取"); ByteBuf buf = (ByteBuf) msg; byte[] receiveByte = new byte[buf.readableBytes()]; buf.readBytes(receiveByte); String body = new String(receiveByte, StandardCharsets.UTF_8); System.out.println("Now is " + body + "; counter:" + (++counter)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.err.println(cause.getLocalizedMessage()); ctx.close(); } }
运行效果
服务端
可以看到服务端只接收了两次,这里发生了粘包客户端
同样客户端应该收到两条回复才对,但是这里只有一条,也发生了粘包。由于我们程序中没有考虑粘包和拆包所以发生了上面的情况。使用Netty中的编码器解决TCP粘包问题
为了解决TCP粘包和拆包导致的半包读写问题,Netty提供了一些自带的编码器用来处理半包。下面的代码是对上面发生异常的代码做的修改。
TimeServer.java下在childHandler方法下做如下修改
ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new TimeServerHandler());
也就是在原有的基础上增加了
LineBasedFrameDecoder
这个解码器和StringDecoder
解码器。TimeServerHandler.java下将channelRead这个方法做如下修改
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // TODO Auto-generated method stub // ByteBuf buf = (ByteBuf) msg; // byte[] req = new byte[buf.readableBytes()]; // buf.readBytes(req); // String body = new String(req, StandardCharsets.UTF_8).substring(0, // req.length - System.getProperty("line.separator").length()); String body = (String) msg; System.out.println("The time server receive order : " + body + "; This counter is : " + (++counter)); String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() + System.getProperty("line.separator") : "BAD ORDERE"; ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); }
可以看到,将上面转化接收数据的过程注释掉,然后把msg强转为String即可(相当简洁)。
TimeClient.java在handler方法中做如下修改
ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new TimeClientHandler());
修改方式和服务端一样。
TimeClientHandler在channelRead方法中做如下修改
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // System.out.println("读取"); // ByteBuf buf = (ByteBuf) msg; // byte[] receiveByte = new byte[buf.readableBytes()]; // buf.readBytes(receiveByte); // String body = new String(receiveByte, StandardCharsets.UTF_8); String body = (String) msg; System.out.println("Now is " + body + "; counter:" + (++counter)); }
同样和服务端一样。
运行效果
服务端
客户端
此时就会发现粘包的问题已经解决了,使用Netty来解决对使用者来说是很方便的,只需要将支持半包的解码的handler添加到ChannelPipeline
中即可,不需要额外的代码。原理分析
LineBasedFrameDecoder
的工作原理是它依次遍历ByteBuf中的可读字节,判断是否有"\n"或者"\r\n",如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,同时支持配置单行最大长度,如果连续读取到最大长度后没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。
StringDecoder
的作用就是将接收到的对象转为字符串,然后再继续调用后面的handler。LineBasedFrameDecoder
+StringDecoder
组合就是按行切换的文本解码器。初次之外,Netty还有其他多种解码器,用来满足不同的需求。 -
TCP粘包,拆包及解决方法
2018-05-24 00:19:44在进行Java NIO学习时,发现,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。我们都知道TCP属于传输层的协议,传输...在进行Java NIO学习时,发现,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。我们都知道TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议。那么UDP是否会发生粘包或拆包的现象呢?答案是不会。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。粘包、拆包表现形式现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。粘包、拆包发生原因发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。等等。粘包、拆包解决办法通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。等等。
-
tcp的拆包,黏包解决方案
2019-08-23 16:04:50在进行Java NIO学习时,发现,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。 我们都知道TCP属于传输层的协议,...在进行Java NIO学习时,发现,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。
我们都知道TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议。那么UDP是否会发生粘包或拆包的现象呢?答案是不会。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
粘包、拆包表现形式
现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:
第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。
第二种情况,接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。
第三种情况,这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。
粘包、拆包发生原因
发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
等等。
粘包、拆包解决办法
通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
等等。
样例程序
我将在程序中使用两种方法来解决粘包和拆包问题,固定数据包长度和添加长度首部,这两种方法各有优劣。固定数据包长度传输效率一般,尤其是在要发送的数据长度长短差别很大的时候效率会比较低,但是编程实现比较简单;添加长度首部虽然可以获得较高的传输效率,冗余信息少且固定,但是编程实现较为复杂。下面给出的样例程序是基于之前的文章《Java中BIO,NIO和AIO使用样例》中提到的NIO实例的,如果对NIO的使用还不是很熟悉,可以先了解一下Java中NIO编程。
固定数据包长度
这种处理方式的思路很简单,发送端在发送实际数据前先把数据封装为固定长度,然后在发送出去,接收端接收到数据后按照这个固定长度进行拆分即可。发送端程序如下:
-
// 发送端
-
String msg = "hello world " + number++;
-
socketChannel.write(ByteBuffer.wrap(new FixLengthWrapper(msg).getBytes()));
-
// 封装固定长度的工具类
-
public class FixLengthWrapper {
-
public static final int MAX_LENGTH = 32;
-
private byte[] data;
-
public FixLengthWrapper(String msg) {
-
ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_LENGTH);
-
byteBuffer.put(msg.getBytes());
-
byte[] fillData = new byte[MAX_LENGTH - msg.length()];
-
byteBuffer.put(fillData);
-
data = byteBuffer.array();
-
}
-
public FixLengthWrapper(byte[] msg) {
-
ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_LENGTH);
-
byteBuffer.put(msg);
-
byte[] fillData = new byte[MAX_LENGTH - msg.length];
-
byteBuffer.put(fillData);
-
data = byteBuffer.array();
-
}
-
public byte[] getBytes() {
-
return data;
-
}
-
public String toString() {
-
StringBuilder sb = new StringBuilder();
-
for (byte b : getBytes()) {
-
sb.append(String.format("0x%02X ", b));
-
}
-
return sb.toString();
-
}
-
}
可以看到客户端在发送数据前首先把数据封装为长度为32bytes的数据包,这个长度是根据目前实际数据包长度来规定的,这个长度必须要大于所有可能出现的数据包的长度,这样才不会出现把数据“截断”的情况。接收端程序如下:
-
private static void processByFixLength(SocketChannel socketChannel) throws IOException {
-
while (socketChannel.read(byteBuffer) > 0) {
-
byteBuffer.flip();
-
while (byteBuffer.remaining() >= FixLengthWrapper.MAX_LENGTH) {
-
byte[] data = new byte[FixLengthWrapper.MAX_LENGTH];
-
byteBuffer.get(data, 0, FixLengthWrapper.MAX_LENGTH);
-
System.out.println(new String(data) + " <---> " + number++);
-
}
-
byteBuffer.compact();
-
}
-
}
可以看出接收端的处理很简单,只需要每次读取固定的长度即可区分出来不同的数据包。
添加长度首部
这种方式的处理较上面提到的方式稍微复杂一点。在发送端需要给待发送的数据添加固定的首部,然后再发送出去,然后在接收端需要根据这个首部的长度信息进行数据包的组合或拆分,发送端程序如下:
-
// 发送端
-
String msg = "hello world " + number++;
-
// add the head represent the data length
-
socketChannel.write(ByteBuffer.wrap(new PacketWrapper(msg).getBytes()));
-
// 添加长度首部的工具类
-
public class PacketWrapper {
-
private int length;
-
private byte[] payload;
-
public PacketWrapper(String payload) {
-
this.payload = payload.getBytes();
-
this.length = this.payload.length;
-
}
-
public PacketWrapper(byte[] payload) {
-
this.payload = payload;
-
this.length = this.payload.length;
-
}
-
public byte[] getBytes() {
-
ByteBuffer byteBuffer = ByteBuffer.allocate(this.length + 4);
-
byteBuffer.putInt(this.length);
-
byteBuffer.put(payload);
-
return byteBuffer.array();
-
}
-
public String toString() {
-
StringBuilder sb = new StringBuilder();
-
for (byte b : getBytes()) {
-
sb.append(String.format("0x%02X ", b));
-
}
-
return sb.toString();
-
}
-
}
从程序可以看到,发送端在发送数据前首先给待发送数据添加了代表长度的首部,首部长为4bytes(即int型长度),这样接收端在收到这个数据之后,首先需要读取首部,拿到实际数据长度,然后再继续读取实际长度的数据,即实现了组包和拆包的操作。程序如下:
-
private static void processByHead(SocketChannel socketChannel) throws IOException {
-
while (socketChannel.read(byteBuffer) > 0) {
-
// 保存bytebuffer状态
-
int position = byteBuffer.position();
-
int limit = byteBuffer.limit();
-
byteBuffer.flip();
-
// 判断数据长度是否够首部长度
-
if (byteBuffer.remaining() < 4) {
-
byteBuffer.position(position);
-
byteBuffer.limit(limit);
-
continue;
-
}
-
// 判断bytebuffer中剩余数据是否足够一个包
-
int length = byteBuffer.getInt();
-
if (byteBuffer.remaining() < length) {
-
byteBuffer.position(position);
-
byteBuffer.limit(limit);
-
continue;
-
}
-
// 拿到实际数据包
-
byte[] data = new byte[length];
-
byteBuffer.get(data, 0, length);
-
System.out.println(new String(data) + " <---> " + number++);
-
byteBuffer.compact();
-
}
-
}
关键信息已经在程序中做了注释,可以很明显的感觉到这种方法的处理难度相对于固定长度要大一些,不过这种方式可以获取更大的传输效率。
这里需要提醒各位同学一个问题,由于我在测试的时候采用的是一台机器连续发送数据来模拟高并发的场景,所以在测试的时候会发现服务器端收到的数据包的个数经常会小于包的序号,好像发生了丢包。但经过仔细分析可以发现,这种情况是因为TCP发送缓存溢出导致的丢包,也就是这个数据包根本没有发出来。也就是说,发送端发送数据过快,导致接收端缓存很快被填满,这个时候接收端会把通知窗口设置为0从而控制发送端的流量,这样新到的数据只能暂存在发送端的发送缓存中,当发送缓存溢出后,就出现了我上面提到的丢包,这个问题可以通过增大发送端缓存来缓解这个问题,
socketChannel.socket().setSendBufferSize(102400);
当然这个话题不在本文的讨论范围,如果有兴趣的同学可以参阅《TCP/IP详解卷一》中的拥塞窗口一章。
关于源码说明,源码默认是把粘包和拆包处理这一部分注释掉了,分别位于NIOTcpServer和NIOTcpClient文件中,需要测试粘包和拆包处理程序的同学需要把这一段注释给去掉。
最后给出源码下载地址
参考
-
-
netty中TCP的黏包/拆包解决之道
2017-01-31 12:03:32TCP黏包/拆包的基础知识 2.没考虑TCP黏包/拆包 导致的异常案例 3.netty中解决TCP黏包/拆包的方法 1.TCP黏包/拆包的原理TCP 是一个“流”协议,所谓流就是没有界限的一串数据。TCP并不了解上层业务数据的具体... -
TCP粘包和拆包
2019-07-15 11:04:27它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 假设客户端分别... -
Golang TCP粘包拆包问题的解决方法
2021-01-03 02:57:06于是通过查阅资料,发现这个就是传说中的TCP粘包问题。下面通过编写代码来重现这个问题: 服务端代码 server/main.go func main() { l, err := net.Listen(tcp, :4044) if err != nil { panic(err) } fmt.... -
tcp黏包与拆包
2018-08-13 20:03:001.黏包2.封包与拆包 1.黏包 1.为什么出现黏包 1.发送方原因 tcp默认会使用Nagle算法,而Nagle算法主要做两件事。1)只有上一个分组得到确认,才会发送下一个分组 2)收集多个小分组,在一个确认到来时一起... -
unity实现Socket通讯(内含tcp粘包/拆包解决)
2020-12-25 17:39:56通过socket通讯实现服务器与客户端的连接。首先服务器利用udp广播发送自己的ip地址,客户端在收到广播后通过此ip以tcp连接的方式连接服务器来通讯。 -
TCP粘包、拆包及解决办法
2020-08-06 15:13:45TCP粘包、拆包及解决办法 文章目录TCP粘包、拆包及解决办法1. TCP会发生粘包、拆包,UDP会发生粘包、拆包吗?2. 什么是粘包、拆包?为什么会发生TCP粘包、拆包?3. 粘包、拆包解决办法 1. TCP会发生粘包、拆包,UDP... -
计算机网络之TCP粘包、拆包
2022-03-17 12:17:49计算机网络之TCP粘包、拆包 -
TCP的黏包与拆包
2019-08-18 00:28:28之前以为这是多么高大上的问题,后来才发现这个问题实在是是简单得不得了,其实在大三的课程设计中就有...但是其实是有更好的方法处理的,TCP是保证送到、有序的。 我在想如果发生这两张异常,有没有好的办法解决? -
TCP粘包和拆包问题
2021-01-31 22:29:471)产生TCP粘包和拆包问题的主要原因是,...如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。TCP属于传输层的协议,传输层除... -
TCP粘包、拆包与解决方案、C++ 实现
2022-01-10 23:05:40说明: TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有...TCP粘包、拆包图解 假设客户端分别发送了两个数据包D1和D2给服务端,由 -
TCP粘包和拆包的定义,产生的原因以及解决方案
2019-07-12 09:46:00TCP粘包:指发送方发送的若干数据包在接收方接收时粘成一团,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾 产生的原因: 1.发送方的原因:TCP默认使用Nagle算法,而Nagle算法主要做两件事情:只有上一个... -
关于TCP黏包问题的解决思路
2018-04-15 19:29:03原文博客地址:http://blog.csdn.net/zhangxinrun/article/details/6721495TCP粘包分析这两天看csdn有一些关于socket粘包,socket缓冲区设置的问题,发现自己不是很清楚,所以查资料了解记录一下: 一 .两个简单... -
Netty - TCP粘包&拆包解决方案
2021-07-06 16:46:41TCP黏包/拆包 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的... -
TCP粘包拆包组包应用之协议长度方式与标志符方式-易语言
2021-06-12 03:30:57对于新手来说,TCP组包粘包是个脑阔疼的事儿,不记得啥时候给人写的例子,翻出来发一下。一共两种模式 如图为协议长度模式 将要发的消息,标志符,长度,内容告诉目标,让其拆开~ 一种为标志符处理,各有喜好,自行... -
Netty 黏包拆包机制
2021-04-15 19:13:26黏包和拆包的产生是由于TCP拥塞控制算法(比如angle算法)和TCP缓冲区机制导致的,angle算法简单来说就是通过一些规则来尽可能利用网络带宽,尽可能的发送足够大的数据。TCP(发送/接收)缓冲区会暂缓数据,并且是有最大... -
Netty 粘包/半包原理与拆包实战 【源代码 新】
2018-11-11 10:15:03- 本实例是《Netty 粘包/半包原理与拆包实战》 一文的源代码工程。 大家好,我是作者尼恩。 在前面的文章中,完成了一个高性能的 Java 聊天程序,尼恩已经再一次的进行了通讯协议的选择。放弃了大家非常熟悉的json... -
JavaTCP粘包、拆包
2019-05-27 13:05:00import java.nio.ByteBuffer; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled;...import io.netty.channel.ChannelFuture;...import io.n... -
TCP传输中粘包/拆包问题
2020-09-16 09:44:28目录简介产生原因解决方法1. 改成短连接2. 封装成帧参考文章 简介 TCP 传输中,客户端发送数据,...如果客户端发送的包的大小比 TCP 的缓存容量小,并且 TCP 缓存可以存放多个包,那么客户端和服务端的一次通信就可能 -
netty中的TCP的黏包/拆包
2018-04-12 17:37:13TCP黏包/拆包原理 TCP是一个流的协议。一个完整的包可能被TCP拆分为多个包进行发送;也有可能把多个小的包封装成一个大的数据包一起发送 例如 客户端发送两个包给服务器,可能产生的情况: 1 a b单独发送 2 a b... -
TCP粘包拆包问题分析与解决
2020-01-13 16:20:21TCP 是一个面向字节流的协议,它是性质是流式的,所以它并没有分段。...因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优... -
netty中TCP的黏包/拆包解决方案
2018-04-12 14:41:45转自https://blog.csdn.net/u010853261/article/details/547988981.TCP黏包/拆包的原理TCP是一个“流”协议,所谓流就是没有界限的一串数据.TCP并不了解上层业务数据的具体定义,它只会根据TCP缓冲区的实际情况进行... -
go语言解决TCP黏包
2021-08-24 16:50:13简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。 2.接收端接收不及时造成的接收端粘包:TCP会把... -
TCP拆包和黏包的过程和解决
2019-01-25 17:27:01TCP拆包和黏包的过程和解决 粘包、拆包解决办法 通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个: 1、... -
记录Netty LengthFieldBasedFrameDecoder解决TCP粘包与拆包的用法详解
2022-02-25 14:35:39前言 LengthFieldBasedFrameDecoder类是Netty提供的用来解析带长度字段数据包的类,...In a stream-based transport such as TCP/IP, received data is stored into a socket receive buffer. Unfortunately, the buffe -
TCP粘包/拆包问题的分析与解决
2020-08-07 15:54:20文章目录1.TCP粘包/拆包问题1.1 TCP粘包/拆包问题说明1.2 TCP粘包/拆包发生的原因2....TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完