Search code examples
c++arraysnetwork-programmingbuffer

C++: char array dropping some values during send


I am facing a very odd problem in my C++ project. I have a client and a server that send char arrays of data to each other. While the initial sending/reception works well (see: Client.firstConnection() in MVE), in the following messages the client appears to drop all but the 12th through 15th chars in the array as it sends it to the server. The server then only receives this partially filled array.

Example:

  1. I want to send the buffer 4_user53:6134;*0/ from the client to the server.
  2. Before sending, my variable, when printed character by character, shows 4_user53:6134;*0/
  3. On the server side, the buffer is received but when printed character by character, shows 4;*0
  4. On the client side, immediately after sending the buffer, the variable, when printed character by character, shows 4;*0.

I do not know why these characters are disappearing. All advice is appreciated, thank you.

Please find the minimal viable example below, I have done my best to annotate points of interest as well as illustrate the results of the print functions.

MVE:

Compiled with 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;
}

In case you still don't believe me, here are screenshots of the prints:

Client: A black terminal screen that shows the issue I have been describing

(while debugging I set the first iterator length to 20, that's why there are 20 values, this is of course inaccurate and I apologise for any inconvenience)

Server: A black terminal screen that shows the issue I have been describing


Solution

  • The function GameInformation::SerializeToArray is bad because it is returning a pointer to non-static local array. The life of the array ends when returning from function and dereferencing pointers pointing at elements of the array after returning is illegal.

    Instead of that, you should allocate a buffer on the heap and return a pointer to that.

    //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;
    }
    

    Note that now you should free (via delete[]) the returned array after finished using that.

    Another option is returning std::vector instead of plain array. This will eliminate needs to manually management memory regions.

    //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;
    }
    

    And the caller in this case will be like this:

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

    The declaration of GameInformation::SerializeToArray should also be changed to return std::vector<char> in this case.