通过 TCP 在结构中发送字符串 (c++)

Sending string in a struct over TCP (c++)

我正在处理服务器-客户端结构,我需要在服务器和客户端之间发送一个字符串,为此我创建了一个包含 int(数据包类型)和字符串的结构,然后我序列化并通过 tcp 连接发送到客户端,然后反序列化它。

我的问题是我在客户端获取的字符串错误,在服务器端的字符串是:"PO-1-25-25\nPO-2-50-50\n",但在客户端获取的字符串是“\x18=$”。

我发送的结构(在客户端也是相同的结构)

struct Packet {

unsigned int packet_type;

std::string message;

void serialize(char * data) {
    memcpy(data, this, sizeof(Packet));
}

void deserialize(char * data) {
    memcpy(this, data, sizeof(Packet));
}
};

我发送结构的代码:

void sendGameStatePacket(unsigned int receiver){


const unsigned int packet_size = sizeof(Packet);
    char packet_data[packet_size];

    Packet packet;
    packet.packet_type = GAME_STATE;
    packet.message = message;


    packet.serialize(packet_data);

    network->sendToOne(packet_data, packet_size, receiver);
}

void sendToOne(char * packets, int totalSize, unsigned int socketID){

SOCKET currentSocket = sessions[socketID];
int iSendResult;    
iSendResult = NetworkServices::sendMessage(currentSocket, packets,totalSize); 

}

int sendMessage(SOCKET curSocket, char * message, int messageSize)
{
    return send(curSocket, message, messageSize, 0);
}

接收结构的客户端代码:

char network_data[MAX_PACKET_SIZE]; //MAX_PACKET_SIZE = 1000000

void AClientGame::update()
{
Packet packet;
int data_length = network->receivePackets(network_data);
unsigned int i = 0;
    while (i < (unsigned int)data_length)
    {
        packet.deserialize(&(network_data[i]));
        i += sizeof(Packet);

        FString message;
        message = packet.message.c_str();
        UE_LOG(LogTemp, Warning, TEXT("Test: %s"), *message);
    }
}

int receivePackets(char * recvbuf)
{
    iResult = NetworkServices::receiveMessage(ConnectSocket, recvbuf, MAX_PACKET_SIZE);
}

int receiveMessage(SOCKET curSocket, char * buffer, int bufSize)
{
    return recv(curSocket, buffer, bufSize, 0);
}

我已经检查过客户端的package.message是“\x18=$”字符串,所以问题不在于从字符串到FString的转换。

我的套接字配置如下:

服务器:

network::ServerNetwork::ServerNetwork(void)
{
    // create WSADATA object
    WSADATA wsaData;

    // our sockets for the server
    ListenSocket = INVALID_SOCKET;
    ClientSocket = INVALID_SOCKET;



    // address info for the server to listen to
    struct addrinfo *result = NULL;
    struct addrinfo hints;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        exit(1);
    }

    // set address information
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;    // TCP connection!!!
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);

    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        exit(1);
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);

    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        exit(1);
    }

    // Set the mode of the socket to be nonblocking
    u_long iMode = 1;
    iResult = ioctlsocket(ListenSocket, FIONBIO, &iMode);

    if (iResult == SOCKET_ERROR) {
        printf("ioctlsocket failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        exit(1);
    }

    // Setup the TCP listening socket
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);

    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        exit(1);
    }

    // no longer need address information
    freeaddrinfo(result);

    // start listening for new clients attempting to connect
    iResult = listen(ListenSocket, SOMAXCONN);

    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        exit(1);
    }
}

客户:

ClientNetwork::ClientNetwork()
{
    // create WSADATA object
    WSADATA wsaData;

    // socket
    ConnectSocket = INVALID_SOCKET;

    // holds address info for socket to connect to
    struct addrinfo *result = NULL,
        *ptr = NULL,
        hints;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        exit(1);
    }



    // set address info
    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;  //TCP connection!!!

    //resolve server address and port 
    iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);

    if (iResult != 0)
    {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        exit(1);
    }

    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);

        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            exit(1);
        }

        // Connect to server.
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);

        if (iResult == SOCKET_ERROR)
        {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            printf("The server is down... did not connect");
        }
    }



    // no longer need address info for server
    freeaddrinfo(result);



    // if connection failed
    if (ConnectSocket == INVALID_SOCKET)
    {
        printf("Unable to connect to server!\n");
        WSACleanup();
        exit(1);
    }

    // Set the mode of the socket to be nonblocking
    u_long iMode = 1;

    iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
    if (iResult == SOCKET_ERROR)
    {
        printf("ioctlsocket failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        exit(1);
    }

    //disable nagle
    char value = 1;
    setsockopt(ConnectSocket, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
}

如果有人能解释为什么它不起作用以及如何修复它将会有很大的帮助

首先,表达式 sizeof(packet) 不会给出包括字符串在内的结构的大小,因为字符串很可能只是一个指针。

并且由于字符串只是一个指针,你在序列化函数中复制的数据不是字符串而是指针,你不能通过网络发送指针,因为它们对于一个进程来说是唯一的单系统。

您必须获取字符串的实际大小,然后分配该数量的内存(加上您需要发送的任何其他内容)并使用该大小。当然,由于字符串的大小可变,您还需要在消息 header.

中发送消息的实际大小

您可以在 "string" 字节之前添加例如另外两个字节,表示 "string" 长度(多少个字符)。所以长度将是 first_byte*256+second_byte

当您在单个数据包中将多个字符串从客户端发送到服务器(或其他方向)时,这会很有用。然后你只需计算偏移量。

struct Packet {

unsigned int packet_type;

std::string message;

void serialize(char * data) {
    memcpy(data, this, sizeof(Packet));
}

void deserialize(char * data) {
    memcpy(this, data, sizeof(Packet));
}
};

这有很多错误,很难知道从哪里开始。首先,deserialize 的调用者如何知道要传递多少字节?其次,实际序列化 message 的代码在哪里?计算结构及其中的数据大小的代码在哪里?

当您 "serialize" 某些东西时,您必须将其排列成 特定的 字节格式。这是什么格式?根本没有将消息转换为任何特定格式的代码。

这是期望事情神奇地工作的代码。

如果您打算使用 TCP,在编写一行代码之前,请写出您将用于在字节级别交换数据的协议的规范。涵盖消息将如何定界,哪一方将在何时传输,等等。您可以查看一些现有的 HTTP 和 SMTP 规范,以了解规范应该是什么样子。

序列化代码应生成规范要求的精确字节格式。反序列化代码应遵循规范的消息定界规则。