tcp黏包问题

2023-05-16

服务端代码如下:

package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
)

func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	var buf [1024]byte
	for {
		n, err := reader.Read(buf[:])
		if err == io.EOF {
			break
		}
		if err != nil {
			fmt.Println("read from client failed, err:", err)
			break
		}
		recvStr := string(buf[:n])
		fmt.Println("收到client发来的数据:", recvStr)
	}
}

func main() {

	listen, err := net.Listen("tcp", "127.0.0.1:30000")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn)
	}
}

客户端代码如下:

package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:30000")
	if err != nil {
		fmt.Println("dial failed, err", err)
		return
	}
	defer conn.Close()
	// 客户端分20次发送的数据,在服务端并没有成功的输出20次,而是多条数据“粘”到了一起。
	/*
	主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

	“粘包”可发生在发送端也可发生在接收端:
	 1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。
	简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,
	而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
	2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。
	当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

	出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

	封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。
	包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量
	就能正确的拆分出一个完整的数据包。

	我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。
	 */
	for i := 0; i < 20; i++ {
		msg := `Hello, Hello. How are you?`
		conn.Write([]byte(msg))
	}
}

启动服务端和客户端,服务端打印结果:

收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?

客户端分20次发送的数据,在服务端并没有成功的输出20次,而是多条数据“粘”到了一起。

为什么会出现粘包
主要原因就是tcp数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发。

“粘包”可发生在发送端也可发生在接收端:

  1. 由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
  2. 接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

解决办法

出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度

package proto

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

// Encode 将消息编码
func Encode(message string) ([]byte, error) {
	// 读取消息的长度,转换成int32类型(占4个字节)
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// 写入消息头
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入消息实体
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

// Decode 解码消息
func Decode(reader *bufio.Reader) (string, error) {
	// 读取消息的长度
	lengthByte, _ := reader.Peek(4) // 读取前4个字节的数据
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	err := binary.Read(lengthBuff, binary.LittleEndian, &length)
	if err != nil {
		return "", err
	}
	// Buffered返回缓冲中现有的可读取的字节数。
	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
}

服务端:

package main

import (
	"bufio"
	"fmt"
	"io"
	"mytest/testnet/testtcp3/proto"
	"net"
)
func process(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	for {
		msg, err := proto.Decode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("decode msg failed, err:", err)
			return
		}
		fmt.Println("收到client发来的数据:", msg)
	}
}

func main() {

	listen, err := net.Listen("tcp", "127.0.0.1:30000")
	if err != nil {
		fmt.Println("listen failed, err:", err)
		return
	}
	defer listen.Close()
	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("accept failed, err:", err)
			continue
		}
		go process(conn)
	}
}

客户端:

package main

import (
	"fmt"
	"mytest/testnet/testtcp3/proto"
	"net"
)
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:30000")
	if err != nil {
		fmt.Println("dial failed, err", err)
		return
	}
	defer conn.Close()
	for i := 0; i < 20; i++ {
		msg := `Hello, Hello. How are you?`
		data, err := proto.Encode(msg)
		if err != nil {
			fmt.Println("encode msg failed, err:", err)
			return
		}
		conn.Write(data)
	}
}

分别启动服务端和客户端,可以看到包被合理的拆分出来了,不再出现粘包现象:

收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?
收到client发来的数据: Hello, Hello. How are you?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tcp黏包问题 的相关文章

  • C# 服务器和 Java 客户端:TCP 套接字通信问题

    我用 C 编写了一个服务器程序TCPListner和一个使用套接字的 Java 客户端程序 但我无法将复杂的对象从 Java 客户端发送到 C 服务器 当我通过将字符串转换为字节数组从 Java 客户端发送到 C 服务器时 当转换回字符串时
  • 如何通过 GPRS 向 GPS 追踪器(TK103、GT02、GT06、TK102 等)发送命令

    这已经被问过这里https stackoverflow com questions 25460743 gps tracker tk103 how to send message through server https stackoverf
  • TCPServer 具有同时全双工通信

    我正在尝试编写一个 C 服务器 客户端 它将同时通过 TCP 相互发送字节数组 我正在努力思考如何实现这一目标 我见过的所有示例都等待消息 然后发送响应 我需要同时进行沟通 我是否需要为服务器和客户端上的传入和传出创建 2 个单独的 TCP
  • TCP 服务器的 GCP 计算引擎防火墙规则

    我创建了一个具有静态外部 IP 地址的 GCP 计算引擎实例 机器类型 n1 standard 2 2 个 vCPU 7 5 GB 内存 操作系统是Linux Debian 我的目的是在机器上创建一个普通的 Node js TCP 服务器
  • 为什么我无法发送这个IP数据包?

    我正在尝试使用 C 发送 IP 数据包 destAddress IPAddress Parse 192 168 0 198 destPort 80 Create a raw socket to send this packet rawSoc
  • tcp_max_syn_backlog 和 somaxconn 有什么区别?

    我一直在阅读一些关于 Linux 上的 TCP 实现的文章 我很困惑 它们之间有什么区别net ipv4 tcp max syn backlog and net core somaxconn和backlog作为参数传递给listen 系统调
  • 什么是消息边界?

    什么是 消息边界 在以下情况下 TCP 和 UDP 之间的区别之一是 UDP 保留消息 边界 我理解之间的区别TCP and UDP 但我不确定的定义 消息边界 由于 UDP 在每个单独的数据包中包含目的地和端口信息 因此是否可以为消息提供
  • 在 C# 中通过 TCP 发送 C 结构体

    我正在编写一个程序 通过 TCP 与一台设备的管理界面进行交互 问题是 设备的文档是用C写的 而我写的程序是用C 写的 我的问题是 文档指定 通信基于基于C结构的API缓冲区 再多的谷歌搜索似乎也无法让我找到这个 API 或如何通过 TCP
  • Scapy 不需要的 RST TCP 数据包

    为了理解TCP是如何工作的 我尝试伪造自己的TCP SYN SYN ACK ACK 基于教程 http www thice nl creating ack get packets with scapy http www thice nl c
  • 如何模拟 TCP/IP 错误?

    在多层应用程序上 我需要模拟各种 TCP IP 错误来测试一些重新连接代码 有谁知道我可以使用什么工具 基于 Windows 来实现此目的 谢谢 Scapy http secdev org projects scapy 允许您控制数据包的各
  • Linux环境下串口数据转换为TCP/IP

    我需要从Linux系统的串口获取数据并将其转换为TCP IP发送到服务器 这很难做到吗 我有一些基本的编程经验 但对 Linux 的经验不多 有没有开源应用程序可以做到这一点 在 Linux 中您不需要编写程序来执行此操作 只是pipe h
  • 构建多线程 TCP/IP 服务器

    我想构建一个可供最多 100 个并发客户端使用的 TCP IP 服务器 但仍不确定如何开始 至少我需要服务器 监听客户端 并将它们全部存储在数组或列表中 对于每个客户端 它需要根据其客户端状态接收和发送数据 当有人连接或断开连接时 服务器应
  • 为什么turn服务器不支持tcp连接?

    我是 WebRTC 新手 我需要为我的 webrtc 应用程序配置我自己的 Turn 服务器 我使用以下命令安装了我的转弯服务器 apt get install coturn 我只需要通过 tcp 运行转变服务器 它不必使用 UDP 进行任
  • 自动打开命名管道和 tcp\ip

    我正在安装一个需要修改 SQL Server 的新产品 具体来说 启用 tcp ip 并打开命名管道 我知道如何手动完成 我想要的是一种通过 SQL 或 C 代码为新客户自动化执行此操作的方法 我希望有任何关于正确方向的建议 您可以使用 C
  • 无法分配请求的地址 - 可能的原因?

    我有一个由主服务器和分布式从服务器组成的程序 从属服务器向服务器发送状态更新 如果服务器在固定时间内没有收到特定从属服务器的消息 则会将该从属服务器标记为关闭 这种情况一直在发生 通过检查日志 我发现从站只能向服务器发送一个状态更新 然后永
  • 为什么 TCP 段中的 SYN 或 FIN 位会占用序列号空间中​​的一个字节?

    我试图理解这种设计背后的基本原理 我浏览了一些 RFC 但没有发现任何明显的东西 这并不是特别微妙 这样 SYN 和 FIN 位本身就可以被确认 因此如果丢失则可以重新发送 例如 如果连接关闭而没有发送更多数据 那么如果 FIN 没有发送任
  • 为什么 UDP 服务器中只有一个套接字?

    我正在准备考试 发现了这个问题 典型的 UDP 服务器可以使用单个套接字来实现 解释一下为什么 对于 TCP 驱动的服务器 我发现创建了两个套接字 一个用于所有客户端访问服务器 另一个用于每个客户端的特定 套接字 用于服务器和客户端之间的进
  • 触发“对等方重置连接”

    我想测试当发生 对等方重置连接 错误时我们的应用程序 嵌入式 ftp 服务器 中发生的日志记录 这个帖子 https stackoverflow com questions 1434451 connection reset by peer很
  • 为什么我收到的数据包数据大小大于mss?

    我在两台 PC 上使用 ifconfig ethX mtu 300 修改了 MTU 并使用 netperf 测试网络 我用 WireShark 嗅探了 SYN 数据包中的 MSS 260 但我得到了一些大于 260 的数据包 为什么 嗅探器
  • 如果其中一台机器死机,TCP 连接如何终止?

    如果两个主机 A 和 B 之间建立了 TCP 连接 假设主机 A 已向主机 B 发送了 5 个八位字节 然后主机 B 崩溃了 由于未知原因 主机 A 将等待确认 但如果没有收到确认 将重新发送八位字节并减小发送者窗口大小 这将重复几次 直到

随机推荐

  • SpringSecurity登陆接口

    接下我们需要自定义登陆接口 xff0c 然后让SpringSecurity对这个接口放行 让用户访问这个接口的时候不用登录也能访问 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证 所以
  • 【ubuntu20】filezilla连接主机和ubuntu20

    1 普通用户登录 sudo apt update sudo apt install openssh server 安装ssh br sudo systemctl status ssh 确认是否运行 br sudo ufw allow ssh
  • CentOS7+Nginx+阿贝云服务器使用心得

    最近有一个项目需要使用云服务器展示demo xff0c 由于是临时使用就想找一个免费的云服务器 由于以前在阿里云 腾讯云 华为云 百度云 亚马逊云都用过免费版 xff0c 这次就在网上搜了一下其它的免费云 正好就找到了阿贝云 https w
  • WSL2运行sudo gnome-session没反应

    必须注意当前用户 xff0c 不一定是在root下创建的gnome session xff0c 以我为例 xff0c 我当时是在leo用户下安装的gnome session xff0c 但之后一直都是以root用户登录 xff0c 所以运行
  • n个人围成一圈,第一个开始报数(1-3),凡报数3退出。问最后留下的人是原来第几号?

    include lt stdio h gt int main int i 61 0 j 61 0 k 61 0 n x int a 100 printf 34 please input a nu 34 scanf 34 d 34 amp n
  • 使用sea-orm执行migrate

    源码github地址 seaormdemo 一 下载工具链 sea orm cli 是sea orm 提供的工具链 xff0c 可通过cargo下载 cargo span class token function install span
  • PVE安装更新源错误

    pve系统ping 网络不通且不能进行apt install 描述 root 64 xuyuquan span class token comment apt get update span Err 1 http ftp debian or
  • failed to run command ‘java’: No such file or directory

    failed to run command java No such file or directory 程序里远程执行shell命令 xff08 nohup java jar xff09 的执行 xff0c 后台日志报错如下 xff1a
  • vue3中的setup函数

    原文 xff1a vue3中的setup函数 落雪小轩韩的博客 CSDN博客 vue3setup 一 概念 xff1a setup是vue3中的一个新的配置项 xff0c 值为一个函数 xff0c 我们在组件中用到的数据 方法等等 xff0
  • vue同步请求

    原文地址 xff1a vue 同步请求 Aa duidui的博客 CSDN博客 vue同步请求 同步请求执行的顺序 async await 挂上的才是同步 没挂上的还是异步 async 方法名 await 请求方法 参数 then res
  • Anaconda上设置虚拟环境,并在jupyter notebook中切换。

    个人记录 xff0c 但欢迎阅读和赐教 我之前在Anaconda Navigator中建立虚拟环境 xff0c 然后在jupyter notebook的terminal中增加对应环境的ipykernel xff0c 这样可行 xff0c 但
  • 字符,字节和编码

    级别 xff1a 初级 摘要 xff1a 本文介绍了字符与编码的发展过程 xff0c 相关概念的正确理解 举例说明了一些实际应用中 xff0c 编码的实现方法 然后 xff0c 本文讲述了通常对字符与编码的几种误解 xff0c 由于这些误解
  • http协议原理

    HTTP工作原理 HTTP协议定义Web客户端如何从Web服务器请求Web页面 xff0c 以及服务器如何把Web页面传送给客户端 HTTP协议采用了请求 响应模型 客户端向服务器发送一个请求报文 xff0c 请求报文包含请求的方法 URL
  • TLS协议/SSL协议

    历史背景 SSL Secure Socket Layer 安全套接层 是基于HTTPS下的一个协议加密层 xff0c 最初是由网景公司 xff08 Netscape xff09 研发 xff0c 后被IETF xff08 The Inter
  • TCP协议

    TCP 基础 https www jianshu com p ef892323e68f TCP 使用固定的连接 TCP 用于应用程序之间的通信 当应用程序希望通过 TCP 与另一个应用程序通信时 xff0c 它会发送一个通信请求 这个请求必
  • UDP协议

    UDP 概述 xff08 User Datagram Protocol xff0c 用户数据报协议 xff09 用户数据报协议 UDP 只在 IP 的数据报服务之上增加了很少一点的功能 xff0c 这就是复用和分用的功能以及查错检测的功能
  • TCP和UDP的区别

    TCP协议与UDP协议的区别 首先咱们弄清楚 xff0c TCP协议和UDP协议与TCP IP协议的联系 xff0c 很多人犯糊涂了 xff0c 一直都是说TCP协议与UDP协议的区别 xff0c 我觉得这是没有从本质上弄清楚网络通信 xf
  • 网络协议概述

    互联网协议介绍 互联网的核心是一系列协议 xff0c 总称为 互联网协议 xff08 Internet Protocol Suite xff09 xff0c 正是这一些协议规定了电脑如何连接和组网 我们理解了这些协议 xff0c 就理解了互
  • go 编写tcp和udp服务端和客户端

    TCP协议 TCP IP Transmission Control Protocol Internet Protocol 即传输控制协议 网间协议 xff0c 是一种面向连接 xff08 连接导向 xff09 的 可靠的 基于字节流的传输层
  • tcp黏包问题

    服务端代码如下 xff1a span class token keyword package span main span class token keyword import span span class token punctuati