Netty通信中的粘包半包问题(一)

前言

我们在日常开发过程中,客户端和服务端的连接大多使用的是TCP协议,因为我们要保证数据的可靠传输,
当网络中出现丢包时要求,要求数据包的发送端重传给接收端。而TCP是一种面向连接的传输层协议,
当使用TCP进行传输时,客户端和服务端会各自维护两个缓冲区,它们分别是发送缓冲区、接收缓冲区,如图所示

我们在日常开发过程中,客户端和服务端的连接大多使用的是TCP协议,因为我们要保证数据的可靠传输,当网络中出现丢包时要求,要求数据包的发送端重传给接收端。而TCP是面向
在网络传输过程中,虽然对要发送的数据包大小没有要求,但是TCP又不可能一次性的把数据全部加载到
发送缓冲区中,这样会有可能撑爆TCP的发送缓冲区,比如说你要发送个1G的数据给服务端,TCP本身是不会
每次根据你要发送的数据包大小划定缓冲区大小的。但是它会收数据链路层的协议限制,
因为数据链路层是服务于传输层协议的,我们需要了解数据链路层当中传输的最大单元
在这里插入图片描述
MTU(Maximum Transmission Unit)最大传输单元,用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能能够接受的有效载荷大小,另外传输层还会进行一个MSS大小的TCP分段。MSS是最大报文长度的缩写,MSS是TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段,所以MSS并不是TCP报文的最大长度,而是MSS=TCP报文长度 - TCP首部长度,在以太网中MTU是1500个字节,其中TCP报头占20个字节,IP包头占20个字节,剩下的大小才是我们能在发送的有效数据包大小,也就是1500-20-20=1460个字节,
也就是说,如果没有进行过调整,当你发送的数据包小于1460个字节的时候,客户端在发送缓冲区里放下你的报文时还会有空余,但是不会立马发送,而是会等待缓冲区达到一个阈值时再发送,这个时候就会出现粘包现象,因为TCP在发送缓冲区会存在其他报文的数据,看着就像粘在一起一样。反过来讲,当你要发送的数据包大于这个发送缓冲区的大小时,将会留到下一次发送缓冲区填充,相当于一个报文被拆分了多次进行发送.这两种现象会导致服务端必须要等待接收到完整数据报文才可以进行处理,不然就得现将数据暂存到内存中,影响处理效率,会影响服务端整体的吞吐量,因为服务端需要维持报文的中间状态,维护报文之间的顺序关系,增加了服务端的工作量

下面我以代码的形式复现一下:

1.Client

package splicing.demo;import constant.Constant;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 java.net.InetSocketAddress;public class EchoCliStickyHalf {private final int port;private final String host;public EchoCliStickyHalf(int port, String host) {this.port = port;this.host = host;}public void start() throws InterruptedException {// 线程组EventLoopGroup group = new NioEventLoopGroup();try {// 客户端启动必备, 和服务器的不同点Bootstrap b = new Bootstrap();b.group(group)// 指定使用NIO的通信模式.channel(NioSocketChannel.class)// 指定服务器的IP地址和端口,和服务器的不同点.remoteAddress(new InetSocketAddress(host, port))// 和服务器的不同点.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new EchoCliStickyHalfHandler());}});// 异步连接到服务器,sync()会阻塞到完成,和服务器的不同点ChannelFuture f = b.connect().sync();// 阻塞当前进程,直到客户端的channel被关闭f.channel().closeFuture().sync();} finally {group.shutdownGracefully().sync();}}public static void main(String[] args) throws InterruptedException {new EchoCliStickyHalf(Constant.DEFAULT_PORT, Constant.DEFAULT_SERVER_IP).start();}
}
package splicing.demo;import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;import java.util.concurrent.atomic.AtomicInteger;public class EchoCliStickyHalfHandler extends SimpleChannelInboundHandler<ByteBuf> {private AtomicInteger counter = new AtomicInteger(0);/*** 客户端读取到网络数据后的处理*/@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {System.out.println("client Accept[" + byteBuf.toString(CharsetUtil.UTF_8) +"] and the counter is :" + counter.incrementAndGet()); }@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {String request = "Mark,Zhuge,Fox,Zhouyu,Loulan" + System.getProperty("line.separator");ByteBufAllocator alloc = ctx.alloc();ByteBuf msg = null;// 我们希望服务器接收到100个这样的报文for (int i = 0; i < 100; i++) {ByteBuf byteBuf = alloc.buffer();msg = alloc.buffer(request.length());msg.writeBytes(request.getBytes());ctx.writeAndFlush(msg);}
//        super.channelActive(ctx);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();
//        super.exceptionCaught(ctx, cause);}
}
import java.util.Date;/*** 常量*/
public class Constant {public static final Integer DEFAULT_PORT = 7777;public static final String DEFAULT_SERVER_IP= "127.0.0.1";// 根据输入信息拼接出一个应答信息public static String response(String msg) {return "Hello, " + msg + ", Now is" + new Date(System.currentTimeMillis()).toString(); }
}

2.Server

package splicing.demo;import constant.Constant;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.net.InetSocketAddress;public class EchoSvrStickyHalf {private static final Logger LOG = LoggerFactory.getLogger(EchoSvrStickyHalf.class);private final int port;public EchoSvrStickyHalf(int port) {this.port = port;}public static void main(String[] args) throws InterruptedException {EchoSvrStickyHalf echoSvrStickyHalf =new EchoSvrStickyHalf(Constant.DEFAULT_PORT);LOG.info("服务器即将启动");echoSvrStickyHalf.start();LOG.info("服务器关闭");}public void start() throws InterruptedException {// 线程组EventLoopGroup group = new NioEventLoopGroup();try {// 服务端启动必备ServerBootstrap b = new ServerBootstrap();b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast(new EchoSvrStickyHalfHandler());}});// 异步绑定到服务器,sync()会阻塞到完成ChannelFuture f = b.bind().sync();LOG.info("服务器启动完成。");// 阻塞当前线程,直到服务器的ServerChannel被关闭f.channel().closeFuture().sync();} finally {group.shutdownGracefully().sync();}}}
package splicing.demo;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;import java.util.concurrent.atomic.AtomicInteger;public class EchoSvrStickyHalfHandler extends ChannelInboundHandlerAdapter {private AtomicInteger counter = new AtomicInteger(0);@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf in = (ByteBuf)msg;String request = in.toString(CharsetUtil.UTF_8);System.out.println("Server Accept[" + request + "] and the counter is :" + counter.incrementAndGet() );String resp = "Hello, " +request + ". Welcome to Netty World" + System.getProperty("line.separator");ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
//        super.channelRead(ctx, msg);}/*** 发生异常后的处理* @param ctx* @param cause* @throws Exception*/@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        super.exceptionCaught(ctx, cause);cause.printStackTrace();ctx.close();}
}

3.分析

代码中明明发送了100个报文,结果服务端收到的报文个数,却是两个报文,中间的某一个报文还发生了半包,
下期我们再来讨论如何解决这种现象
在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/340502.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 基础【八】--数据类型-字典【2024.1.11】

1.定义 字典的内容在花括号 {} 内&#xff0c;键-值&#xff08;key-value&#xff09;之间用冒号 : 分隔&#xff0c;键值对之间用逗号 , 分隔&#xff0c;比如创建字典 &#xff0c;如下所示&#xff1a; d{name:小明,age:18}# 使用 dict 函数&#xff1a;强转 # 方式一&am…

034 - STM32学习笔记 - TIM定时器(三) - 高级定时器2

034 - STM32学习笔记 - TIM定时器&#xff08;三&#xff09; - 高级定时器2 哥们最近搞了个公众号&#xff0c;后面的文章会同步在公众号上发布&#xff0c;各位看官帮忙点点关注&#xff0c;后续一些其他方面的学习内容也会在公众号上发布&#xff0c;有兴趣的可以看看哟&…

SPI协议的4种模式

https://blog.csdn.net/buhuidage/article/details/121180622 MODE0 MODE3

人生重开模拟器(c语言)

前言&#xff1a; 人生重开模拟器是前段时间非常火的一个小游戏&#xff0c;接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏&#xff1a; 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 &#xff08;1&#xff09; 游戏开始的时…

浅谈WPF之Popup弹出层

在日常开发中&#xff0c;当点击某控件时&#xff0c;经常看到一些弹出框&#xff0c;停靠在某些页面元素的附近&#xff0c;但这些又不是真正的窗口&#xff0c;而是页面的一部分&#xff0c;那这种功能是如何实现的呢&#xff1f;今天就以一个简单的小例子&#xff0c;简述如…

FineBI实战项目一(14):订单销售总数分析开发

点击添加组件按钮&#xff0c;打开组件页面。 设置组件的属性&#xff0c;比如图标样式&#xff0c;指针值&#xff0c;目标值、颜色、大小、标签等。 点击下方切换到仪表盘。 点击仪表板中的左上方组件&#xff0c;添加组件到仪表盘。 编辑标题 第一个组件成功添加到仪表板。

生骨肉冻干推荐测评|希喂、VE、百利、PR等多款热门生骨肉冻干测评

随着养猫的观念逐渐科学化&#xff0c;越来越多的铲屎官开始关注猫咪主食的健康和营养问题。 冻干因其模拟猫咪原始捕猎猎物模型配比、低温加工的特点&#xff0c;被认为是最符合猫咪饮食天性的选择。 相比传统的膨化猫粮&#xff0c;生骨肉冻干中的淀粉和碳水化合物添加较少…

AIGC大模型必备知识——LLM ,你知道它是如何训练的吗?小白必读深度好文

Look&#xff01;&#x1f440;我们的大模型商业化落地产品&#x1f4d6;更多AI资讯请&#x1f449;&#x1f3fe;关注Free三天集训营助教在线为您火热答疑&#x1f469;&#x1f3fc;‍&#x1f3eb; 近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域经历了令人瞩目…

Rust类型之字符串

字符串 Rust 中的字符串类型是String。虽然字符串只是比字符多了一个“串”字&#xff0c;但是在Rust中这两者的存储方式完全不一样&#xff0c;字符串不是字符的数组&#xff0c;String内部存储的是Unicode字符串的UTF8编码&#xff0c;而char直接存的是Unicode Scalar Value…

纯前端 —— 200行JS代码、实现导出Excel、支持DIY样式,纵横合并

前期回顾 Vue3 TS Element-Plus 封装Tree组件 《亲测可用》_vue3ts 组件封装-CSDN博客https://blog.csdn.net/m0_57904695/article/details/131664157?spm1001.2014.3001.5501 目录 具体思路&#xff1a; 1. 准备HTML结构 2. 定义CSS样式 3. 初始化表格数据 4. 创建表…

QT第四天

头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QTime>//时间类 #include<QTimerEvent>//定时器事件类 #include<QtTextToSpeech> //语言播报类 #include<QPushButton> namespace Ui { class Widget; }clas…

NOIP2017 提高组 奶酪(DFS、BFS、并查集)一题三解

原文链接&#xff1a;NOIP真题第三讲&#xff1a;奶酪 题目来源&#xff1a;2017 年 NOIP 提高组 第一题 本题考察点&#xff1a;【DFS、BFS、并查集】 一、题目及链接 题目链接&#xff1a; https://www.luogu.com.cn/problem/P3958 题意&#xff1a;老鼠是否可以从下表面…