通过套接字从 C++ 服务器向 Python 客户端发送数据的问题

Mic*_*ray 1 c++ python sockets

我创建了一个简单的 C++ 服务器,它试图vector<uint8_t>通过套接字将 a的字节发送到 Python 客户端。我已经让所有服务器和客户端连接正常,但是数据在 python 中输出不正确。我首先发送一个整数,描述期望的字节数,然后发送字节。但是,当我运行我的 python 脚本并调用recv套接字时,我有时会得到不正确的字节数值。

我已经调试了 C++ 和 Python 代码,它显示了一些奇怪的行为。首先,我意识到在调试 Python 代码时它运行良好。事实证明,在我的 Python 脚本中,如果我sleep(1)在读取整数标头的前 4 个字节和读取其余字节之间调用,它会起作用。但是,当我在recv没有睡眠的情况下进行多次通话时,事情就会出错。我还检查了字节字节序的内容是否正确。我什至在服务器和客户端之间添加了一个握手程序,以确保它们能很好地协同工作。

对于 C++:设置套接字:

int Server::setup_socket(int port){
    int server_fd, new_socket; 
    struct sockaddr_in address; 
    int opt = 1; 
    int addrlen = sizeof(address); 

    // Creating socket file descriptor 
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){ 
        perror("socket failed"); 
        exit(EXIT_FAILURE); 
    } 

    // Forcefully attaching socket to the port 
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))){ 
        perror("setsockopt"); 
        exit(EXIT_FAILURE); 
    } 
    address.sin_family = AF_INET; 
    address.sin_addr.s_addr = INADDR_ANY; 
    address.sin_port = htons(port); 

    // Forcefully attaching socket to the port 
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0){ 
        perror("bind failed"); 
        exit(EXIT_FAILURE); 
    } 
    if (listen(server_fd, 3) < 0){ 
        perror("listen"); 
        exit(EXIT_FAILURE); 
    } 
    printf("Successfully connected to port %d\n", port);
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0){ 
        perror("accept"); 
        exit(EXIT_FAILURE); 
    } 

    return new_socket;
}

Run Code Online (Sandbox Code Playgroud)

发送数据和握手方法:

void Server::send_image(cv::Mat &image) {
    std::vector<uint8_t> buf;
    std::vector<int> param(2);
    param[0] = cv::IMWRITE_JPEG_QUALITY;
    param[1] = 80; //default(95) 0-100
    cv::imencode(".jpg", image, buf, param);

    int length = buf.size();
    printf("Sending image of size: %d\n", length);
    write(data_socket, &length, sizeof(length));
    write(data_socket, buf.data(), length);
}

void Server::confirm_sent(){
    uint8_t confirmation[1];
    write(conf_socket, confirmation, 1);
}

void Server::confirm_received(){
    uint8_t confirmation[1];
    read(conf_socket, confirmation, 1);
}
Run Code Online (Sandbox Code Playgroud)

蟒蛇代码:

data_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # For sending data
conf_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # For hand shaking

# Connect the socket to the port where the server is listening
data_address = ('192.168.1.146', 2323)
conf_address = ('192.168.1.146', 2324)
print('connecting to %s port %s' % data_address)
data_sock.connect(data_address)
time.sleep(1)
print('connecting to %s port %s' % conf_address)
conf_sock.connect(conf_address)

while True:
    conf_sock.recv(1)  # Confirm sent
    size1 = int.from_bytes(data_sock.recv(4), byteorder="big") 
    size2 = socket.ntohl(size1) # Fixes endian problems
    # time.sleep(1) # Inserting this fixes things, but I don't want the delay
    data = np.frombuffer(data_sock.recv(size2), dtype=np.uint8)
    print(f"{size1}, {size2}, {data.shape}")
    conf_sock.send(bytes(1))
Run Code Online (Sandbox Code Playgroud)

C++ 输出和预期大小:

Max speed spi is 8000000
OV5642 detected.
Successfully connected to port 2323
Successfully connected to port 2324
Sending image of size: 134966
Sending image of size: 135072
Sending image of size: 134628
Sending image of size: 134846
Sending image of size: 134704
Sending image of size: 134885
Sending image of size: 133942
Run Code Online (Sandbox Code Playgroud)

Python收到的大小:

connecting to 192.168.1.146 port 2323
connecting to 192.168.1.146 port 2324
906953216, 134966, (95568,)
1224436735, 4285266760, (45190,)
2585803520, 3874970, (137968,)
939478527, 4283301687, (137524,)
103119361, 24782086, (136294,)
1526714366, 4275044186, (127464,)
469746175, 4290903835, (136333,)
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 5

网络数据可以慢慢到来,并socket.recv()会给您最多字节的请求数量,但数量较少,如果没有在缓冲足够的数据。

您需要继续调用,recv()直到所有字节都到达为止。这很常见,您需要一个函数来为您处理重复调用:

def socket_read(sock, expected):
    """Read expected number of bytes from sock

    Will repeatedly call recv until all expected data is received

    """
    buffer = b''
    while len(buffer) < expected:
        buffer += sock.recv(expected - len(buffer))
    return buffer
Run Code Online (Sandbox Code Playgroud)

并使用该函数接收您的数据:

message_size = int.from_bytes(socket_read(data_sock, 4), byteorder="little")
data = np.frombuffer(socket_read(data_sock, message_size), dtype=np.uint8)
Run Code Online (Sandbox Code Playgroud)

关于字节顺序的说明:由服务器以特定的字节顺序发送数据。TCP/IP 连接上的网络顺序是大端顺序,因此您的 C++ 代码需要使用该顺序,并且int.from_bytes(..., byteorder="big") 使用socket.ntohl(). 该byteorder参数与平台无关,使用此方法将字节解释为整数不受平台差异的影响。

现在,您的 C++ 代码根本不处理字节顺序;使用服务器代码中的htonl()函数来确保写出正确的字节顺序:

def socket_read(sock, expected):
    """Read expected number of bytes from sock

    Will repeatedly call recv until all expected data is received

    """
    buffer = b''
    while len(buffer) < expected:
        buffer += sock.recv(expected - len(buffer))
    return buffer
Run Code Online (Sandbox Code Playgroud)

然后int.from_bytes(..., byteorder="big")在 Python 中使用。