spkvn
spkvn

Reputation: 194

recv() char array size

I'm working on implementing a C++ client server chat program to learn more / practice socket programming. I'm using winsockV2.

Briefly, the client program connects to a server, who stores the client socket in a vector client program sends messages for the server to distribute to other clients in the vector.

The problem I think I'm running into is that the clients and server are receiving the message and storing it in a char message[256] and if the message is shorter than 256, strange chars are displayed when I std::cout << message; which I'm being told is uninitialized memory. Here's an example of the output:

k:message from client to other client╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠(■o

Is there some way of creating a character array of the size of the received message? i.e

char recvMessage[4096];
int s = recv(socket, recvMessage, sizeof(recvMessage),0);
char recvOutput[strlen(recvMessage)] = recvMessage;
std::cout << recvOutput << std::endl;

Otherwise what is your solution for recv'ing messages which you do not know the length of?

If I'm being a complete idiot, please be kind, I came from PHP. classes are below:

SVR.CPP

See receiveMessages() and distributeMessages() functions

#include "stdafx.h"
#include "svr.h"

svr::svr()
{
    //WSA Business I don't understand
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0)
    {
        /* Tell the user that we could not find a usable */
        /* Winsock DLL.                                  */
        printf("WSAStartup failed with error: %d\n", err);
    }
    //End of WSA Business

    //get addressSize
    addressSize = sizeof(address);
    //set address data members
    address.sin_family = AF_INET;
    address.sin_port = htons(444);
    address.sin_addr.s_addr = INADDR_ANY;

    //init sListen
    sListen = socket(AF_INET, SOCK_STREAM, 0);
    bind(sListen, (sockaddr*)&address, addressSize);
}

svr::~svr()
{
}

void svr::start()
{
    std::thread newConnThread(&svr::newConnection, this);
    newConnThread.join();
}

void svr::receiveMessages(int clientIndex)
{
    std::cout << "\tsvr::recv thread started for client index:" << clientIndex << std::endl;

    //create char arr
    char recvMessage[256];

    //forever
    while (true)
    {
        //receive message and input it to recvMessage char arr.
        recv(clients[clientIndex], recvMessage, sizeof(recvMessage), 0);

        //if message is not null, send out to other clients
        if (recvMessage != NULL)
        {
            std::cout << "\t\tINFO:Received message of length: " << std::strlen(recvMessage) << " size: " << sizeof(recvMessage) << " : " << recvMessage << std::endl;
            distributeMessages(recvMessage, clientIndex);
        }
    }
}

//distributes messages to all clients in vector. called by receiveMessages function, normally in rMessages thread.
void svr::distributeMessages(std::string message, int clientIndex)
{
    for (unsigned int i = 0; i < clients.size(); i++)
    {
        if (clientIndex != i)
        {
            send(clients[i], message.c_str(), message.length(), 0);
        }
        else
        {
            //would have sent to self, not useful.
        }
    }
}

//accepts new connections and adds sockets to vector.
void svr::newConnection()
{
    //mark for accept, unsure of somaxconn value;
    listen(sListen, SOMAXCONN);

    std::cout << "\tSERVER: awaiting new connections..." << std::endl;
    while (true)
    {
        //accept connection and push on to vector.
        clients.push_back(accept(sListen, (sockaddr*)&address, &addressSize));

        //responds to new clients.
        const char *message = "Hi, you've successfully connected!";

        int clientIndex = clients.size() - 1;
        int sent = send(clients[clientIndex], message, 33, 0);

        //start new receiveMessage thread
        std::thread newClient(&svr::receiveMessages, this, clientIndex);

        //detach here, let newConn thread operate without depending on receiveMessages
        newClient.detach();
    }
    std::cout << "\tSERVER: no longer listening for new connections" << std::endl;
 }

CLI.CPP

See cSend() and cRecv() functions

#include "stdafx.h"
#include "cli.h"

cli::cli(char *ip)
{
    //WSA
    {
        WORD wVersionRequested;
        WSADATA wsaData;
        int err;

        // Use the MAKEWORD(lowbyte,highbyte) macro declared in windef.h
        wVersionRequested = MAKEWORD(2, 2);

        err = WSAStartup(wVersionRequested, &wsaData);

        if (err != 0)
        {
            std::cout << "WSAStartup failed with the error: " << err;
        }
    }

    //get addressSize 
    addressSize = sizeof(address);

    //set address struct data members
    address.sin_family = AF_INET;
    address.sin_port = htons(444);

    //if ip empty, prompt user;
    if (ip == NULL)
    {
        std::string ipInput;
        std::cout << "\n\tConnect to which IP: ";
        std::cin >> ipInput;
        address.sin_addr.s_addr = inet_addr(ipInput.c_str());
    }
    else
    {
        address.sin_addr.s_addr = inet_addr(ip);
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);

    std::cout << "\n\tYour username: ";
    std::cin >> uname;
}

cli::~cli()
{
}

void cli::start()
{
    try
    {
        //hold string
        char message[33];

        std::cout << "\n\tcli::start() called";
        int conRet;

        //connects to server socket & receives a message, stores in it message variable
        conRet = connect(sock, (sockaddr*)&address, (int)addressSize);
        recv(sock, message, sizeof(message), 0);

        std::cout << "\n\tSERVER: " << message;

        //starts threads, pass this for object scope.
        std::thread sendThread(&cli::cSend, this);
        std::thread recvThread(&cli::cRecv, this);

        //this function (start) will return/end when send and recv threads end.
        sendThread.join();
        recvThread.join();
    }
    catch (std::exception e)
    {
        std::cerr << e.what() << std::endl;
    }
}

void cli::cSend()
{
    std::cout << "\n\tcli::send thread started";
    //char arr for sending str;
    std::string getLine;
    while (true)
    {
        std::cout << "\n\t" << uname << ":" << std::flush;
        //set to "" because i suspected the value remains in the string after a loop.
        std::string message = "";

        //get input, put it in message
        std::getline(std::cin, message);

        //get full message
        std::string fullMessage = uname + ":" + message;

        //get constant int, size of fullMessage
        const int charArrSize = fullMessage.length();

        std::cout << "\t\tINFO: Sending character array of length: " << charArrSize << " size: " << sizeof(fullMessage.c_str()) << " : " << fullMessage.c_str() << std::endl;

        //sends it
        send(sock, fullMessage.c_str(), charArrSize, 0);
    }
}

void cli::cRecv()
{
    std::cout << "\n\tcli::recv  thread started";

    //initialize arr to 0, will hopefully help avoid the weird chars in the cout
    char recvMessage[256]{ '\0' };

    while (true)
    {
        recv(sock, recvMessage, sizeof(recvMessage), 0);

        std::cout << "\t\tINFO:Received message of length: " << std::strlen(recvMessage) << " size: " << sizeof(recvMessage) << " : " << recvMessage << std::endl;

        std::cout << recvMessage << std::endl;
    }
}

Upvotes: 0

Views: 1512

Answers (1)

Sam Varshavchik
Sam Varshavchik

Reputation: 118425

what is your solution for recv'ing messages which you do not know the length of?

recv() tells you the length of the message it received. You don't have to wonder what it is. That's recv()'s return value.

 int s = recv(socket, recvMessage, sizeof(recvMessage),0);

See -- there you go. It's right here in front of you. It's s. Of course if there was an error s would be negative and you need to check for that. But, ignoring that little detail, your worries are over: s is the length of your message you just received.

 char recvOutput[strlen(recvMessage)] = recvMessage;

That's not going to work. What is strlen() doing here? strlen() computes the size of the string, expecting the string to be an old-fashioned, C-style character string that's terminated by a \0 byte. recv() does not terminate anything it receives with a \0 byte. Instead, it returns the actual character count.

And, besides, this won't work anyway. You can't initialize an array this way.

Your obvious intent here, apparently, is to expect to receive a text string as message. Well, since your language of choice is C++, and you tagged your question as such, the logical conclusion is that you should be using what C++ gives you to deal with text strings: the std::string class:

std::string recvOutput{recvMessage, recvMessage+s};

There you go. Mission accomplished. Since you already known the length of the received message in s, as we've determined before (and after double-checking that s is not negative), you can simply use std::string's existing constructor that initializes the new string given an iterator, or a pointer, to the start and the end of string.

When dealing with low-level operating system interfaces, like sockets, you have no choice but to use primitive data types, like plain char arrays and buffers, because that's the only thing that the operating system understands. But, with the rich set of templates and classes offered by the C++ library, your code should switch to using C++ classes and templates at the first opportunity, in order to be able to use all those resources. As such, as soon as you've determined how big is the text string recv() just came up with, just stuff it into a std::string before figuring out what to do with it.

Upvotes: 5

Related Questions