【Socket】解决TCP粘包问题

socket,tcp · 浏览次数 : 0

小编点评

TCP粘包问题是一种典型的网络编程问题,由于TCP协议本身的特点,数据包在传输过程中可能会出现粘在一起的情况,这对于接收端来说可能会导致解析错误。本文主要讨论了TCP粘包问题的产生原因、解决方法以及不同解决方案的应用场景。 一、TCP粘包问题产生原因 由于TCP是面向连接的协议,客户端和服务端需要进行三次握手才能建立连接,而在四次的挥手过程中,客户端和服务端都可以主动断开连接。这种特性导致了客户端和服务端之间会建立一个传输管道,数据可以在其中不断传输。但是,由于数据传输和接收存在差异,可能会出现服务端处理速度较慢,导致缓冲区堆积多个数据包的情况。当服务端处理完后再去取数据时,可能会取到多个数据包粘在一起的数据包,导致处理出现问题。 二、TCP粘包问题解决方法 1. 设置包长:通过设置消息长度,可以避免粘包问题。这种方法适用于客户端和服务端都能够预先知道消息长度的场景。 2. 设置包头包尾:通过设置包头包尾,可以明确区分各个数据包,解决粘包和拆包问题。例如,服务端可以将每个数据包的头部和尾部设置为特定的字符或数字,接收端通过识别这些特征来判断何时停止接收数据包。 3. 设置消息分隔符:通过设置消息分隔符,可以在消息之间添加一个标识符,从而使得各个数据包的内容能够被独立解析。例如,服务端可以将消息的结束标志(如字符串或数字)作为分隔符,这样接收端只要在遇到分隔符时就认为一个消息结束,从而避免了粘包问题。 三、结论 TCP粘包问题是由于TCP的流式传输特点导致的,在传输过程中多个数据包可能会粘在一起。粘包问题会导致接收端无法正确解析数据包,因为接收端无法区分哪些字节属于哪个数据包,可能会出现数据包内容混乱或不完整的情况。为了解决这个问题,可以使用固定长度消息、消息分隔符、消息头加消息体、应用层协议等方法。具体选择哪种方法需要根据应用场景和需求来确定。

正文

一、介绍

TCP一种面向连接的、可靠的、基于字节流的传输层协议。

三次握手:

  1. 客户端发送服务端连接请求,等待服务端的回复。
  2. 服务端收到请求,服务端回复客户端,可以建立连接,并等待。
  3. 客户端收到回复并发送,确认连接。服务端收到回复。连接成功。
    image.png
    四次挥手:

与三次握手不同,客户端和服务端都可以主动断开连接。

  1. 服务A向服务B发送FIN报文段,表示没有数据要传输
  2. 服务B收到报文段,回复一个ACK报文段,表示也没有数据需要传输了。
  3. 服务B发送FIN报文段,请求关闭连接。
  4. 服务A收到报文段,服务B发送ACK报文段,服务B收到报文段后直接关闭连接,服务A没有收到回复,也开始断开连接。
    image.png

因为复杂的三次握手和四次挥手,保证了数据的可靠性和安全性。因此也造成了更大的开销。

二、产生的问题

由于TCP的可靠性传输,可以理解为客户端和服务端之间建立了一个传输管道,可以互相不断的传输数据。但是可能由于数据的传输与接收之间存在差异。使用在服务端和客户端之间,存在一个缓冲区,用于数据的缓冲。数据传输之前会先到缓冲区。

例如服务端A和客户端B。A不断向服务端传输数据,B不断处理服务A传输的数据。服务A发送数据到缓冲区,服务B从缓冲区获取数据来处理。由于服务B处理的速度比较慢,就会导致缓冲区堆积多个数据包。当服务B处理完再取时,取出的可能是多个数据包粘在一起的数据包,这时候处理就会出现问题。
image.png

三、解决方案

设置包长、包头包尾、消息分隔符解决粘包和拆包问题。这些方法通过明确消息边界,确保接收端能够准确地解析每个完整的消息。这里举例数据包分隔符。

1、设置包头包尾

现在我们模拟粘包情况,也就是客户端数据堆积。

Server

import socket
import time

def receive_message(sock):
    buffer = b""
    while True:
        packet = sock.recv(1024)
        if not packet:
            break
        buffer += packet
        print("缓冲区数据 : "+ str(buffer))
        time.sleep(5)
        while True:
            start_index = buffer.find(b"StartPackage")
            end_index = buffer.find(b"EndPackage")
            if start_index != -1 and end_index != -1 and start_index < end_index:
                start_index += len(b"StartPackage")
                message = buffer[start_index:end_index]
                buffer = buffer[end_index + len(b"EndPackage"):]  
                print("收到客户端消息: "+message.decode())
            else:
                break 

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(('localhost', 8888))
server_sock.listen(1)

client_sock, _ = server_sock.accept()

receive_message(client_sock)
client_sock.close()
server_sock.close()

Client

import socket
import time

def send_message(sock, message):
    packet = b"StartPackage" + message.encode() + b"EndPackage"  
    sock.sendall(packet)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8888))

for i in range(1,11,1):
    message = "Hello, world!--"+str(i)
    send_message(sock, message)
    print("发送消息 "+message)
    time.sleep(1)
sock.close()

根据服务端输出可以看到,缓冲区已经出现粘包,多个数据包堆积到一起,这里利用包头包尾进行拆包,确保数据的完整性。

2、设置包长

Server

import socket
import struct
import time

def receive_message(sock):
    buffer = b""
    while True:
        packet = sock.recv(1024)
        if not packet:
            break
        buffer += packet
        print(f"缓冲区数据 : {buffer}")
        
        while len(buffer) >= 4:  
            header = buffer[:4]
            message_length = struct.unpack('>I', header)[0]
            print(f"包长为: {message_length}")

            if len(buffer) < 4 + message_length:
                break

            start_index = 4
            end_index = 4 + message_length
            message = buffer[start_index:end_index]
            buffer = buffer[end_index:] 
            print(f"收到客户端消息: {message.decode()} ")
        time.sleep(5)  

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(('localhost', 8888))
server_sock.listen(1)
client_sock, _ = server_sock.accept()
receive_message(client_sock)
client_sock.close()
server_sock.close()

Client

import socket
import struct
import time

def send_message(sock, message):
    message_bytes = message.encode()
    message_length = len(message_bytes)
    header = struct.pack('>I', message_length)
    packet = header + message_bytes
    sock.sendall(packet)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8888))

for i in range(1, 11):
    message = "Hello, world!--" + str(i)
    send_message(sock, message)
    print(f"发送消息:{message}" )
    time.sleep(1)

sock.close()

可以看到由于处理的时间过长,导致数据堆积在缓冲区形成粘包。通过在消息头部设置包长,确定数据包的完整性。通过包长将粘包进行拆包。

3、设置包分隔符

Server

import socket
import time

def receive_message(sock):
    buffer = b""
    delimiter = b"<END>"
    while True:
        packet = sock.recv(1024)
        if not packet:
            break
        buffer += packet
        print("f缓冲区数据: {buffer} ")
        
        while True:
            end_index = buffer.find(delimiter)
            if end_index != -1:  
                message = buffer[:end_index]  
                buffer = buffer[(end_index + len(delimiter)):]  
                print(f"收到客户端消息: { message.decode()} ")
            else:
                break
        time.sleep(5)              

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(('localhost', 8888))
server_sock.listen(1)
client_sock, _ = server_sock.accept()
receive_message(client_sock)
client_sock.close()
server_sock.close()

Client

import socket
import time

def send_message(sock, message):
    delimiter = b"<END>"
    packet = message.encode() + delimiter 
    sock.sendall(packet)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8888))

for i in range(10):
    message = "Hello, world!--" + str(i)
    send_message(sock, message)
    print(f"发送消息: {message}")
    time.sleep(1)
sock.close()

可以看到也是出现了数据堆积,粘包,但是最后打印的结果是正确的。通过使用数据包分隔符,保证数据的完整性。

四、总结

TCP粘包问题是由于TCP的流式传输特点导致的,在传输过程中多个数据包可能会粘在一起。粘包问题会导致接收端无法正确解析数据包,因为接收端无法区分哪些字节属于哪个数据包,可能会出现数据包内容混乱或不完整的情况。为了解决这个问题,可以使用固定长度消息、消息分隔符、消息头加消息体、应用层协议等方法。具体选择哪种方法需要根据应用场景和需求来确定。

与【Socket】解决TCP粘包问题相似的内容:

【Socket】解决TCP粘包问题

TCP粘包是指在使用TCP协议进行数据传输时,发送方连续发送的多个数据包在接收方收到时被黏合成一个大的数据包。这种现象可能会导致接收方无法正确解析数据,从而影响应用程序的正常运行。

[转帖]tcp 粘包 和 TCP_NODELAY 学习

https://www.cnblogs.com/zhangkele/p/9494280.html TCP通信粘包问题分析和解决 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将

【Socket】解决UDP丢包问题

UDP是一种不可靠的、无连接的、基于数据报的传输层协议。相比于TCP就比较简单,像写信一样,直接打包丢过去,就不用管了,而不用TCP这样的反复确认。所以UDP的优势就是速度快,开销小。

[转帖]Linux句柄调优之nofile、nr_open、file-max

https://www.jianshu.com/p/8fb056e7b9f8 在开发运维的时候我们常常会遇到类似“Socket/File: Can’t open so many files”,“无法打开更多进程”,或是coredump过大等问题,这些都可以设置资源限制来解决。今天在教某位客户设置最大

[转帖]Linux句柄调优之nofile、nr_open、file-max

https://www.jianshu.com/p/8fb056e7b9f8 在开发运维的时候我们常常会遇到类似“Socket/File: Can’t open so many files”,“无法打开更多进程”,或是coredump过大等问题,这些都可以设置资源限制来解决。今天在教某位客户设置最大

[转帖]Linux句柄调优之nofile、nr_open、file-max

https://www.jianshu.com/p/8fb056e7b9f8 在开发运维的时候我们常常会遇到类似“Socket/File: Can’t open so many files”,“无法打开更多进程”,或是coredump过大等问题,这些都可以设置资源限制来解决。今天在教某位客户设置最大

基于服务器响应的实时天气数据进行JSON解析的详细代码及其框架

#include #include #include #include #include #include #include

Socket 如何处理粘包

Socket 如何处理粘包 什么是粘包什么是半包? 粘包: 比如发送了AA BB 两条消息,但是另一方接收到的消息却是AAB,像这种一次性读取了俩条数据的情况就是粘包 半包: 比如发送的消息是ABC时,另一方收到的是AB和C俩条消息,这就是半包 为什么会有这种问题呢? 这是因为 TCP 是面向连接的

14.2 Socket 反向远程命令行

在本节,我们将继续深入探讨套接字通信技术,并介绍一种常见的用法,实现反向远程命令执行功能。对于安全从业者而言,经常需要在远程主机上执行命令并获取执行结果。本节将介绍如何利用 `_popen()` 函数来启动命令行进程,并将输出通过套接字发送回服务端,从而实现远程命令执行的功能。在实现反向远程命令执行时,我们可以使用 `_popen(buf, "r")` 函数来执行特定的命令,并将其输出重定向到一个

14.3 Socket 字符串分块传输

首先为什么要实行分块传输字符串,一般而言`Socket`套接字最长发送的字节数为`8192`字节,如果发送的字节超出了此范围则后续部分会被自动截断,此时将字符串进行分块传输将显得格外重要,分块传输的关键在于封装实现一个字符串切割函数,将特定缓冲区内的字串动态切割成一个个小的子块,当切割结束后会得到该数据块的个数,此时通过套接字将个数发送至服务端此时服务端在依次循环接收数据包直到接收完所有数据包之后