Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Socket programming read() is reading all of my writes()

Tags:

c

sockets

I have a client and a server. I have two read() in my client and two write() in my server code. The server sends data to the client on the first write(), the client reads and stores to a buffer but it doesn't stop reading, it keeps reading through the server's second write() because in my client i have it set up to read 255 in the stream(from my understanding). I put 255 because i don't know how long the data datasize for first write() is. How do i fix this?

Client:

n = read(sockfd,buffer,255);
if (n < 0) 
     error("ERROR reading from socket");
      printf("%s\n",buffer);

 n = read(sockfd,buffer,255);
if (n < 0) 
     error("ERROR reading from socket");
      printf("%s\n",buffer);

Server:

  n = write(newsockfd,datasize,strlen(datasize));
if (n < 0) error("ERROR writing to socket");

n = write(newsockfd,data,255);
if (n < 0) error("ERROR writing to socket");
like image 541
user2644819 Avatar asked Oct 01 '13 22:10

user2644819


2 Answers

What you are experiencing is how TCP works. If the server calls write() multiple times before the client calls read(), then read() can receive everything that was previously written, up to the maximum buffer size that you specify. TCP has no concept of message boundaries, like UDP does. There is nothing wrong with that. You just need to account for it, that's all.

If you need to know where one message ends and the next begins, then you simply need to frame your messages. There are a couple of different ways you can do that.

  1. Send the data length before sending the actual data, so the client knows how much data to read, eg:

    Server:

    int datalen = ...; // # of bytes in data
    int tmp = htonl(datalen);
    n = write(newsockfd, (char*)&tmp, sizeof(tmp));
    if (n < 0) error("ERROR writing to socket");
    n = write(newsockfd, data, datalen);
    if (n < 0) error("ERROR writing to socket");
    

    Client:

    int buflen;
    n = read(sockfd, (char*)&buflen, sizeof(buflen));
    if (n < 0) error("ERROR reading from socket");
    buflen = ntohl(buflen);
    n = read(sockfd, buffer, buflen);
    if (n < 0) error("ERROR reading from socket");
    else printf("%*.*s\n", n, n, buffer);
    
  2. wrap the data with delimiters that do not appear in the actual data, then the client can keep reading and look for those delimiters. Use whatever delimiters make sense for your data (STX/ETX, line breaks, special reserved characters, etc):

    Server:

    char delim = '\x2';
    n = write(newsockfd, &delim, 1);
    if (n < 0) error("ERROR writing to socket");
    n = write(newsockfd, data, datalen);
    if (n < 0) error("ERROR writing to socket");
    delim = '\x3';
    n = write(newsockfd, &delim, 1);
    if (n < 0) error("ERROR writing to socket");
    

    Client:

    char tmp;
    
    do
    {
        n = read(sockfd, &tmp, 1);
        if (n < 0) error("ERROR reading from socket");
    
        if (tmp != '\x2')
            continue;
    
        buflen = 0;
    
        do
        {
            n = read(sockfd, &tmp, 1);
            if (n < 0) error("ERROR reading from socket");
    
            if (tmp == '\x3')
                break;
    
            // TODO: if the buffer's capacity has been reached, either reallocate the buffer with a larger size, or fail the operation...
            buffer[buflen] = tmp;
            ++buflen;
        }
        while (1);
    
        printf("%*.*s\n", buflen, buflen, buffer);
        break;
    }
    while (1);
    
like image 150
Remy Lebeau Avatar answered Oct 04 '22 15:10

Remy Lebeau


You've got the right idea about sending the length data before sending the actual data, but you're sending datasize in the wrong format. Sending it as an ascii string means the length of datasize will vary depending on the length of data:

For instance:

  • If data is 5 bytes in length, datasize will be "5".
  • If data is 100 bytes in length, datasize will be "100".

Unfortunately when it comes to serializing data, this just won't work, datasize must always take up the same number of bytes. You need to write this into the socket as an integer, and read it again at the other end as an integer. Then write this exact number of bytes of data into the socket and read this exact number of bytes of data at the other end:

For example:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void send(int sock)
{
    const char* msg = "this is a message!";
    uint16_t len = strlen(msg); 
    uint16_t networkLen = htons(len); // convert to network byte order

    write(sock, &networkLen, sizeof(networkLen));
    write(sock, msg, len);    
}

void receive(int sock)
{
    char msg[1024];

    uint16_t networkLen;
    read(sock, &networkLen, sizeof(networkLen));

    uint16_t len = ntohs(networkLen); // convert back to host byte order
    read(sock, msg, sizeof(msg) - 1);

    msg[len] = '\0';

    printf("%u %s\n", len, msg);
}

int main(int argc, char** argv)
{
    int sockets[2];
    pipe(sockets);

    send(sockets[1]);
    receive(sockets[0]);
}
like image 43
goji Avatar answered Oct 04 '22 16:10

goji