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.mzph.cn/news/615879.shtml

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

相关文章

vue前端开发自学,插槽练习第二次,name属性的使用

vue前端开发自学,插槽练习第二次,name属性的使用!可以使用name属性&#xff0c;来自定义一个名字&#xff0c;这样&#xff0c;就可以在一个组件内同时出现多个插槽的内容了。在子组件内接收的时候&#xff0c;很简答&#xff0c;只需要在slot标签里面加上name“mz”&#xff1…

业务向——基于多多进宝平台的CPS

业务向——基于多多进宝平台的CPS 导读小试牛刀商品活动推广商品详情获取频道推广订单获取及和用户绑定小结 导读 多多进宝是拼多多的开放平台&#xff0c;为广大商家和推广者提供了一个机会&#xff0c;通过推广拼多多的商品来实现收益。多多进宝的CPS&#xff08;按效果付费…

[JAVA数据结构] 认识 Iterable、Collection、List 的常见方法签名以及含义

目录 (一)Iterable 1. 介绍 2. 常见方法 (二)Collection 1. 介绍 2. 常见方法 (三) List 1. 介绍 2. 常见方法 总结 (一) Iterable 1. 介绍 Iterable接口是Java中的一个接口&#xff0c;它是集合框架中的根接口之一。Iterable接口表示实现了迭代功能&#xff0c;即可以通过迭…

鸿蒙HarmonyOS兼容JS的类Web开发

鸿蒙HarmonyOS兼容JS的类Web开发 文章目录 鸿蒙HarmonyOS兼容JS的类Web开发文件组织目录结构文件访问规则媒体文件格式 js标签配置pageswindow示例 app.js应用生命周期应用对象6 HML语法参考页面结构数据绑定普通事件绑定冒泡事件绑定5捕获事件绑定5列表渲染条件渲染逻辑控制块…

HTML5 画布绘制海报

需求&#xff1a; 1、根据用户填写的联系人信息&#xff1a;姓名、手机号及微信二维码&#xff0c;生成海报&#xff0c;并下载保存到本地&#xff1b; 2、可多个海报切换供用户选择 实现&#xff1a;使用html5实现&#xff0c;为方便用户&#xff0c;做的手机网站的样式&am…

图形化编程:下一代的创新教育工具

在科技日新月异的今天&#xff0c;编程已经成为了一项必备的技能。然而&#xff0c;传统的编程语言对于许多人来说仍然是一项挑战&#xff0c;尤其是对于年轻的学习者。为了解决这个问题&#xff0c;图形化编程应运而生&#xff0c;它以其直观、易理解和易操作的特点&#xff0…

【MySQL】本地创建MySQL数据库详解

文章目录 下载MySQL安装重置密码本地连接 下载MySQL 下载网址&#xff1a;https://dev.mysql.com/downloads/mysql/ 安装 将下载好的压缩包解压到D盘。 在解压好的文件夹中创建my.ini文件。 将以下代码复制粘贴到创建好的my.ini文件中。注意修改文件路径。 [mysqld] #设置…

PHP留言板实现

完整教程PHP留言板 登陆界面 一个初学者的留言板&#xff08;登录和注册&#xff09;_php留言板登录注册-CSDN博客 留言板功能介绍 百度网盘 请输入提取码 进入百度网盘后&#xff0c;输入提取码&#xff1a;knxt&#xff0c;即可下载项目素材和游客访问页面的模板文件。 &…

讯飞医疗“单飞”上市,科大讯飞的“AI算盘”还灵吗?

近年来&#xff0c;人工智能的各种应用呈现出爆炸式的增长态势&#xff0c;“AI”模式在众多领域中不断展现出其多元化的潜力。就在最近&#xff0c;国内的人工智能技术领军企业也迈出了重要的一步。一直被誉为国内“AI之光”的科大讯飞在1月9日晚间发布了一份公告&#xff0c;…

jmeter--常用插件及服务器监控(14)

一.jmeter插件管理器 下载jmeter插件管理器&#xff1a;plugins-manager.jar 下载plugins-manager.jar并将其放入lib/ext目录&#xff0c;然后重启JMeter。 插件管理界面 打开选项->Plugins Manager&#xff08;界面见下图&#xff09;&#xff0c;“Installed Plugns”…

Windows pip install -r requirement.txt 太慢

解决方案一&#xff1a; 1、在虚拟环境中切换下载的源&#xff1a; pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple 2、当出现有pip.txt文件写入时&#xff0c;再执行pip安装 pip install -r requirement.txt 解决方案二&#xff1a; 1、在…

使用Openssl生成Https免费证书以及Nginx配置

1 证书和私钥的生成 1.创建服务器证书密钥文件 server.key&#xff1a; openssl genrsa -des3 -out server.key 2048 输入密码&#xff0c;确认密码&#xff0c;自己随便定义&#xff0c;但是要记住&#xff0c;后面会用到。 2.创建服务器证书的申请文件 server.csr openssl r…

jmeter接口自动化测试如何部署jenkins

首先&#xff0c;保证本地安装并部署了jenkins&#xff0c;jmeter&#xff0c;xslproc 我搭建的自动化测试框架是jmeterjenkinsxslproc ---注意&#xff1a;原理是&#xff0c;jmeter自生成的报告jtl文件&#xff0c;通过xslproc工具&#xff0c;再结合jmeter自带的模板修改&…

React Native 环境安装

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases. 搭建开发环境 React Native 中文网 Homebrew&#xff08;包管理器&#xff09; → rvm&#xff08;ruby版本管理&#xff09; → ruby → cocoapods 安装 Homebrew Homebrew /bin/ba…

网安入门14-文件包含(file:// )

​ 什么是文件包含漏洞——来自ChatGPT4 文件包含漏洞是指应用程序在加载文件时&#xff0c;允许用户控制被加载文件的名称&#xff0c;从而导致恶意代码的执行或敏感信息的泄露。文件包含漏洞主要分为两种&#xff1a; 本地文件包含漏洞&#xff08;LFI&#xff09; &#…

jmeter和meterSphere如何使用第三方jar包

工具引用jar包语言都是beanshell 问题起因&#xff1a;metersphere 接口自动化实现过程中&#xff0c;如何实现字符串加密且加密方法依赖第三方库&#xff1b; 使用语言&#xff1a;beanshell脚本语言&#xff0c;java语言 使用工具&#xff1a;idea jmeter metersphere 1.首…

基于K-Means聚类算法与随机森林模型评估信贷风险客户【500010101】

项目背景 本数据集来自一家德国银行&#xff0c;由加州大学霍夫曼教授于 2016 年收集整理&#xff0c;每条记录代表了一个接受银行信贷的客户&#xff0c;这也就说明了&#xff0c;这些客户都是通过了贷款申请的&#xff0c;通过可视化分析对数据进行初步探索&#xff0c;并利…

蓝桥杯基础知识3 memset()

蓝桥杯基础知识3 memset() #include <bits/stdc.h> using namespace std;int main(){int a[5]; //随机数for(int i 0;i < 5; i)cout << a[i] << \n;cout << \n;memset(a, 0, sizeof a); //0for(int i 0;i < 5; i)cout << a[i] << …

【Java 干货教程】Java实现分页的几种方式详解

一、前言 无论是自我学习中&#xff0c;还是在工作中&#xff0c;固然会遇到与前端搭配实现分页的功能&#xff0c;发现有几种方式&#xff0c;特此记录一下。 二、实现方式 2.1、分页功能直接交给前端实现 这种情况也是有的&#xff0c;(根据业务场景且仅仅只能用于数据量…

多模态大模型Clip

一、经典分类模型的问题: 类别固定当前的模型只能胜任一个任务&#xff0c;迁移到新任务上非常困难类别互斥当前的CV数据集标注劳动密集&#xff0c;成本较高&#xff0c;当前模型泛化能力较差 负样本的组成(Batchsize有N个文本-图像对) Batchsize太小&#xff0c;负样本太少…