C++:字符数组在发送过程中删除了一些值

C++: char array dropping some values during send

我在我的 C++ 项目中遇到了一个非常奇怪的问题。我有一个客户端和一个服务器,它们相互发送字符数组数据。虽然初始 sending/reception 运行良好(请参阅:MVE 中的 Client.firstConnection()),但在以下消息中,客户端在将其发送到服务器时似乎丢弃了数组中除第 12 到第 15 个字符之外的所有字符。然后服务器只接收这个部分填充的数组。

示例:

  1. 我想将缓冲区 4_user53:6134;*0/ 从客户端发送到服务器。
  2. 在发送之前,我的变量在逐字符打印时显示 4_user53:6134;*0/
  3. 在服务器端,接收到缓冲区,但是当逐个字符打印时,显示 4;*0
  4. 在客户端,发送缓冲区后,变量在逐字符打印时立即显示 4;*0

我不知道为什么这些字符会消失。感谢所有建议,谢谢。

请在下面找到最小的可行示例,我已尽力注释兴趣点并说明打印功能的结果。

MVE:

使用 VS 2017、C++ 11 编译。

//Header file information
// all imports:
#include <cstdlib>
#include <iostream>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <random>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>

// Global vars
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "6000"
#define MAX_CONS 1 //Total No of Clients allowed

//Client
class Client{

bool Client::firstConnection()
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    int iResult;
    // Initialize Winsock
    iResult = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
    if( iResult != 0 )
    {
        printf( "WSAStartup failed with error: %d\n", iResult );
        return 1;
    }
    ZeroMemory( &hints, sizeof( hints ) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the 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();
        return 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
        this->ConnectSocket = socket( ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol );
        if( this->ConnectSocket == INVALID_SOCKET )
        {
            printf( "socket failed with error: %ld\n", WSAGetLastError() );
            WSACleanup();
            return 1;
        }
        // Connect to server.
        iResult = connect( this->ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen );
        if( iResult == SOCKET_ERROR )
        {
            closesocket( this->ConnectSocket );
            this->ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    freeaddrinfo( result );
    if( this->ConnectSocket == INVALID_SOCKET )
    {
        printf( "Unable to connect to server!\n" );
        WSACleanup();
        return 1;
    }
    std::cout << "Connection to server successful!\n";
        // Send an initial buffer
        int iResult = send( this->ConnectSocket, ( this->username ).c_str(), (int)strlen( ( this->username ).c_str() ), 0 ); //username is a perfectly fine string

// -> This instance of send does NOT drop any data! <- //

        if( iResult == SOCKET_ERROR )
        {
            printf( "send failed with error: %d\n", WSAGetLastError() );
            closesocket( this->ConnectSocket );
            WSACleanup();
            return false;
        }
    return true;
}

bool Client::sendCommand( std::string command )
{
    // Send a buffer    
    char* tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below

    const char* send_buf = tmp; // have tried with and without const, results are the same

    for( int i = 0; i < (int)strlen( send_buf ); ++i )
    {
        printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
    }
// Output (literally copy/pasted):
/*
Buffer[0] = [4]
Buffer[1] = [_]
Buffer[2] = [u]
Buffer[3] = [s]
Buffer[4] = [e]
Buffer[5] = [r]
Buffer[6] = [5]
Buffer[7] = [3]
Buffer[8] = [:]
Buffer[9] = [6]
Buffer[10] = [1]
Buffer[11] = [3]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [/]
*/
// This is correct and the desired output!!! ^

    printf( "Sending: %s, length = %d\n", send_buf, (int)strlen( send_buf ) );
    // output (literally copy/pasted):
/*
Sending: 4_user53:6134;*0/, length = 17
*/
    // This is correct and the desired output!!!

    int iResult = send( this->ConnectSocket, send_buf, (int)strlen( send_buf ), 0 );

    for( int i = 0; i < iResult; ++i )
    {
        printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
    }
    //Output (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/
// Here we only see the values 12-15. This is not the desired output. This is the issue I want to solve.
    printf( "Iresult = %d\n", iResult );
    //output= 17. This is correct.

    if( iResult == SOCKET_ERROR )
    {
        printf( "\nSend failed with error: %d\n", WSAGetLastError() );
        closesocket( ConnectSocket );
        WSACleanup();
        return false;
    }
    return true;
}

bool Client::receiveMessage()
{
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    int iResult = recv( this->ConnectSocket, recvbuf, recvbuflen, 0 );

    if( iResult > 0 )
    {
        printf("Message received OK!\n");
    }
    else if( iResult == 0 )
    {
        printf( "\nConnection closed\n" );
    }
    else
    {
        printf( "\nrecv failed with error: %d\n", WSAGetLastError() );
    }
    return true;
}

}

int main()
{
    std::string in = "";
    int n = 0;
    bool rec = false;
    if( this->firstConnection() )
    {
        while( in != "Q" )
        {
            std::cin >> in;
            this->sendCommand( in );
            while( !rec )
            {
                rec=this->receiveMessage();
            }
        
        }
        return this->clientClose();
    }
    else
    {
        return false;
    }
}


//Server
class Server {

Server::Server()
{
    this->currentConnections = 0;
}

void Server::accept_clients()
{
    for( int i = 0; i < MAX_CONS; i++ )
    {
        if( !this->client[i].con ) //i.e a client has not connected to this slot
        {
            if( this->accept_client( &( this->client[i] ) ) )
            {
                //update server status
                this->Server_Status( CLIENT_CON );
            }
        }
    }
}

int Server::accept_client( _client* x )
{
    x->i = sizeof( sockaddr );
    x->cs = accept( this->ListenSocket, (sockaddr*)&x->addr, &x->i );
    if( x->cs == 0 || x->cs == SOCKET_ERROR )
    {
        printf( "SOCKET ERROR\n" );
        return false;
    }

    x->con = true;
    FD_ZERO( &x->set );
    FD_SET( x->cs, &x->set );
    printf( "Client accepted \n" );

    // declaring character array
    char char_array[DEFAULT_BUFLEN];
    strcpy_s( char_array, result.c_str() );

    this->send_clients( char_array );

    return true;
}

void Server::send_clients( char* s )
{
    int len = (int)strlen( s );

    for( int i = 0; i < MAX_CONS; i++ )
    {
        if( this->client[i].con ) //valid slot,i.e a client has parked here
        {
            this->send_client( &( this->client[i] ), s, len );
        }
    }
}

int Server::send_client( _client* x, char* buffer, int sz )
{
    char* send_buf;
    if( strchr( buffer, ';' ) == NULL )
    {
        send_buf = x->gameinfo.SerializeToArray( DEFAULT_BUFLEN ); // see below
    }
    else
    {
        send_buf = buffer;
    }
    x->i = send( x->cs, send_buf, DEFAULT_BUFLEN, 0 );
    if( x->i == SOCKET_ERROR || x->i == 0 )
    {
        printf( "Error: disconnecting client\n" );
        disconnect_client( x );
        return ( false );
    }
    else
    {
        return ( true );
    }
}

void Server::recv_clients()
{
    char buffer[DEFAULT_BUFLEN] = { 0 };

    for( int i = 0; i < MAX_CONS; i++ )
    {
        if( client[i].con ) //valid slot,i.e a client has parked here
        {
            try
            {
                if( !this->recv_client( &( this->client[i] ), buffer, DEFAULT_BUFLEN ) )
                {
                    //Error receiving
                    printf( "Error receiving message from client!\n" );
                }
            }
            catch( ... )
            {
                printf( "Error\n" );
            }
        }
    }
}


int Server::recv_client( _client* x, char* buffer, int sz )
{
    if( FD_ISSET( x->cs, &x->set ) )
    {
        x->i = recv( x->cs, buffer, sz, 0 );
        // checking contents
        for( int i = 0; i < sz+1; ++i ) 
        {
            printf( "Buffer[%d] = [%c]\n", i, send_buf[i] );
        }
        // output is (literally copy/pasted):
/*
Buffer[0] = [ ]
Buffer[1] = [ ]
Buffer[2] = [ ]
Buffer[3] = [ ]
Buffer[4] = [ ]
Buffer[5] = [ ]
Buffer[6] = [ ]
Buffer[7] = [ ]
Buffer[8] = [ ]
Buffer[9] = [ ]
Buffer[10] = [ ]
Buffer[11] = [ ]
Buffer[12] = [4]
Buffer[13] = [;]
Buffer[14] = [*]
Buffer[15] = [0]
Buffer[16] = [ ]
*/

        if( x->i == 0 )
        {
            disconnect_client( x );
            return false;
        }
        std::string result = this->update( x, buffer, sz ); // returns a perfectly normal std::string - no issues here!!!

        // declaring character array
        char char_array[DEFAULT_BUFLEN];
        strcpy_s( char_array, result.c_str() );

        this->send_clients( char_array );
        return true;
    }

    return false;
}


}

int main()
{
    Server gameServer = Server();
    while( true )
    {
        gameServer.accept_clients(); //Receive connections
        gameServer.recv_clients(); //Receive and respond to data from clients
    }
}


//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
    std::string s_out = "Hello world;50*0/";
    char out[512]; // more than enough space, no overflow issues here I promise
    strcpy_s( out, s_out.c_str() );
    return out;
}

如果你仍然不相信我,这里是打印的屏幕截图:

客户:

(调试时我将第一个迭代器长度设置为 20,这就是为什么有 20 个值,这当然是不准确的,对于给您带来的不便,我深表歉意)

服务器:

函数 GameInformation::SerializeToArray 不好,因为它 return 指向非静态局部数组。当从函数 returning 和在 returning 之后取消引用指向数组元素的指针是非法的时,数组的生命结束。

取而代之的是,您应该在堆上分配一个缓冲区并 return 一个指向该缓冲区的指针。

//Game info functions shared between client and server (only relevant ones)
char* GameInformation::SerializeToArray( int buflen )
{
    std::string s_out = "Hello world;50*0/";
    char* out = new char[512]; // more than enough space, no overflow issues here I promise
    strcpy_s( out, 512, s_out.c_str() );
    return out;
}

请注意,现在您应该在使用完后释放(通过 delete[])returned 数组。

另一种选择是 returning std::vector 而不是普通数组。这将消除手动管理内存区域的需要。

//Game info functions shared between client and server (only relevant ones)
std::vector<char> GameInformation::SerializeToArray( int buflen )
{
    std::string s_out = "Hello world;50*0/";
    std::vector<char> out(512); // more than enough space, no overflow issues here I promise
    strcpy_s( &out[0], 512, s_out.c_str() );
    return out;
}

在这种情况下调用者将是这样的:

    // Send a buffer    
    std::vector<char> tmp = this->gameinfo.SerializeToArray( DEFAULT_BUFLEN );

    const char* send_buf = tmp.data();

在这种情况下GameInformation::SerializeToArray的声明也应该改为returnstd::vector<char>