【问题标题】:Sending data using socket from Python(client) to C++(server) arrays使用套接字从 Python(客户端)发送数据到 C++(服务器)数组
【发布时间】:2018-07-29 19:06:33
【问题描述】:

我一直在尝试使用套接字将数组从 Python 发送到 C++,但一直遇到问题。

Python 方面存在直接发送数组的问题,例如 pickle 与 C++ 不兼容,因此我发现的唯一半可靠方法是将其作为字符串发送:

import socket
import sys
import random

# Create a TCP/IP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = ('localhost', 5555)
print >>sys.stderr, 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Message to be sent to C++
# message = [random.randint(1, 10),random.randint(1, 10),random.randint(1, 10)]
i = 0

while i < 5:
    a_converted = (random.randint(1,255), random.randint(1,255), random.randint(1,255))
    #a_converted = 'words'

    print a_converted
    # Sending message to C++
    sock.sendall(str(a_converted))
    i += 1

sock.close()

将它作为字符串发送的问题是我实际上需要它作为另一端的双样式数组。我目前拥有的 C++ 代码如下:

#include "stdafx.h"
#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h> //for SOCKET communication
#include <sstream>
#include <stdlib.h>

//Linking Ws2_32.lib, Mswsock.lib, Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#pragma warning(disable:4996)//to disable warning message

using namespace std;

int main()
{
    WSADATA WSAData;

    SOCKET server, client;

    SOCKADDR_IN serverAddr, clientAddr;

    WSAStartup(MAKEWORD(2, 0), &WSAData);
    server = socket(AF_INET, SOCK_STREAM, 0);

    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(5555);
    ::bind(server, (SOCKADDR *)&serverAddr, sizeof(serverAddr));
    listen(server, 0);

    cout << "Listening for incoming connections..." << endl;

    string a_converted[1000];

    int clientAddrSize = sizeof(clientAddr);
    if ((client = accept(server, (SOCKADDR *)&clientAddr, &clientAddrSize)) != INVALID_SOCKET)
    {
        cout << "Client connected!" << endl;
        // Loop 
        int i = 0;

        while (i<5) {

            recv(client, (char*)a_converted, sizeof(a_converted), 0);

            char char_array[sizeof(a_converted)];

            strcpy(char_array, (char*)a_converted);
            memset(a_converted, 0, sizeof(a_converted));
            cout << "Client says: " << char_array << endl; 
            cout << endl;

            i = i++;

        }
        closesocket(client);
        WSACleanup();
        cout << "Client disconnected." << endl;
    }
    cout << "Press Enter to continue" << endl;
    getchar();
}

信息已收到且正确,但我无法正确转换数据。我曾尝试使用 atof 和类似的函数在 C++ 端进行转换,但是 Python 端的逗号和括号的存在似乎导致它出错并给出零,而且我试图从字符串中删除它们几乎没有运气。

我不禁认为必须有更好的方法来做到这一点,但我对编码真的很陌生,所以如果我忽略了某些东西,我不会感到惊讶。 我希望能解释一下如何直接发送 C++ 可以从 Python 读取的数组,或者如何转换它在 C++ 中发送的字符串。

【问题讨论】:

  • 为什么不对序列化数据使用一些标准格式,比如 JSON? Pickle 当然不适用于您的情况,因为它是特定于 Python 的。

标签: python c++ arrays sockets


【解决方案1】:

实现此目的最直接的方法是使用 python 的 struct 模块将您的数组编码为便于在 C++ 中接收的二进制格式。

例如,要发送一个 32 位整数数组,您可以这样做:

import struct

def encode_int_array(int_list):
    buf = struct.pack("!I" + "I" * len(int_list), len(int_list), *int_list)
    return buf

解释:! 字符指定了在编码中使用的字节顺序(这里是大端/“网络”顺序),I 字符在这里用于数组长度,然后再一次对于每个要编码的整数。然后将实际数组长度和每个整数打包。

因此,如果您使用列表[1, 2, 3] 调用此函数,则为pack 提供的格式字符串将为"!IIII",其余参数将为3, 1, 2, 3(第一个“3”是数组长度编码)。最终结果是一个 bytes 字符串,其中包含编码的 32 位(4 字节)整数:

|ArrayLen|Integer0|Integer1|Integer2|....

将上述内容与sendall 一起使用以传输结果缓冲区:

sock.sendall(encode_int_array(
    [random.randint(1,255), random.randint(1,255), random.randint(1,255)]))

在 C++ 端,首先读取 4 个字节(获取数组长度),将数组长度转换为原生字节顺序,然后再读取额外的 4 * 数组长度字节以获取所有整数;然后将其中的每一个转换为本机字节顺序。您应该小心,永远不要假设recv 会收到所有您想要的数据。 SOCK_STREAM 语义不保证这一点。因此,您需要确保收到的数字与您预期的完全一致。

C++ 端可能如下所示:

#include <cstdint>  // uint32_t et al definitions

// Function to receive exactly "len" bytes.
// Returns number of bytes received, or -1 on EOF or error
int recv_all(int sock, char *buf, unsigned int len)
{
    unsigned int n = 0;
    int status;
    while (n < len) {
        status = recv(sock, buf + n, len - n);
        if (status == 0) {
            // Unexpected End of File
            return -1; // Or whatever
        } else if (status < 0) {
            // Error
            return -1; // Need to look at errno to find out what happened
        } else {
            n += status;
        }
     }
     return (int)n;
}

...
int status;
// Receive array length from client
uint32_t array_len;
status = recv_all(client, reinterpret_cast<char *>(&array_len), sizeof array_len);
if (status < 0) {
    // handle error
}

array_len = ntohl(array_len); // Convert length to native byte order

// Receive array contents from client
uint32_t int_array[array_len];
status = recv_all(client, reinterpret_cast<char *>(&int_array[0]), sizeof int_array);
if (status < 0) {
    // handle error
}
for (unsigned int n = 0; n < array_len; ++n)
    int_array[n] = ntohl(int_array[n]); // Convert to native byte order

(如果你只想发送单字节整数,在上面的pack调用中用'B'替换'I',下面的C++也需要相应地调整——用uint8_t代替uint32_t,比如说)

【讨论】:

  • 除了让 array_len 在 array_int 中被接受的一些问题(提到它不能被视为一个常量),它会按照我的意愿传输。对过程的非常清楚的解释。
【解决方案2】:

这听起来像是一个序列化问题。由于您想连接两种不同的语言,我建议使用标准化的、众所周知的格式,例如 JSON 或 XML。有很多库可以将 JSON 或 XML 转换为对象,反之亦然。

最终的死胡同是将数据打包成二进制文件并通过套接字发送。首先尝试 JSON 或 XML 的外部库

编辑:

JSON 描述(以一种非常简单的方式),如何将对象保存为文本(序列化)。就这么简单

{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ],
  "children": [],
  "spouse": null
}

(取自https://en.wikipedia.org/wiki/JSON

您可以想象,从文本再次读取到对象(反序列化)是一个非常直接的过程。那里有图书馆可以为您完成这项工作。这对您的项目意味着:

  • 找到一个可以将对象序列化为 JSON 格式的 python JSON 库
  • 找到一个可以将数据反序列化为对象的 c++ JSON 库

我已经看到了这个在使用中,因为每个数据类型都被视为一个字符串来发送它,并且在反序列化方面,你需要为每种情况决定它真正是哪种数据类型(将字符串转换为 double 或 int或浮点数或布尔值等。)

【讨论】:

【解决方案3】:

你需要将python中的String转换为Bytes,而

sock.sendall

参考Python: convert string to byte array

根据您的 python 语法,它应该是 python 2。

Python 3 在 sendall 时可以轻松找到并坚持转换为 Bytes。

在python 2中,你可以像使用bytearray一样

sock.sendall (bytearray(str(a_converted)))

在python 3中,您可以在转换为字节时调用默认为UTF-8的encode。

sock.sendall(str(a_converted).encode())

【讨论】:

  • 你知道如何让 C++ 正确接收它吗,因为 recv 函数只会接受 char*?
  • 我可以找到这样的作品。客户说: (97, 12, 127)(187, 67, 80)(235, 177, 44)(91, 215, 50)(10, 80, 31)╠╠╠╠╠╠╠╠╠╠╠
  • @MauriceRandomNumber 提到,如果您不费心编写自己的解析逻辑,则应该依靠 JSON 或 XML 来解析标准格式。 31 之后的符号在接收后尝试打印未分配的字节。您可以通过查找分隔符来修复它。
猜你喜欢
  • 2020-06-01
  • 1970-01-01
  • 2016-07-17
  • 2017-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-26
  • 1970-01-01
相关资源
最近更新 更多