一. 前言

在网络编程中,粘包是指TCP协议在传输过程中,多条数据被合并成一条数据发送或者一条数据被拆分成多个数据发送的现象。

二. 粘包问题的常规处理方法:

使用固定长度的包头 可以在发送数据前先发送一个固定长度的包头,包头中包含了数据的长度信息。接收方根据包头中的长度信息来正确地拆分数据。

三. 示例代码

接收方:【server】服务端示例代码

# -*- coding: utf-8 -*-
import json
import socket
import struct
from threading import Thread

import select


class SimSocketServer(Thread):

    def __init__(self, addr, logger=None):
        Thread.__init__(self, name="SimSocketServer")
        self.SN = 0
        self.connected_clients = []
        self.addr = addr
        self.seqNo = 0
        self.sample_No = 0
        self.socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 存储已连接的 client_socket
        self.connected_clients = []
        self.started = True  # 服务启动标志
        self.dataBuffer = bytes()
        self.headerSize = 8  # 协议头(4)+长度(2)+命令字(2),消息头的字节总数
        self.stut = '<4sHH'  # 字节串格式

        self.logger = logger
        self.jsonTemplate = {
            "Command": "FORWARD_ELEV_INFO",
            "DeviceId": "C0002T",
        }

    def run(self):
        print('=================================================================')
        print("HC SimBattery Server addr:{} started listen...".format(self.addr))
        print('=================================================================')
        # 创建一个服务器 socket,并绑定到指定地址和端口
        self.socket_server.bind(self.addr)
        self.socket_server.listen(5)
        # 将 socket_server 设置为非阻塞模式
        self.socket_server.setblocking(False)
        self.started = True
        # threading.Thread(target=self.send_heartbeat2client).start()
        # threading.Thread(target=self.send_sample2client).start()
        # threading.Thread(target=self.send_cmd2client).start()

        while True:
            # 使用 select 函数监听所有连接的 client_socket
            readable_sockets, _, _ = select.select([self.socket_server] + self.connected_clients, [], [], 1)

            # 处理所有可读的 socket
            for sock in readable_sockets:
                # 如果是 socket_server 表示有新的连接
                if sock == self.socket_server:
                    client_socket, client_address = self.socket_server.accept()
                    self.connected_clients.append(client_socket)
                    print(f"New client connected -> : {client_address}")
                # 否则是已连接的 client_socket,需要处理收到的数据
                else:
                    try:
                        data = sock.recv(1024 * 100)
                        print(f'recv client [{sock.getpeername()}] origin data -> {data}')
                        if data:
                            # 处理接收到的消息
                            # print(f"Received data from {sock.getpeername()}: {data.decode()}")
                            res = self.receive(data)

                        else:
                            # 如果收到的数据为空,表示连接已经断开,需要关闭 socket 并从 connected_clients 中移除
                            print(f"Client -> {sock.getpeername()} disconnected")
                            sock.close()
                            self.connected_clients.remove(sock)

                        # 发送消息到所有连接的客户端(除了发送者)
                        # for client in self.connected_clients:
                        #     if client != sock:
                        #         client.send(data)
                    except Exception as e:
                        # 出现异常,也需要关闭 socket 并从 connected_clients 中移除
                        self.logger.warn(f"Error occurred while receiving data from {sock.getpeername()}: {e}")
                        sock.close()
                        self.connected_clients.remove(sock)

    def receive(self, pk_data):
        try:
            if pk_data:
                if pk_data.startswith(b'{'):
                    json_data = json.loads(pk_data)
                    # json_data = eval(pk_data)
                    print('【收到】客户端上传的采样[JSON]数据 ->', json_data)
                    return json_data
                elif pk_data.startswith(b'hello'):
                    self.dataBuffer += pk_data
                    while len(self.dataBuffer) != 0:
                        # 数据量不足消息头部时跳出函数继续接收数据
                        if len(self.dataBuffer) < self.headerSize:
                            # sm_log.logger.info("数据包(%s Byte)小于消息头部长度,跳出小循环" % len(self.dataBuffer))
                            break

                        upk_head = struct.unpack(self.stut, bytearray(self.dataBuffer[:self.headerSize]))  # 解码出消息头部
                        bodyhead = upk_head[0]
                        # 获取消息正文长度
                        bodySize = upk_head[1]
                        # 分包情况处理,跳出函数继续接收数据
                        if len(self.dataBuffer) < self.headerSize + bodySize + 2 + 2 or (
                                self.headerSize + bodySize + 2 + 2) > 1024:  # 包含流水号及crc校验码
                            # sm_log.logger.info("数据包(%s Byte)不完整(总共%s Byte),跳出小循环" % (len(self.dataBuffer), self.headerSize + bodySize))
                            break
                        # 读取消息正文的内容
                        pk_body = self.dataBuffer[self.headerSize:self.headerSize + bodySize]
                        pk_sn = self.dataBuffer[self.headerSize + bodySize:self.headerSize + bodySize + 2]
                        pk_crc = self.dataBuffer[self.headerSize + bodySize + 2:self.headerSize + bodySize + 4]

                        pk_hd = self.dataBuffer[4:8]  # 负载长度 + cmd_id 的字节码
                        # crc_cmp = crc16Add((pk_hd + pk_body + pk_sn))
                        # crc_cmp = int(crc_cmp, 16)
                        # crc_cmp = struct.pack('<H', crc_cmp)
                        # print(f'服务端解析函数解析的:crc_cmp -> {crc_cmp} ====== 客户端发送过来的:pk_crc -> {pk_crc}')
                        if pk_crc:
                            cmd_id = upk_head[2]
                            # 处理消息
                            print('处理消息 ->', pk_data)
                            # msgHandler(cmd_id, pk_body, pk_sn, )
                            # msgHandler(cmd_id, pk_body, pk_sn, p_que_main, p_plc_ctl, p_que_socket_send, m_pc_conn_sta)
                        else:
                            print(
                                f'===CRC校验出错,接收信息解析失败, crc_cmp = {crc_cmp.hex()}, pk_crc = ={pk_crc.hex()} ===')

                        # 粘包情况的处理,获取下一个数据包部分
                        self.dataBuffer = self.dataBuffer[self.headerSize + bodySize + 2 + 2:]

                    if len(self.dataBuffer) != 0:
                        return True  # 继续接收消息
                    else:
                        return False  # 不再接收消息
            else:
                return False  # 不再接收消息
        except Exception as e:
            print(f"recv [pk_data] error: {e} [pk_data] {pk_data}, dataBuffer: {self.dataBuffer}")
            self.dataBuffer = bytes()
            return False  # 不再接收消息


if __name__ == '__main__':
    simSocketServer = SimSocketServer(("127.0.0.1", 2021))
    simSocketServer.start()
  1. 创建一个Socket服务器,绑定到指定地址和端口,并监听连接请求。
  2. 使用select函数监听所有连接的客户端Socket,处理可读的Socket。
  3. 如果可读的Socket是服务器Socket,说明有新的客户端连接请求,接受连接并添加到已连接的客户端列表。
  4. 如果可读的Socket是已连接的客户端Socket,说明有数据发送过来,接收数据并处理。
  5. 对于接收到的数据,根据不同的数据格式进行解析和处理。
  6. 如果数据为空,表示连接已断开,关闭Socket并从已连接客户端列表中移除。
  7. 如果出现异常,关闭Socket并从已连接客户端列表中移除。

四. 总结

需要注意的是,这个代码示例只是演示了如何处理消息粘包的问题,它并不考虑错误处理和异常情况。在实际应用中,需要根据具体需求添加适当的错误处理和异常处理代码。总的来说原理和方法一类似,都是需要根据消息头来确定处理粘包的消息内容。

以上就是关于Python - 【socket】粘包常见的另一种方法处理。