kmiklas
kmiklas

Reputation: 13453

How to send a 6-character c-string over a socket connection with a 1024 byte buffer, and force flush

EDIT1: Per request of John Bollinger, I've included the full client and server code below.


I am sending 4-digit penny prices over a socket connection; e.g., 43.75, 29.43, 94.75, etc. Buffer size is set to 1024 bytes. At the moment, I am converting float prices to c-strings for transmission--not optimal, but I'm working on it. By my calculations, price size is 6 bytes: 4 digits, a decimal point, and the c-string terminating character '\0'.

The problem is that, on the client side, prices are not printed until the 1024-byte buffer is full. I would like each price tick sent and handled as it comes in, and force a buffer flush, and have each tick to be handled separately. In other words, I'd like each price to be sent in a separate packet, and not buffer the 1024 bytes.

How can I force each price tick to be handled separately? Thanks for your help. Sincerely, Keith :^)

The socket connection code is taken from the following url: http://www.programminglogic.com/example-of-client-server-program-in-c-using-sockets-and-tcp/

Server-side:

char buffer[1024]; // buffer set to 1024
char res[1024] // res contains the a float rounded and converted to a string.

// res is copied into buffer, and then sent with size 6:
// 22.49\0 = 6 characters.

strcpy(buffer, res);
send(newSocket,buffer,6,0);

Client-side:

while(1) {
    recv(clientSocket, buffer, 1024, 0);
    printf("%s ",buffer);
}

I would expect the prices to print as they arrive, like so:

pickledEgg$ 49.61
pickledEgg$ 50.20
pickledEgg$ 49.97
pickledEgg$ etc..

but 1024 bytes worth of prices are being buffered:

pickledEgg$ 49.61 50.20 49.97 49.86 49.52 50.24 49.79 49.52 49.84 50.29 49.83 49.97 50.34 49.81 49.84 49.50 50.08 50.06 49.54 50.04 50.09 50.08 49.54 50.43 49.97 50.33 50.29 50.08 50.43 50.02 49.86 50.06 50.24 50.33 50.43 50.25 49.58 50.25 49.79 50.43 50.04 49.63 49.88 49.86 49.93 50.22 50.38 50.02 49.79 50.41 49.56 49.88 49.52 49.59 50.34 49.97 49.93 49.63 50.06 50.38 50.15 50.43 49.95 50.40 49.77 50.40 49.68 50.36 50.13 49.95 50.29 50.18 50.09 49.66 50.06 50.04 50.38 49.95 49.56 50.18 49.86 50.13 50.09 49.88 49.74 49.91 49.88 49.70 49.56 50.43 49.58 49.74 49.88 49.54 49.63 50.15 49.97 49.79 49.52 49.59 49.77 50.31 49.81 49.88 50.47 50.36 50.40 49.86 49.81 49.97 49.54 50.18 50.11 50.13 50.08 50.36 50.06 50.45 50.06 50.13 50.38 49.65 49.88 50.29 49.70 50.00 50.45 49.68 50.29 50.47 50.29 50.09 50.27 49.59 50.45 50.24 50.47 49.88 50.11 49.77 49.86 50.16 49.97 50.47 50.31 49.56 49.84 50.38 50.02 50.40 49.52 49.90 50.09 49.90 50.20 49.81 50.38 50.15 49.99 49.70 50.11 49.77 49.79 49.88 49.88 49.75 50.13 50.36 49.63 49.74 50.1

EDIT1: Server-side code:

/****************** SERVER CODE ****************/

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

#include <time.h>
#include <stdlib.h>
#include <math.h>

void reverse(char *str, int len)
{
    int i=0, j=len-1, temp;
    while (i<j)
    {
        temp = str[i];
        str[i] = str[j];
        str[j] = temp;
        i++; j--;
    }
}

int intToStr(int x, char str[], int d)
{
    int i = 0;
    while (x)
    {
        str[i++] = (x%10) + '0';
        x = x/10;
    }

    // If number of digits required is more, then
    // add 0s at the beginning
    while (i < d)
        str[i++] = '0';

    reverse(str, i);
    str[i] = '\0';
    return i;
}

void ftoa(float n, char *res, int afterpoint)
{
    // Extract integer part
    int ipart = (int)n;

    // Extract floating part
    float fpart = n - (float)ipart;

    // convert integer part to string
    int i = intToStr(ipart, res, 0);

    // check for display option after point
    if (afterpoint != 0)
    {
        res[i] = '.';  // add dot

        // Get the value of fraction part upto given no.
        // of points after dot. The third parameter is needed
        // to handle cases like 233.007
//        fpart = fpart * pow(10, afterpoint);
        fpart = fpart * 100;

        intToStr((int)fpart, res + i + 1, afterpoint);
    }
}
float randPrice() {
    int b;
    float d;
    b = 4950 + rand() % 100 + 1;
    d = (float)b/100;
    return d;
}
void wait() {
  int i, j, k;
    for (i=0; i<10000; ++i) {
      for (j=0; j<10000; ++j) {
        k = i + j + i * j;
      }
    }  
}

int main(){
  int welcomeSocket, newSocket;
  char buffer[1024];
  struct sockaddr_in serverAddr;
  struct sockaddr_storage serverStorage;
  socklen_t addr_size;

  char res[1024];
  float n;

  srand(time(NULL));

  /*---- Create the socket. The three arguments are: ----*/
  /* 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) */
  welcomeSocket = socket(PF_INET, SOCK_STREAM, 0);

  /*---- Configure settings of the server address struct ----*/
  /* Address family = Internet */
  serverAddr.sin_family = AF_INET;
  /* Set port number, using htons function to use proper byte order */
  serverAddr.sin_port = htons(7891);
  /* Set IP address to localhost */
  serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  /* Set all bits of the padding field to 0 */
  memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);  

  /*---- Bind the address struct to the socket ----*/
  bind(welcomeSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));

  /*---- Listen on the socket, with 5 max connection requests queued ----*/
  if(listen(welcomeSocket,5)==0)
    printf("Listening\n");
  else
    printf("Error\n");

  /*---- Accept call creates a new socket for the incoming connection ----*/
  addr_size = sizeof serverStorage;
  newSocket = accept(welcomeSocket, (struct sockaddr *) &serverStorage, &addr_size);

  /*---- Send prices to the socket of the incoming connection ----*/

 while(1) {
    n = randPrice(); // Get a random, float price
    ftoa(n, res, 2); // Convert price to string
    strcpy(buffer, res); // copy to buffer
    send(newSocket,buffer,6,0); // send buffer
    wait();
  }

  return 0;
}

Client-side code:

/****************** CLIENT CODE ****************/

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

void wait() {
  int i, j, k;
    for (i=0; i<10000; ++i) {
      for (j=0; j<10000; ++j) {
        k = i + j + i * j;
      }
    }  
}

int main(){
  int clientSocket;
  char buffer[1024];
  struct sockaddr_in serverAddr;
  socklen_t addr_size;

  /*---- Create the socket. The three arguments are: ----*/
  /* 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) */
  clientSocket = socket(PF_INET, SOCK_STREAM, 0);

  /*---- Configure settings of the server address struct ----*/
  /* Address family = Internet */
  serverAddr.sin_family = AF_INET;
  /* Set port number, using htons function to use proper byte order */
  serverAddr.sin_port = htons(7891);
  /* Set IP address to localhost */
  serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  /* Set all bits of the padding field to 0 */
  memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);  

  /*---- Connect the socket to the server using the address struct ----*/
  addr_size = sizeof serverAddr;
  connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);

  /*---- Read the message from the server into the buffer ----*/

  int r = 0;

  while(1) {
    r = recv(clientSocket, buffer, 1024, 0);
    printf("recv value: %i\n", r);
    printf("%s ",buffer);
    wait();
  }
  return 0;
}

Upvotes: 1

Views: 3623

Answers (2)

user4581301
user4581301

Reputation: 33982

My apologies for lack of clarity in my comment.

It is impossible to reliably separate data based solely on the packet in which it arrived. Disabling Nagle's Algorithm with TCP_NODELAY may greatly improve the likelihood of getting the desired behaviour but nothing can guarantee it.

For example:

Message A is written and sent immediately
Message B is written and sent immediately
Message A is delayed on the network (too many possible reasons to list)
Message B arrives at receiver
Message A arrives at receiver  
Receiver makes Messages A and B available

recv will read everything from the buffer, Message A and Message B, up to the maximum number of bytes requested. Without some method of telling Message A from Message B, you cannot proceed.

OK, but you know the length of Message A and Message B, 6 bytes, so you simply recv 6 bytes. Right?

Almost. For a number of reasons, the sender may not be able to send the whole message in one packet and a recv for 6 bytes only returns, say, 2.

The only way to be sure, other than nuking the site from orbit, is to loop on recv until all 6 bytes have been read.

bool receiveAll(int sock, 
                char * bufp, 
                size_t len)
{
    int result;
    size_t offset = 0;

    while (len > 0)
    { // loop until we have all of our data
        result = recv(sock, &bufp[offset], len, 0);
        if (result < 0)
        { // Socket is in a bad state 
            // handle error
            return false;
        }
        else if (result == 0)
        { // socket closed
            return false;
        }
        len -= result;
        offset += result;
    }
    return true;
}

Usage:

while(receiveAll(clientSocket, buffer 6)) {
    printf("%s ",buffer);
}

This will keep looping until the socket is closed or an error forces the loop to exit. No waiting is required, recv waits for you.

What it doesn't have is a good mechanism for a polite shutdown of the client while the server is still running.

This allows the sender to remain stupid. Otherwise the sender needs to do something similar making sure that it only ever sends full messages, and no messages ever straddle multiple packets. This is significantly more effort to get right than the loop in the receiveAll function. See Akash Rawal's answer for hints on how to do this.

Upvotes: 3

Akash Rawal
Akash Rawal

Reputation: 144

It is recv() that is buffering 1024 bytes.

You have 2 options:

  1. Read character-by-character (buffer size = 1). Inefficient but simple.

  2. Set O_NONBLOCK using fcntl() on client side and use select() to wait till there is data to read and then call recv(). Complex, you could get any number of data or even partial data, but it is going to be efficient.

Upvotes: 3

Related Questions