Paul
Paul

Reputation: 331

Is there any way to know the amount of bytes send from the client to the server and process the recv() in networks

I am trying to build a chat application between the server and the client. My doubt is for sending information from the client or from the server I was able to handle the partial send with the help of the loop, but I am unable to find out the length of the send data bytes from the client to the server or from the server to the client, thereby having problem in creating the memory for the received bytes and printing.

My chat function code for the client:


int chat_function(int sockfd) 
{ 
    char ch;
    char *buf;
    char *newp;
    int ret_send = 0;
    int ret_recv = 0;
    int buf_size = 0; 

    while(1) { 
        printf("From client, enter the message : ");
        buf = (char *)malloc(sizeof(char));
        if (buf == NULL)
            return -1;  
        while ((ch = getchar()) != '\n') {
            buf[buf_size++] = ch;
            newp = (char *)realloc(buf, (buf_size + 1) * sizeof(char));
            if ( newp == NULL) {
                free(buf);
                return -1;
            }
            buf = newp; 
        }
        buf[buf_size] = '\0';
        ret_send = send_all(sockfd, buf, buf_size);
        if (ret_send == -1)
            error(1, errno, "error in send() function call\n");
        memset(buf, 0, buf_size);
        ret_recv = recv_all(sockfd, buf, buf_size);
        if (ret_recv == -1) {
            error(1, errno, "error in recv() function call\n");
        } else if (ret_recv == -2) {
            printf("Oops the server has closed the connection\n");
            free(buf);
            break;
        }
        printf("From Server : %s", buf); 
        if ((strncmp(buf, "exit", 4)) == 0) { 
            printf("Client Exit...\n");
            free(buf); 
            break; 
        }
        free(buf);
    } 
} 

For handling partial send:

int send_all(int sockfd, char *buf, int buf_size)
{
    int bytes_left = 0;
    size_t send_bytes = 0;

    bytes_left = buf_size
    while (1) {
        send_bytes = send(fd, buf, bytes_left, 0);
        if (send_bytes == -1)
            return -1;
        buf = buf + send_bytes;
        bytes_left = bytes_left - send_bytes;
        if (bytes_left == 0)
            break;
    }
    return 0;
}

Upvotes: 1

Views: 872

Answers (2)

m0hithreddy
m0hithreddy

Reputation: 1829

There is no such mechanism built into TCP or UDP. You need to implement your own protocol on top of it. One of the possible solutions is:

  • If the content delivered is static.

If the sending end knows the size of the data that is being delivered prior, your client and server can agree on specific terms. For example, the first four bytes sent by the server is the size of the remaining message represented in network byte order.

Server code

uint32_t n_size = htonl(size);  // Convert the data size into network byte order.

write(sockfd, &n_size, sizeof(n_size)); // Send to the client.

Client code

uint32_t n_size;

int n_read = 0;

for ( ; ; ) {

    int rd_status = read(sockfd, (void*) &n_size + n_read, sizeof(n_size) - n_read);

    if (rd_status <= 0)
        goto handle_this_case;

    n_read = n_read + rd_status;

    if (n_read == sizeof(n_size))
        break;
}

uint32_t size = ntohl(n_size);
  • If the content delivered is generated on the fly.

In this case, even the server is not aware of the size of the message. You need to build your functions for handling this case. Below I have shown a bare minimal implementation:

Client-Side:

struct data_unit
{
    void* data;
    int size;
};

struct data_storage
{
    struct data_unit unit;
    struct data_storage* next;
};

void append_data(struct data_storage* storage, struct data_unit* unit);

struct data_unit* dump_data(struct data_storage* storage);

int main()
{
    struct data_storage storage;

    struct data_unit unit;
    unit.data = malloc(MAX_SIZE);

    for ( ; ; ) {

        int rd_status = read(sockfd, unit.data, MAX_SIZE);

        if (rd_status < 0)
            goto handle_this_case;
        else if (rd_status == 0)
            break;

        unit.size = rd_status;

        append_data(&storage, &unit);
    }

    struct data_unit* t_data = dump_data(&storage);
}

Upvotes: 1

Guest
Guest

Reputation: 136

TCP is a stream protocol, meaning there are no message boundaries: it is just a full-duplex (meaning data flows in both directions at the same time, as if there were two separate lanes) more or less continuous stream of data.

UDP is a datagram protocol, and does have message boundaries. There is an ioctl (FIONREAD/SIOCINQ) that provides the length of the next datagram, but because it involves a syscall, doing that for every message you receive is going to be slow and inefficient. Instead, you normally use a buffer large enough to hold the largest acceptable message, and copy it if/when necessary. However, UDP also has no reliability guarantees, and often UDP datagrams are completely lost without any trace or discernible reason; that's just what happens.

For a chat client-server connection, you'll want to use TCP.

Since the underlying connection is just a stream of data, you need to design a protocol for the communications, so that the stream can be split into messages, with each message processed separately.

The simplest case would be to use the nul character, \0, as a message separator.

The "send" function would then look something like this:

/* Returns 0 if message successfully sent,
   nonzero errno code otherwise. */
int  send_message(int descriptor, const char *message)
{
    /* If message is NULL, we cannot use strlen(); use zero for that. */
    const size_t  message_len = (message) ? strlen(message) : 0;

    /* Temporary variables for the sending part. */
    const char        *ptr = message;
    const char *const  end = message + message_len + 1; /* Include '\0' at end */
    ssize_t            bytes;

    /* Check valid descriptor and message length. */
    if (descriptor == -1 || message_len < 1)
        return errno = EINVAL;

    /* Write loop for sending the entire message. */
    while (ptr < end) {
        bytes = write(descriptor, ptr, (size_t)(end - ptr));
        if (bytes > 0) {
            ptr += bytes;
        } else
        if (bytes != -1) {
            /* This should never happen. */
            return errno = EIO;
        } else
        if (errno != EINTR) {
            /* We do not consider EINTR an actual error; others we do. */
            return errno;
        }
    }

    return 0;
}

The above send_message() function writes the specified string, including the string terminating nul character \0, to the specified descriptor.

On the read end, we need a buffer large enough to hold at least one full message. Instead of always waiting for incoming data, we need to check if the buffer already contains a full message, and if it does, return that. Also, you do not necessarily want to always wait for an incoming message, because that would mean you cannot send two messages in a row.

So, here's my suggestion:

static int     incoming_desc = -1;
static char   *incoming_data = NULL;
static size_t  incoming_size = 0;
static char   *incoming_next = NULL;  /* First received but not handled */
static char   *incoming_ends = NULL;  /* Last received but not handled */

#define  INCOMING_CHUNK  4096

/* Receive a new message into dynamically allocated buffer,
   and return the length.  Returns 0 when no message, with errno set.
   Waits at most ms milliseconds for a new message to arrive.
   errno == EAGAIN: no message, timeout elapsed.
   errno == ECONNABORTED: other end closed the connection.
*/
size_t  get_message(char **message, size_t *size, long ms) 
{
   struct timeval  timeout;

   /* Make sure the parameters are sane. */
   if (!message || !size || ms < 0) {
       errno = EINVAL;
       return 0;
   }

   /* For this function to work like getline() and getdelim() do,
      we need to treat *message as NULL if *size == 0. */
   if (!*size)
       *message = NULL;

   timeout.tv_sec = ms / 1000;
   timeout.tv_usec = (ms % 1000) * 1000;

   /* Timeout loop. */
   while (1) {
       fd_set  readfds;
       ssize_t bytes;
       size_t  used;
       int     result;

       /* Is there a pending complete message in the buffer? */
       if (incoming_ends > incoming_next) {
           char *endmark = memchr(incoming_next, '\0', (size_t)(incoming_ends - incoming_next));
           if (endmark) {
               const size_t  len = (size_t)(endmark - incoming_next) + 1;

               /* Reallocate the message buffer, if necessary. */
               if (len > *size) {
                   char *temp = realloc(*message, len);
                   if (!temp) {
                       errno = ENOMEM;
                       return 0;
                   }
                   *message = temp;
                   *size = len;
               }

               /* Copy message, */
               memcpy(*message, incoming_next, len);

               /* and remove it from the buffer. */
               incoming_next += len;

               /* In case the other end sent just the separator, clear errno. */
               errno = 0;

               /* We return the length sans the separator. */
               return len - 1;
           }
       }

       /* Do we have time left to check for input? */
       if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0)
           break;  /* Nope. */

       /* Is incoming_desc one we can select() for? */
       if (incoming_desc < 0 || incoming_desc >= FD_SETSIZE)
           break;  /* Nope. */

       FD_ZERO(&readfds);
       FD_SET(incoming_desc, &readfds);
       result = select(incoming_desc + 1, &readfds, NULL, NULL, &timeout);
       if (result < 1)
           break;  /* Nothing interesting happened (we ignore error here). */
       if (!FD_ISSET(incoming_fd, &readfds))
           break;

       /* Number of bytes used in the buffer right now. */
       used = (size_t)(incoming_ends - incoming_data);

       /* Do we have at least INCOMING_CHUNK bytes available? */
       if (used + INCOMING_CHUNK >= incoming_size) {
           /* Nope.  Repack the incoming buffer first. */
           if (incoming_next > incoming_data) {
               const size_t  len = (size_t)(incoming_ends - incoming_next);
               if (len > 0)
                   memmove(incoming_data, incoming_next, len);
               incoming_next = incoming_data;
               incoming_ends = incoming_data + len;
           }
           /* Recalculate the number of bytes we have free now. Enough? */
           used = (size_t)(incoming_ends - incoming_data);
           if (used + INCOMING_CHUNK > incoming_size) {
               /* Grow incoming buffer. */
               const size_t  newsize = used + INCOMING_CHUNK;
               char *temp = realloc(incoming_data, newsize);
               if (!temp) {
                   errno = ENOMEM;
                   return 0;
               }
               incoming_next = temp + (size_t)(incoming_next - incoming_data);
               incoming_ends = temp + used;
               incoming_data = temp;
               incoming_size = newsize;                   
           }
       }

       /* Read more data into the buffer; up to a full buffer. */
       bytes = read(incoming_fd, incoming_ends, incoming_size - used);
       if (bytes > 0) {
           incoming_ends += bytes;
       } else
       if (bytes == 0) {
           /* Other end closed the connection.  We may have a partial message
              in the buffer, and should handle that too, but for now, we
              just error out. */
           errno = ECONNABORTED;
           return 0;
       } else
       if (bytes != -1) {
           /* Should never happen. */
           errno = EIO;
           return 0;
       } else
       if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
           /* No data yet, interrupted by signal delivery, etc. */
           continue;
       } else {
           /* errno is set to indicate which error happened. */
           return 0;
       }
    }

    /* Timeout. */
    errno = EAGAIN;
    return 0;
}

Note that get_message() works like getline(): you do e.g.

char   *msg = NULL;
size_t  size = 0;
size_t  len;

len = get_message(&msg, &size, 100); /* 100 ms = 0.1 seconds */
if (len) {
    /* msg contains a full message of len characters */
} else
if (errno == ECONNABORTED) {
    /* Other end closed the connection */
} else
if (errno != EAGAIN) {
    fprintf(stderr, "Error receiving data: %s.\n", strerror(errno));
}

Then, you can reuse the same dynamically allocated buffer by just calling e.g.

len = get_message(&msg, &size, 100); /* 100 ms = 0.1 seconds */

again.

Upvotes: 1

Related Questions