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

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

本文关键字:字符串 结构 TCP c++ 通过      更新时间:2023-10-16

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

我的问题是我在客户端得到错误的字符串,服务器端的字符串是:"PO-1-25-25PO-2-50-50",但我在客户端得到的字符串是"\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: %dn", 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: %dn", 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: %ldn", 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: %dn", 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: %dn", 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: %dn", 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: %dn", 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: %dn", 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: %ldn", 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: %dn", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        exit(1);
    }
    //disable nagle
    char value = 1;
    setsockopt(ConnectSocket, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
}

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

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

由于字符串只是一个指针,

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

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

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的代码在哪里?计算包含数据的结构大小的代码在哪里?

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

这是期望事情通过魔术工作的代码。

如果你打算使用TCP,在你编写哪怕一行代码之前,写出一个规范,你应该使用在字节级别交换数据。涵盖如何分隔消息、何时传输哪一端等。您可以查看HTTP和SMTP等一些现有规范,以了解规范应该是什么样子。

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

您可以

这样做,以便在"字符串"字节前面添加例如另外两个字节,它们将代表"字符串"长度(多少个字符)。 所以长度将是first_byte*256+second_byte

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