Go语言网络编程:TCP粘包问题——Go实现封包拆包

news2024/10/6 6:53:01

一:TCP粘包介绍

1.1 TCP介绍

在这里插入图片描述

如上图,TCP具有面向连接、可靠、基于字节流三大特点。

字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。纯裸TCP收发的这些 01 串之间是没有任何边界的,你根本不知道到哪个地方才算一条完整消息。
在这里插入图片描述

正因为这个没有任何边界的特点,所以当我们选择使用TCP发送"夏洛"和"特烦恼"的时候,接收端收到的就是"夏洛特烦恼",这时候接收端没发区分你是想要表达"夏洛"+“特烦恼"还是"夏洛特”+“烦恼”。
在这里插入图片描述

1.2 粘包/拆包介绍

在这里插入图片描述

根据计算机网络的TCP/IP协议,粘包和拆包问题在数据链路层、网络层以及传输层都有可能发生。

  • 在数据链路层,数据被封装成帧进行传输。帧是数据链路层的传输单位,含了数据和帧头部信息。在数据链路层中,粘包和拆包问题可能发生在帧的传输过程中,例如在以太网中多个数据帧可能会被合并成一个较大的帧进行传输,导致粘包问题;或者一个数据帧被拆分成多个较小的帧进行传输,导致拆包问题。

  • 在网络层,数据被装成IP数据报进行传输。IP数据报包含了IP头部和数据部分。在网络层中,粘包和拆包问题可能发生在IP数据报的传输过程中例如在路由器中,多个IP数据报可能会被合并成一个较大的IP数据报进行传输,导致粘包问题;或一个数据报被拆分成多个较小的IP数据报进行传输,导致问题。

  • 在传输层,数据被封装成TCP报文段进行传输。TCP报文段包含了TCP头部和数据部分在传输层中,粘包和拆包问题主要发生在TCP报文段的传输过程中。由于TCP是面的可靠传输议,它将应用层的数据流一系的数据段,然后将这数据段封装成TCP报文段进行传输。发送方可能会将多个应用层的数据段合并成一个TCP报段进行传输,导致粘包问题;而接收方可能一次性接收到多个应用层的数据段,导致拆问题。

因此,粘包和拆包问题在数据链路层、网络层以及传输层都有可能发生,不过数据链路层,网络层的粘包和拆包问题都由协议进行处理了,日常应用开发都是应用层对接传输层,因此在实际开发中,面临的都是TCP粘包拆包问题。

而应用层另一个协议UDPUDP是一个无连接协议,客户端和服务端之间没有建立持久的连接。通信中,客户端只负责发送数据,而不需要关心服务端是否正常接收或处理数据。

由于UDP不提供连接状态的维护和不保证数据传输的可靠性,因此UDP通常用于实时较高、对数据准确性要求相对较低的场景,如直播行业、音视频传输等。在这些领域,数据的实时性比传输的可靠性更为重要,而丢失部些误差对于用户体验的影响相对较小。
与TCP不同,UDP没有内置的分包和粘包处理机制。由于UDP以独立的数据报形式传输数据,每个UDP数据包都是一个完整的单元,不存在粘包问题。每个UDP数据包都独立发送、接收和处理,因此不会发生TCP中常见的粘包现象。

尽管UDP不存在粘包问题,但应用程序仍需自行处理可能存在的数据分片和重组,以确保数据的完整性和正确性,特别是在进行大型数据包的传输时。此外,由于UDP的不可靠性,应用程序也需要考虑丢包、重复和顺序错乱等问题,对数据进行适当的处理和恢复机制,以满足实时性要求。

在这里插入图片描述

TCP粘包并不是TCP协议造成的问题,因为TCP协议本就规定字节流式传输(算法决定:利用缓冲区,有拥塞控制,大小包合并),它不含消息、数据包等概念,需要应用层自己设计消息边界。

1.3 何时出现TCP粘包

TCP粘包问题可能发生在以下情况下:

  1. 发送方连续发送多个小数据包:如果发送方快速发送多个较小的数据包,TCP协议在发送端可能会将它们组合成一个较大的数据包进行传输,导致接收端收到的数据粘在一起。
  2. 接收方读取数据不及时:如果接收方的读取速度较慢,无法及时消耗完接收缓冲区中的数据,TCP协议可能会将多个数据包累积在接收缓冲区中,导致粘包现象。
  3. TCP拥塞控制机制:当TCP因为网络拥塞而启动拥塞控制机制时,发送方会减少发送窗口的大小,从而导致数据包发送频率减缓。这可能导致发送方将多个数据包打包在一起,形成粘包。
  4. 网络延迟或抖动:网络延迟或抖动可能导致数据包的到达时间不确定,TCP协议在接收端可能会将到达的数据包缓存在接收缓冲区中,并等待后续数据包的到达,从而引发粘包问题。

需要注意的是,即使是连续发送大量数据,在正常的网络环境和合理的数据处理方式下,TCP通常可以保持数据包的完整性和顺序,不会出现粘包。当发送方过于迅速发送数据而使接收方无法跟上或处理不当,此时可能发生粘包问题。注意,TCP粘包问题并非TCP协议设计上的缺陷,而是由于TCP协议的数据传输特性和网络条件的影响所致。解决TCP粘包问题通常需要应用层协议设计或使用特定的数据分隔方式来确的数据传输和解析。

TCP粘包问题在同一主机上的不同端口之间通常不会发生。当在同一主机上的不同端口之间建立TCP连接时,数据传输是在主机内部进行的,而不经过网络。

  1. 内部数据传输:当两个应用程序通过不同端口在同一主机上建立TCP连接时,数据传输并不离开主机。数据从发送方应用程序通过使用套接字(socket)发送到操作系统内核的TCP层,然后由TCP层直接传递给接收方应用程序的套接字。这个过程在内部完成,不涉及实际的网络传输和网络协议的影响。
  2. 操作系统缓冲:发送方应用程序将数据写入操作系统内核的发送缓冲区。接收方应用程序从操作系统内核的接收缓冲区读取数据。这些缓冲区被设计成大小合适且独立的,以确保数据的完整性和正确性。因此,在同一主机上的不同端口之间的TCP连接中,数据在这些缓冲区中被正确地划分、存储和传输。
  3. 数据处理:接收方应用程序可以根据自己的需求和协议规范,对接收到的数据进行解析和处理。由于数据传输在主机内,没有网络延迟、传输错误或网络拥塞等因素的影响。这意味着接收方应用程序可以按照预期的数据格式进行解析和处理,而不需要考虑TCP粘包问题。

虽然同一主机上不同端口之间的TCP连接不会导致TCP粘包问题,但仍需要确保接收方应用程序能够正确解析处理预期的数据格式,以免发生解析错误。这需要应用程序在设计中考虑到数据格式的规范,并采取适当的解析方法来确保数据的正确解释和使用。

1.4 Go语言TCP粘包示例

在这里插入图片描述
客户端如上图所示,服务端通过bufio.NewReader(conn)进行读取,正常情况如下:
在这里插入图片描述

但如果将time.Sleep(time.Second)注释掉,可能会出现下图的结果:
在这里插入图片描述

主要原因是因为我们是应用层软件,是跑在操作系统之上的软件,当我们向服务器发送一个数据时,是调用操作系统的相关接口发送的,操作系统再经过各种复杂的操作,发送到对方机器。但是操作系统有一个发送数据缓冲区,默认情况如果缓冲区是有大小的,如果缓冲区没满,是不会发送数据的。
这就导致了TCP粘包问题的出现。当我们连续发送多个数据包时,如果这些数据包在发送过程中没有填满操作系统的发送缓冲区,它们会被缓存在缓冲区中,直到缓冲区满或者满足一定条件才会一次性发送到对方机器。因此,接收方可能会一次性接收到多个数据包的内容,从而导致粘包问题。
然而,为什么使用sleep(1s)可以解决这个呢这是因为发送缓冲区不仅仅被我们的应用程序使用,还可能被其他程序使用。当我们使用sleep(1s)时,等待1秒的时间足够让其他程序将缓冲区填满,然后各自发送自的数据。这样,即使我们的应用程序连续发送多个小数据包,由于缓冲区已经被其他程序填满,我们的数据也及时发送出发。

二、解决TCP粘包

2.1 应用层常用解决TCP粘包协议

应用层常用的几种解决TCP粘包问题的协议和技术如下:

  1. 定长包协议(Fixed-Length Protocol):发送方将每个数据包固定长度,不足部分用补齐字符填充。接收方按照固定长度截取数据,确保每个数据包长度一致,从而避免粘包问题。
  2. 换行符协议(Delimiter-based Protocol):发送方在每个数据包尾部添加特定的换行符(如’\n’),接收方通过识别换行符来划分不同的数据包。
  3. 长度字段协议(Length-Field Protocol):发送方在每个数据包前部添加一个表示数据包长度的字段,接收方根据该长度字段来解析数据包边界,确保正确分离每个数据包。长度字段可以是固定长度,也可以采用变长编码方式。
  4. 自定义协议:应用层可以设计自定义的协议,通过在数据包中使用特定的标识符、头部信息或其他约定进行数据分隔和解析协议和技术可以在应用层上增加更高级的数据隔机制,使得应用程序能够正确解析和处理数据,避免粘包问题。选择适合具体应用场景和需求的协议,可以提高数据传输的可靠性和正确性。

这些协议和技术需要发送方和接收方之间达成一致并正确实现,以确保数据的准确分隔和解析。双方都应按照协议规范进行数据的发送和接收处理,以免仍然出现数据解析错误的情况。

2.2 长度字段协议解决TCP粘包

package stick

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
)

// Pack 将消息编码,输入消息实体,返回消息长度与消息实体组成的字节流,客户端发送消息时调用进行封包
func Pack(message string) ([]byte, error) {
	length := int32(len(message)) // 获取消息长度
	var pkg = new(bytes.Buffer)   // 创建字节缓冲区

	// 写入消息头部,使用小端序,将长度信息写入字节缓冲区,int32占用4个字节
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		fmt.Println("写入消息头失败", err)
		return nil, err
	}

	// 写入消息实体,将消息实体信息写入字节缓冲区
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		fmt.Println("写入消息实体失败", err)
		return nil, err
	}

	return pkg.Bytes(), nil // 返回编码结果
}

// UnPack 解码消息,输入二进制字节流,前4个字节为消息长度,后面为消息实体,返回消息实体,服务端接收消息时调用进行解包
func UnPack(reader *bufio.Reader) (string, error) {
	// 读取信息长度,前4个字节
	lengthByte, _ := reader.Peek(4)
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}

	// 检查缓冲区中可读的字节数是否足够容纳该消息
	if int32(reader.Buffered()) < length+4 {
		return "", err
	}

	// 读取消息真正的内容
	pack := make([]byte, int(4+length))
	_, err = reader.Read(pack)
	if err != nil {
		return "", err
	}

	return string(pack[4:]), nil // 返回解码结果
}

以上是进行封包与拆包的go函数,在客户端发送数据时,使用Pack()进行封包;在服务端读取数据时,使用UnPack()进行拆包。

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

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

相关文章

对弈人工智能!myCobot 280开源六轴机械臂Connect 4 四子棋对弈下篇

前言 在上篇文章中&#xff0c;我们探讨了如何创造一个能够进行Connect4的对弈大脑。简单的介绍了几种对弈算法&#xff0c;例如极小化极大算法&#xff0c;Alpha-Beta剪枝算法等&#xff0c;最关键的是目前最流行的神经网络算法和深度学习。神经网络算法&#xff0c;让计算机…

Dubbo zookeeper

1、RPC全称为remote procedure call&#xff0c;即远程过程调用。Dubbo作为一个RPC框架,其最核心的功能就是要实现跨网络的远程调用 2、Dubbo提供了三大核心能力&#xff1a;面向接口的远程方法调用&#xff0c;智能容错和负载均衡&#xff0c;以及服务自动注册和发现。 3、 Du…

Google SEO内容指南:您实现最大自然流量的路线图

欢迎来到令人兴奋的SEO内容世界&#xff01; SEO就像拥有一个秘方&#xff0c;结合了创造力、策略和技术诀窍的正确成分&#xff0c;使您的内容在广阔的数字环境中大放异彩。 但最好的是 – SEO内容并不是要牺牲您独特的声调或损害您的创造力。相反&#xff0c;它是关于了解搜…

从0开始,手写MySQL数据管理器DM

说在前面 从0开始&#xff0c;手写一个MySQL的学习价值在于&#xff1a; 可以深入地理解MySQL的内部机制和原理&#xff0c;MySQL可谓是面试的绝对重点和难点&#xff0c; 尼恩曾经指导过的一个7年经验小伙&#xff0c;凭借精通MySQL 搞定月薪40K。 从而更好地掌握MySQL的使…

六、Eureka服务发现(源码分析)

1 什么是服务发现 根据服务名称发现服务的实例过程客户端会在本地缓存服务端的列表拉取列表是有间隔周期的 &#xff08;导致服务上线 客户端不能第一时间感知到 &#xff08;可以容忍&#xff09;&#xff09;其实每次做服务发现 都是从本地的列表来进行的 2 测试服务发现 …

哆啦A梦和小猪佩奇(Python实现)

目录 1 哆啦A梦 2 小猪佩奇 3 Python代码实现&#xff08;哆啦A梦&#xff09; ​ 4 Python代码实现&#xff08;小猪佩奇 &#xff09; 1 哆啦A梦 “只要把愿望系在竹竿上请求月亮女神&#xff0c;心愿便能达成”。我超喜欢这句话。 哆啦A梦的创造要追溯到1969年的某个…

【PHP语言-PDO接口】PDO接口执行脚本操作数据库

目录 前言&#xff1a; 一、 PDO简介 二、 PDO对象方法 前言&#xff1a; PDO&#xff1a;数据库抽象层 简介&#xff1a;PDO扩展为PHP访问数据库定义了一个轻量级的、一致性的接口&#xff0c;PDO解决了数据库连接不统一的问题。 一、 PDO简介 1、PDO简介 &#xff08;1…

iview 文档中的三个提示彩蛋

第一个彩蛋 在iview的Collapse 折叠面板最底下&#xff0c;简洁模式的第二个&#xff0c;双击数字 19840124 是一个日期&#xff0c;也就是 1984 年 1 月 24 日&#xff0c;这一天&#xff0c;苹果发布了麦金塔电脑&#xff08;Macintosh&#xff09;&#xff0c;对于苹果来说…

当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用

第一章 理论基础与研究热点分析 1. 生态系统服务与生态系统服务价值介绍 ​ 2. 生态系统服务价值研究方法 3. 生态系统服务价值研究热点 Citespace文献可视化分析 VOSviewer文献可视化分析 第二章 空间数据来源及预处理 1. 空间数据简介 2. ArcGIS Pro数据采集与分析 数…

【Python】matplotlib.pyplot 详解与使用(内有大量例子)

0. 写在前面 本文是根据 matplotlib 3.7.1 版本撰写的&#xff0c;若出现有文章与实际有出入的情况请查看版本是否一致。 我们使用 matplotlib.pyplot 需要使用以下的语句来导入它 import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np1. 官方文档详…

chrome开发调试小技巧—Replay XHR(重新请求)

一、需求 想要验证一个ajax请求&#xff0c;需要每次都需要在页面点几次才会触发或者刷新页面&#xff0c;着急调试看效果时&#xff0c;可以通过chrome的Replay XHR功能直接同参数重新请求ajax 二、实现 chrome调试工具network下找到要重新发起的ajax请求&#xff0c;右键找…

Vue核心语法

Vue核心语法 vue下载 我们以前都是用的框架来搭建的&#xff0c;省去了很多内容&#xff0c;今天我们从原始的方式来使用vue&#xff0c;下面是下载地址 响应式 未使用响应式 <!DOCTYPE html> <html lang"en"><head><meta charset"U…

基于J-Link RTT Viewer输出日志(适用于JLink DAPLink STLink)

前言 通过RTT输出日志&#xff0c;可以不占额外的引脚和外设&#xff0c;速度非常快&#xff0c;几乎不影响程序的实时性。 参考官方介绍文档 安装J-Link驱动 官网下载地址&#xff0c;本文选择的是7.60版本&#xff0c;如果官网下载太慢&#xff0c;可以点击在CSDN下载 …

linux入门进程概念中(僵尸进程,孤儿进程,进程优先级,并行和并发,环境变量)

目录 一、进程状态 1.看看Linux Kernel怎么说 1.1阻塞 2.进程状态查看 3.僵尸进程 3.1模拟僵尸进程的实验 3.2僵尸进程的危害 4.孤儿进程 4.1模拟孤儿进程实验 二、进程优先级 三、环境变量 3.1常见环境变量 3.2查看环境变量的方法&#xff1a; 3.3 加入环境变量 …

性能测试基础知识及性能指标

目录 1.1、性能概述&#xff1a; 1.2 、测试目标 1.3 、性能测试方法 2 .1 、需求分析 2.2 、测试对象 2.3 、拆分对象 2.4 、指标分析 3.1 、用例设计 4.1、性能监控关键指标 结尾 &#x1f381;更多干货 前言&#xff1a;最近公司接了个项目&#xff0c;领导开会突…

【python】面向对象语言的特性

面向对象语言的特性 封装继承继承定义继承下的方法重写 类型注解变量类型注解函数(方法)类型注解Union 联合类型注解 多态定义抽象类 面向对象语言的三大特性&#xff1a;封装、继承、多态 本文主要来介绍这三个特性 封装 封装&#xff1a;指的是将对象的状态信息隐藏在对象内…

nodejs(express)+TypeScripts环境

初始化项目&#xff1a; npm init -y 安装包&#xff1a; npm i types/express //安装type类型的express如果不加types就是安装js文件&#xff0c;虽然对项目的运行没啥问题但是会没有提示npm i typescriptnpm i types/mysql安装完成后就开始配置了&#xff1a; 在项目的根…

VCL组件DevExpress VCL图表控件中文指南 - 如何实现值标签自定义?

DevExpress VCL拥有230个VCL界面控件、40个自定义设计的VCL应用主题&#xff0c;它能帮助您创建优异的用户体验&#xff0c;提供高影响力的业务解决方案&#xff0c;并利用您现有的VCL技能为未来构建下一代应用程序。 在刚更新的DevExpress VCL v23.1组件库中&#xff0c;包含…

英伟达GeForce Game Ready 536.40 WHQL驱动程序快速获取

6月底&#xff0c;随着英伟达RTX 4060显卡发布&#xff0c;英伟达随即发布了Game Ready 536.40 WHQL显卡驱动&#xff0c;为RTX 4060显卡提供支持。除了RTX 4060 显卡驱动支持外&#xff0c;还有其他方面的优化&#xff0c;驱动人生为大家带来536.40 WHQL驱动支持一览。 NVIDI…

I2S协议

器件连接图&#xff1a; I2S&#xff08;Inter-IC Sound&#xff09;是一种数字音频传输协议&#xff0c;用于在集成电路之间传输音频数据。它通常用于连接音频设备&#xff0c;如数字音频处理器、音频编解码器、音频DAC和ADC等。 I2S协议通过三根线进行数据传输&#xff1a;时…