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>() {
                        @Override
                        protected 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);
    
    /**
     * 客户端读取到网络数据后的处理
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("client Accept[" + byteBuf.toString(CharsetUtil.UTF_8) +
                "] and the counter is :" + counter.incrementAndGet()); 
    }

    @Override
    public 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);
    }

    @Override
    public 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>() {
                        @Override
                        protected 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);


    @Override
    public 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
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
        ctx.close();
    }
}

3.分析

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv5改进有效涨点目录 | 包含卷积、主干、检测头、注意力机制、Neck上百种创新机制

⭐ YOLOv5改进有效系列目录 ⭐ 前言 Hello&#xff0c;各位读者们好 本专栏自开设一个月以来已经更新改进教程70余篇其中包含C2f、主干、检测头、注意力机制、Neck多种结构上创新&#xff0c;也有损失函数和一些细节点上的创新。同时本人一些讲解视频和包含我所有创新的YOLO…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷⑥

2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷6 目录 需要竞赛软件包环境以及备赛资源可私信博主&#xff01;&#xff01;&#xff01; 2023年全国职业院校技能大赛&#xff08;高职组&#xff09; “云计算应用”赛项赛卷6 模块一…

MySQL:DML数据操作语言(添加,删除,修改),DDL数据查询语言(条件查询,分组查询,排序查询,分页查询)

目录 1.DML&#xff08;数据操作语言&#xff09;1.添加数据2.修改数据3.删除数据 2.DQL(数据查询语言)1.DQL-语法2.基本查询3.条件查询(WHERE)1.语法&#xff1a;2.条件&#xff1a;3.案例: 4.聚合函数1.介绍2.常见聚合函数3.语法4.案例 5.分组查询&#xff08;GROUP BY&#…

Proteus仿真stm32f103r6输出PWM/正弦波

资料下载地址&#xff1a;Proteus仿真stm32f103r6输出PWM/正弦波 一、仿真图 Proteus仿真stm32f103r6输出PWM/正弦波 二、程序 #include "pbdata.h"u16 fre; void RCC_Configuration(void); void GPIO_Configuration(void); void TIM3_Configuration();void Dela…

SpringBoot项目docker镜像生成

1. 本文思路 拉取基础镜像基于镜像创建容器在容器中&#xff0c;安装所需依赖部署脚本提交容器&#xff0c;生成新的镜像编写Dockerfile&#xff0c;添加启动命令&#xff0c;生成最终镜像导出镜像 2. 操作步骤 2.1 基础环境 # 拉取镜像 docker pull centos:7.6.1810 # 运行…

C++ n皇后问题 || 深度优先搜索模版题

n− 皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n &#xff0c;请你输出所有的满足条件的棋子摆法。 输入格式 共一行&#xff0c;包含整数 n 。 …

迅为RK3568开发板Android11/12/Linux编译驱动到内核

在平时的驱动开发中&#xff0c;经常需要在内核中配置某种功能&#xff0c;为了方便大家开发和学习&#xff0c;本小 节讲解如何在内核中添加驱动。具体的讲解原理讲解请参考本手册的驱动教程。 Android11 源码如果想要修改内核&#xff0c;可以运行以下命令进行修改: cd ke…

机器学习_8、支持向量机

支持向量机解决鸢尾花数据集分类问题 # 导入鸢尾花数据集 from sklearn.datasets import load_iris import pandas as pd import numpy as npiris_data load_iris() Xiris_data.data yiris_data.target# 划分训练集与测试集 from sklearn.model_selection import train_test_…

【PaperReading- VLM】1. FERRET

CategoryContent论文题目FERRET: REFER AND GROUND ANYTHING ANYWHERE AT ANY GRANULARITY作者Haoxuan You (Columbia University), Haotian Zhang, Zhe Gan, Xianzhi Du, Bowen Zhang, Zirui Wang, Liangliang Cao (Apple AI/ML), Shih-Fu Chang (Columbia University), Yinfe…

【java】Error:java: 无效的源发行版: 12,只需三步

运行项目报错 “Error:java: 无效的源发行版: 12” 先在file下的project Structure 下选择 Project 将 Project language level 选择版本 8 对应的。 然后点击右下角的Apply OK 再在file下的project Structure 下选择Moudels ,将该项目的Sources改为8. 然后点击右下角的Apply…

Logstash应用介绍

1.Logstash介绍 1.1 前世今生 Logstash 项目诞生于 2009 年 8 月 2 日。其作者是世界著名的运维工程师乔丹西塞(JordanSissel)&#xff0c;乔丹西塞当时是著名虚拟主机托管商 DreamHost 的员工。 Logstash 动手很早&#xff0c;对比一下&#xff0c;scribed 诞生于 2008 年&am…

zabbix监控部署

目录 一、什么是zabbix&#xff1f; 二、zabbix监控原理 三、zabbix常见的五个程序 四、zabbix监控mysql实验 1、部署服务端 2、部署客户端 3、自定义监控内容 一、什么是zabbix&#xff1f; zabbix 是一个基于 Web 界面的提供分布式系统监视以及网络监视功能的企业级的…

Java应用实践课程设计——背诵单词助手

项目描述 该项目实现了一个简单的单词背诵小助手系统&#xff0c;包括管理员模块和用户模块。管理员可以对CET4表进行增加、删除、修改和查询等操作&#xff1b;用户可背诵CET4表中的单词&#xff0c;回顾已掌握和未掌握的单词。 数据库设计——words.sql

JVM加载class文件的原理机制

1、JVM 简介 JVM 是我们Javaer 的最基本功底了&#xff0c;刚开始学Java 的时候&#xff0c;一般都是从“Hello World ”开始的&#xff0c;然后会写个复杂点class &#xff0c;然后再找一些开源框架&#xff0c;比如Spring &#xff0c;Hibernate 等等&#xff0c;再然后就开发…

物联网通讯协议NB-lot和LoRa差异分析

像把大象装冰箱一样&#xff0c;物联网&#xff0c;万物互联也是要分步骤的。 一、感知层(信息获取层)&#xff0c;即利用各种传感器等设备随时随地获取物体的信息; 二、网络层(信息传输层)&#xff0c;通过各种电信网络与互联网的融合&#xff0c;将物体的信息实时准确地传递…

大数据 - Doris系列《三》- 数据表设计之表的基本概念

目录 &#x1f436;3.1 字段类型 &#x1f436;3.2 表的基本概念 3.2.1 Row & Column 3.2.2 分区与分桶 &#x1f959;3.2.2.1 Partition 1. Range 分区 2. List 分区 进阶&#xff1a;复合分区与单分区的选择 3.2.3 PROPERTIES &#x1f959;3.2.3.1 分片副本数 &#x1f…

使用MySQL的过程中,有没有遇到过count()比较慢的情况?

count(*)的实现方式 MyISAM引擎把一个表的总行数存在了磁盘上&#xff0c;执行count(*)的时候直接返回这个数&#xff0c;效率很高&#xff1b; InnoDB引擎执行count(*)的时候&#xff0c;需要把数据一行一行地从引擎里面读出来&#xff0c;然后累积计数。 上述说明是在没有…

【MATLAB源码-第107期】基于matlab的OFDM系统在瑞利信道下功率分配仿真,使用注水算法。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在瑞利衰落信道下&#xff0c;OFDM&#xff08;正交频分复用&#xff09;系统的功率分配可以通过“注水算法”&#xff08;water-filling algorithm&#xff09;的方法来优化。这种算法的目的是在不同的子载波上分配不同的功…

蓝桥杯省赛无忧 STL 课件11 pair

01 pair的定义和结构 在C中&#xff0c;pair是一个模板类&#xff0c;用于表示一对值的组合&#xff0c;它位于头文件中。 pair类的定义如下: template<class T1,class T2>struct pair{T1 first;//第一个值T2 second;//第二个值// 构造函数pair();pair(const T1& X…

深度解析Cron表达式:精确控制任务调度的艺术

深度解析Cron表达式&#xff1a;精确控制任务调度的艺术 希望我们都可以满怀期待的路过每一个转角 去遇见 那个属于自己故事的开始 去追寻那个最真实的自己 去放下 去拿起 安然&#xff0c;自得&#xff0c;不受世俗牵绊… 导言 在计算机科学领域&#xff0c;任务调度是一项关…