目录

Socket 通信处理不定长的数据包( Python 与 C++ )

3种一般方法

在使用 Python 和 C++ 的 socket 通信时,如果要处理不定长的数据包,可以使用以下方法之一:

  1. 在发送数据包之前,先发送一个固定长度的头部信息,用来指示接下来要接收的数据包的长度。然后再根据这个长度来接收数据。

  2. 使用分隔符来分割不同的数据包。例如,在每个数据包的末尾添加一个特殊字符,然后在接收端根据这个特殊字符来判断一个数据包是否接收完整。

  3. 使用循环接收的方法,每次接收一定长度的数据,直到接收到完整的数据包为止。

方法实现

固定长度的头部信息

python 端处理逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import socket
import struct

def send_msg(sock, msg):
    # 在发送数据之前,先发送一个固定长度的头部信息,用来指示接下来要接收的数据包的长度
    msg = struct.pack('>I', len(msg)) + msg
    sock.sendall(msg)

def recv_msg(sock):
    # 接收固定长度的头部信息
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # 根据头部信息中指示的长度来接收数据包
    return recvall(sock, msglen)

def recvall(sock, n):
    data = b''
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data += packet
    return data

# 示例:
s = socket.socket()
s.connect(('localhost', 12345))
send_msg(s, b'Hello, world!')
data = recv_msg(s)
print(data)
s.close()

c++ 端处理逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>

void send_msg(int sock, const char *msg) {
    uint32_t len = strlen(msg);
    uint32_t nlen = htonl(len);
    
    // 在发送数据之前,先发送一个固定长度的头部信息,用来指示接下来要接收的数据包的长度
    send(sock, &nlen, sizeof(uint32_t), 0);
    send(sock, msg, len, 0);
}

char* recv_msg(int sock) {
    uint32_t nlen;
    // 接收固定长度的头部信息
    recv(sock, &nlen, sizeof(uint32_t), 0);
    uint32_t len = ntohl(nlen);
    // 根据头部信息中指示的长度来接收数据包
    char *msg = new char[len+1];
    recv(sock, msg, len, 0);
    msg[len] = '\0';
    return msg;
}

int main() {
   int s = socket(AF_INET , SOCK_STREAM , 0);
   struct sockaddr_in server;
   server.sin_addr.s_addr = inet_addr("127.0.0.1");
   server.sin_family = AF_INET;
   server.sin_port = htons(12345);
   connect(s , (struct sockaddr *)&server , sizeof(server));
   
   send_msg(s , "Hello World!");   
   char *data = recv_msg(s);   
   std::cout << data << std::endl;   
   delete[] data;   
   close(s);   
   return 0;
}

c++端也可实现 senall 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int sendall(int s, const void *buf, int *len){
    int total = 0;        // how many bytes send
    int bytesleft = *len; // how many left
    int n;
    while(total < *len) {
        n = send(s, (uint8_t *)buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }
    *len = total; // return number actually sent here
    return n==-1?-1:0; // return -1 on failure, 0 on success
} 

int main(int argc, char **argv)
{
    char sendBuffer[128];
    /* 省略其他代码 */
    int msg_len = sizeof(sendBuffer);
    if (sendall(socketDescriptor, sendBuffer, &msg_len) == -1) {
        perror("message sendall");
        printf("only sent %d bytes because of the error!\n", msg_len);
        close(socketDescriptor);
        break;
    }
}

其中ssocketDescriptor

使用分隔符(未实现)

循环接收(未实现)