Jason Lutz Jason Lutz -4 years ago 114
C++ Question

How to write multiple lines over a socket in c++

quite new to socket programming and just had a short question, i'm trying to display multiple lines of output on my client from the server but I can't seem to get the lines to separate. Any help appreciated.

Server:

void help(int sock)
{
n = write(sock,"Commands:\n",11);
n = write(sock,"HELP -> Displays usable commands\n",34);
n = write(sock,"BROADCAST 'message' -> Sends 'message' to every user\n",54);
n = write(sock,"SEND 'user' 'message' -> Sends 'message' to 'user'\n",52);
n = write(sock,"DISPLAY -> Displays all current users\n",39);
n = write(sock,"LEAVE -> Ends the current session\n",35);
}


Client:

while(buffer[0] != 'L')
{
bzero(buffer,256);
n = read(sockfd,buffer,255);
cout << buffer << "\n";
}

Answer Source

There are three issues with your code:

  1. your calls to write() are including the null terminators of the strings. You should not be that in this situation.

  2. your read() code is ignoring the return value of read(). Just because you ask for 255 bytes does not guarantee that you will receive 255 bytes. The return value tells you how many bytes were actually received. And the bytes that you do receive are not guaranteed to be null terminated, either, so you can't write the buffer as a plain char* pointer alone to std::cout, that will make it look for a null terminator. std::cout has a write() method that you can use to specify how many chars are to be written. Use the return value of read() for that purpose.

  3. aside from that, you are assuming that call to read() will read a single complete line, so that you can check the first char in the last received buffer for the L of the final LEAVE line. That is simply not true, and you can't rely on that. TCP is a byte stream, read() is going to receive and return arbitrary amounts of data, depending on what is available in the socket's receive buffer. If you need to read line-based data, you will have to accumulate the input bytes into a growing buffer of some kind and then scan that for line breaks as new data arrives. You can then remove only completed lines from that buffer and process their content as needed.

Try something more like this instead:

int writestr(int sock, const char *str)
{
    int n, len = strlen(str);
    while (len > 0)
    {
        n = write(sock, str, len);
        if (n < 0) return n;
        str += n;
        len -= n;
    }
    return 0;
}

void help(int sock)
{
    n = writestr(sock, "Commands:\n");
    n = writestr(sock, "HELP -> Displays usable commands\n");
    n = writestr(sock, "BROADCAST 'message' -> Sends 'message' to every user\n");
    n = writestr(sock, "SEND 'user' 'message' -> Sends 'message' to 'user'\n");
    n = writestr(sock, "DISPLAY -> Displays all current users\n");
    n = writestr(sock, "LEAVE -> Ends the current session\n");
}

char buffer[256];
std::string data;
std::string::size_type pos, last_pos = 0;

while (true)
{
    n = read(sockfd, buffer, sizeof(buffer));
    if (n <= 0) break;

    std::cout.write(buffer, n);

    data.append(buffer, n);

    pos = data.find('\n', last_pos);
    if (pos != std::string::npos)
    {
        std::string line = data.substr(0, pos);
        /* if you want to support CRLF line breaks, do this instead:
        std::string::size_type len = pos;
        if ((len > 0) && (data[len-1] == '\r'))
            --len;
        std::string line = data.substr(0, len);
        */

        data.erase(0, pos+1);

        if (line.compare(0, 5, "LEAVE") == 0)
            break;

        last_pos = 0;
    }
    else
        last_pos = data.size();
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download