C++ – How to send and receive an array of integers from client to server with C sockets

arraysc++socketstcp

The problem: send and receive an array of integers (later floats) from client to server using TCP and the sockets C API. Must run both in Winsock and UNIX.

In the future different endianess for client/server can be handled, but now the test was made between 2 machines with same endianess (Windows client, Linux server).

I implemented the client and server, all seems to work, but the question is a doubt on how the send() (client) and recv() (server) calls handle the way my implementation is made. Or, in other words, if the approach has a flaw.

The approach was:

  1. On the client generate a vector of uint8_t according to a predefined algorithm (N sequences of values from 0 to 254). This sequence is reproduced in the server to compare
    with the incoming data (by comparing 2 vectors).

  2. On the client, send the array size

  3. On the client, send the array using a loop on the array, call send() for each element.

  4. On the server, recv() the array size.

  5. On the server, recv() the array using a loop on the array size, call recv() for each element.

To check my approach,

  1. I save the bytes received on the server to a file inside the previous recv() loop

  2. After the loop , read this file, generate another vector with the same size according to step 1), compare the 2 vectors.
    They match, using tests up 255,000,000 array elements sent and received.

Question:
One can then assume that the server recv() loop is guaranteed to match the client send() loop?

Or, in other words, that the array indices arrive in the same order?

I am following the excellent "TCP/IP Sockets in C" (Donahoo, Calvert) and on the example of echo client / server

http://cs.baylor.edu/~donahoo/practical/CSockets/

Quote:

"The bytes sent by a call to send() on one end of a connection may not all be returned by a single call to recv() on the other end."

The recv() part is handled differently in this example, a loop is made until the total number of bytes received matches the (known size) of bytes sent, according to:

while (totalBytesRcvd < echoStringLen)
{
  bytesRcvd = recv(sock, echoBuffer, RCVBUFSIZE - 1, 0))
  totalBytesRcvd += bytesRcvd;   /* Keep tally of total bytes */
}

Complete example:

http://cs.baylor.edu/~donahoo/practical/CSockets/code/TCPEchoClient.c

But this case is for one send() with multiple bytes call that might be not received all at once.

In my case there are N send calls (1 byte each) and N receive calls (1 byte each), that happen to be made in the same order.

Question:
Does the TCP/IP protocol guarantee that the multiple send calls (that have sequential time stamps)
are guaranteed to be received in order? Or time not an issue here?

Some research:

When sending an array of int over TCP, why are only the first amount correct?

Quote:

"There is nothing to guarantee how TCP will packet up the data you send to a stream – it only guarantees that it will end up in the correct order at the application level."

Some more links

How do I send an array of integers over TCP in C?

Thanks

EDIT : code edited with main() functions and usage, and variable names for clarity

Usage example: send N sequences 1 time to server at IP-address

./client -i IP-address -n N -d

Code: Client.cpp

#if defined (_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <iostream>
#include <stdint.h>
#include <vector>

const unsigned short server_port = 5000;  // server port
void client_echo_text(const char *server_ip);
void client_send_data(const char *server_ip, const uint32_t arr_size_mult);

///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////

// usage: 
// send N sequences 1 time to server at <IP adress>
// ./client -i <IP adress> -n N -d 
// same with infinite loop
// ./client -i <IP adress> -n N -l 

int main(int argc, char *argv[])
{
  char server_ip[255]; // server IP address (dotted quad)
  strcpy(server_ip, "127.0.0.1");
  uint32_t arr_size_mult = 10;

  //no arguments
  if (argc == 1)
  {
    client_send_data(server_ip, arr_size_mult);
  }

  for (int i = 1; i < argc && argv[i][0] == '-'; i++)
  {
    switch (argv[i][1])
    {
    case 'i':
      strcpy(server_ip, argv[i + 1]);
      i++;
      break;
    case 'e':
      client_echo_text(server_ip);
      exit(0);
      break;
    case 'n':
      arr_size_mult = atoi(argv[i + 1]);
      i++;
      break;
    case 'd':
      client_send_data(server_ip, arr_size_mult);
      exit(0);
      break;
    case 'l':
      while (true)
      {
        client_send_data(server_ip, arr_size_mult);
      }
      break;
    }
  }

  return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
//client_send_data
///////////////////////////////////////////////////////////////////////////////////////

void client_send_data(const char *server_ip, const uint32_t arr_size_mult)
{
  int sock;                          // socket descriptor
  struct sockaddr_in server_addr;    // server address

  //data
  const uint32_t arr_size = arr_size_mult * 255; // array size

  //construct array
  std::vector<uint8_t> val8(arr_size);
  uint8_t v8 = 0;
  for (size_t i = 0; i < arr_size; ++i)
  {
    val8[i] = v8;
    v8++;
    if (v8 == 255)
    {
      v8 = 0;
    }
  }

#if defined (_MSC_VER)
  WSADATA ws_data;
  if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
  {
    exit(1);
  }
#endif

  // create a stream socket using TCP
  if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  {
    exit(1);
  }

  // construct the server address structure
  memset(&server_addr, 0, sizeof(server_addr));          // zero out structure
  server_addr.sin_family = AF_INET;                      // internet address family
  server_addr.sin_addr.s_addr = inet_addr(server_ip);    // server IP address
  server_addr.sin_port = htons(server_port);             // server port

  // establish the connection to the server
  if (connect(sock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0)
  {
    std::cout << "connect error: " << strerror(errno) << std::endl;
    exit(1);
  }

  //send array size
  if (send(sock, (char *)&arr_size, sizeof(uint32_t), 0) != sizeof(uint32_t))
  {
    exit(1);
  }

  std::cout << "client sent array size: " << (int)arr_size << std::endl;

  //send array
  for (size_t i = 0; i < arr_size; ++i)
  {
    v8 = val8[i];
    if (send(sock, (char *)&v8, sizeof(uint8_t), 0) != sizeof(uint8_t))
    {
      exit(1);
    }
  }

  std::cout << "client sent array: " << std::endl;

#if defined (_MSC_VER)
  closesocket(sock);
  WSACleanup();
#else
  close(sock);
#endif

}

Code: Server.cpp

if defined(_MSC_VER)
#include <winsock.h>
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include <iostream>
#include <stdint.h>
#include <assert.h>
#include <vector>

const unsigned short server_port = 5000;  // server port
void server_echo_text();
void server_recv_data(bool verbose);
void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size);

///////////////////////////////////////////////////////////////////////////////////////
//main
///////////////////////////////////////////////////////////////////////////////////////

int main(int argc, char *argv[])
{
  bool verbose = false;

  //no arguments
  if (argc == 1)
  {
    server_recv_data(verbose);
  }

  for (int i = 1; i < argc && argv[i][0] == '-'; i++)
  {
    switch (argv[i][1])
    {
    case 'v':
      std::cout << "verbose mode: " << std::endl;
      verbose = true;
      break;
    case 'e':
      std::cout << "running echo server: " << std::endl;
      server_echo_text();
      exit(0);
      break;
    case 'd':
      std::cout << "running data server: " << std::endl;
      server_recv_data(verbose);
      exit(0);
      break;
    }
  }

  return 0;

}

///////////////////////////////////////////////////////////////////////////////////////
//server_recv_data
///////////////////////////////////////////////////////////////////////////////////////

void server_recv_data(bool verbose)
{
  const int MAXPENDING = 5;             // maximum outstanding connection requests
  int server_socket;                    // socket descriptor for server
  int client_socket;                    // socket descriptor for client
  sockaddr_in server_addr;              // local address
  sockaddr_in client_addr;              // client address
  int  recv_size;                       // size in bytes returned by recv() 
#if defined (_MSC_VER)
  int len_addr;                         // length of client address data structure
#else
  socklen_t len_addr;
#endif

  //data
  uint32_t arr_size = 0;
  size_t slab_size = 1;
  FILE *file;

#if defined (_MSC_VER)
  WSADATA ws_data;
  if (WSAStartup(MAKEWORD(2, 0), &ws_data) != 0)
  {
    exit(1);
  }
#endif

  // create socket for incoming connections
  if ((server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  {
    exit(1);
  }

  // construct local address structure
  memset(&server_addr, 0, sizeof(server_addr));     // zero out structure
  server_addr.sin_family = AF_INET;                 // internet address family
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // any incoming interface
  server_addr.sin_port = htons(server_port);        // local port

  // bind to the local address
  if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) < 0)
  {
    //bind error: Permission denied
    //You're probably trying to bind a port under 1024. These ports usually require root privileges to be bound.
    std::cout << "bind error: " << strerror(errno) << std::endl;
    exit(1);
  }

  // mark the socket so it will listen for incoming connections
  if (listen(server_socket, MAXPENDING) < 0)
  {
    exit(1);
  }

  for (;;) // run forever
  {
    // set length of client address structure (in-out parameter)
    len_addr = sizeof(client_addr);

    // wait for a client to connect
    if ((client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len_addr)) < 0)
    {
      exit(1);
    }

    // convert IP addresses from a dots-and-number string to a struct in_addr and back
    char *str_ip = inet_ntoa(client_addr.sin_addr);
    std::cout << "handling client " << str_ip << std::endl;

    // receive array size
    if ((recv_size = recv(client_socket, (char *)&arr_size, sizeof(uint32_t), 0)) != sizeof(uint32_t))
    {
      exit(1);
    }

    std::cout << "server received array size: " << (int)arr_size << std::endl;

    //save file
    file = fopen("file.bin", "wb");
    fwrite(&arr_size, sizeof(uint32_t), 1, file);

    //receive array
    for (size_t i = 0; i < arr_size; ++i)
    {
      uint8_t v8;
      if ((recv_size = recv(client_socket, (char *)&v8, sizeof(uint8_t), 0)) != sizeof(uint8_t))
      {
        exit(1);
      }

      //write 1 element
      fwrite(&v8, sizeof(uint8_t), slab_size, file);
    }

    fclose(file);

    std::cout << "server received array: " << std::endl;
    check_file(arr_size, verbose, slab_size);

    // close client socket
#if defined (_MSC_VER)
    closesocket(client_socket);
#else
    close(client_socket);
#endif
  }


}

///////////////////////////////////////////////////////////////////////////////////////
//check_file
///////////////////////////////////////////////////////////////////////////////////////

void check_file(const uint32_t arr_size, bool verbose, const size_t slab_size)
{
  //read file
  std::vector<uint8_t> val8(arr_size);
  std::vector<uint8_t> val8_c(arr_size);
  uint32_t arr_size_r;
  uint8_t v8;
  FILE *file;

  file = fopen("file.bin", "rb");
  fread(&arr_size_r, sizeof(uint32_t), 1, file);
  assert(arr_size_r == arr_size);

  for (size_t i = 0; i < arr_size; ++i)
  {
    fread(&v8, sizeof(uint8_t), slab_size, file);
    val8[i] = v8;
    if (verbose) std::cout << (int)val8[i] << " ";

  }
  if (verbose) std::cout << std::endl;

  fclose(file);

  //check data, define array the same as in client, compare arrays
  v8 = 0;
  for (size_t i = 0; i < arr_size; ++i)
  {
    val8_c[i] = v8;
    v8++;
    if (v8 == 255)
    {
      v8 = 0;
    }
  }

  //compare arrays
  for (size_t i = 0; i < arr_size; ++i)
  {
    if (val8_c[i] != val8[i])
    {
      std::cout << "arrays differ at: " << i << " " << (int)val8_c[i] << " " << (int)val8[i] << std::endl;
      assert(0);
    }
  }

  std::cout << "arrays match: " << (int)arr_size << " " << (int)arr_size_r << std::endl;
  std::cout << std::endl;

}

Best Solution

TCP is a streaming protocol, it guarantees the exact replication of the sent stream at receiver. So yes, ordering will match, always. The protocol stack will reorder messages if they come out of order. So if you reliably catch the beginning of the stream and the end of the stream then everything in between will come in order and in the good shape.

I am not sure though you'd ever want to send a single number and not pre-marshal them into a large buffer. You will get several orders of magnitude improvement in performance.