Sample C Client Application

C on Unix (in this case Ubuntu) is provided with an inbuilt sockets API. This example is the full program from which snippets have been taken as examples in the Sockets Programming Tutorial. The application connects to a web server and uses the HTTP 1.0 protocol to request the root page of the web site.

Sockets
Contents
Contents
Sockets
Examples
Prev

 

The Code


/***
  A sample C client application that uses the HTTP/1.0 protocol to GET
  a pre-defined page from a web server. NB. with HTTP/1.0 the server
  will break the connection after sending the requested page.

  This example program is provided to illustrate use of the generic unix
  sockets API on a best effort basis. There is no warranty to its
  functioning in any environment nor to its suitability for any express
  function. You are free to include the entirety or portions of this code
  into your own environment/application as you wish and under your
  own responsibility with regard suitability of purpose.

***/
#define server_ipaddr "127.0.0.1" 
#define server_service "80" 
#define server_data "GET / HTTP/1.0\r\n\r\n" 
#define dump_data 0                // 0 no data dump, 1 dump

#include <ctype.h> 
#include <errno.h> 
#include <netdb.h> 
#include <stdio.h> 
#include <stdarg.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/socket.h> 
#include <sys/time.h> 
#include <sys/types.h> 
#include <time.h> 
#include <unistd.h> 
#include <arpa/inet.h> 

struct mBuf {
  char mTim[12];
  char s1;
  char mId[3];
  char s2;
  char mText[105];
};

struct snys_glob {
  struct mBuf msgBuf;
  char        work[256];
};

struct snys_conn {
  char        serverName[256];
  char        serverService[6];
  struct snys_glob * pGlob;
  struct snys_buf * pSndBuf;
  struct snys_buf * pRcvBuf;
  int           fd;
  unsigned char status;
#define conn_status_open 0b10000000    // socket open
#define conn_status_conn 0b01000000    // socket connected
#define conn_status_dump 0b00000001    // dump all sent/received data
};

struct snys_buf {
  char *  pnext;
  int     pad;
  int     dlen;
  char    data[4096];
};

/***
  Write a message to standard out.
  Parameters:
    struct_glob * pointer to the global work area
    int         the message number, a skelton for which
                must exist in the array msgTab (see below)
    various     as defined in the message skeleton and according
                to the substitution parameters defined in printf().
***/
int doMessage( struct snys_glob *pGlob, int mNum, ...) {
#define msgMax 9 
  const char *msgTab[msgMax + 1];
      msgTab[0] = "doMessage() entered with invalid message number";
      msgTab[1] = "%s Task active";
      msgTab[2] = "%s request failed, errno: %d - %s";
      msgTab[3] = "Socket %d %s";
      msgTab[4] = "Connected with server: %s";
      msgTab[5] = "Sent %d of %d bytes on connection %s";
      msgTab[6] = "Received %d bytes (total %d) from %s";
      msgTab[7] = "Server at %s disconnected";
      msgTab[8] = "Receive buffer overflow, message too long";
      msgTab[9] = "Dbg: %s";

  struct timeval timVal;
  time_t timWork;

  gettimeofday(&timVal, NULL);
  timWork = timVal.tv_sec;
  strftime( pGlob->msgBuf.mTim, 10, "%T.", localtime(&timWork));
  sprintf( pGlob->msgBuf.mTim+9, "%.3d M%.2d", (int)(timVal.tv_usec/1000), mNum);
  pGlob->msgBuf.s2 = ' ';

  if ( mNum > msgMax) {
     strncpy( pGlob->msgBuf.mText, msgTab[0],
                 sizeof(pGlob->msgBuf.mText));
  } else {
     va_list( argp);
     va_start( argp, mNum);
     vsnprintf( pGlob->msgBuf.mText, sizeof(pGlob->msgBuf.mText),
                  msgTab[mNum], argp);
     va_end( argp);
  }
  puts( (const char *)&pGlob->msgBuf);
  return(0);
}

void doDump( struct snys_glob *pGlob,
                unsigned char *pData, int length) {
  void x2c( unsigned char, char *);
  unsigned char c;
  unsigned   i, x, offset=0;
  char       line_pad[8];
  char       line_out[60];
  char      *lp;
  memset( line_pad, ' ', sizeof(line_pad));

  i = 1;
  memset( line_out, ' ', 60);
  line_out[36] = '|';
  line_out[55] = '|';
  line_out[56] = '\0';
  lp = line_out;

  for ( x=1; x<=length; x++, pData++) {
     c = *pData;
     x2c( c, lp);
     if (isgraph(c))
        line_out[i+37]=(unsigned char)c;
     else
        line_out[i+37]='.';
     i++;
     lp += 2;
     if (i % 4 == 1) lp++;
     if (i % 16 == 1) {
        i = 1;
        puts( (const char *)&line_pad);
        memset(line_out,' ',60);
        line_out[36] = '|';
        line_out[55] = '|';
        line_out[56] = '\0';
        lp = line_out;
        offset += 16;
     }
  }
  if (*line_out != ' ')
     puts( (const char *)&line_pad);
}

/***
    x2c: unpack byte to 2 char hex
***/
void x2c(unsigned char Byte, char *pIndex) {
  *pIndex = (unsigned char)((Byte & 0X00F0) >>4);
  *pIndex += (*pIndex >9) ? 55 : 48;
  *(pIndex + 1) =  (unsigned char)(Byte & 0X000F);
  *(pIndex + 1) += (*(pIndex + 1) > 9) ? 55 : 48;
  return;
}

/***
  Initialization: get required memory etc. Using separate memory areas
  makes it easier to cut and paste this code into a more complex program.
  Note these areas would normally be obtained and initialized in individual
  functions e.g. getBuf(), freeBuf() etc.
***/
struct snys_conn* doInit() {
  struct snys_glob * pGlob;
  struct snys_conn *pConn;
  struct snys_buf *pBuf;
  time_t      rawtime;
  struct tm * startTime;
  char          date[16];

 // Get and Init a global work area

  if (!(pGlob = (struct snys_glob *)malloc(
                      sizeof( struct snys_glob)))) {
     printf( "Unable to obtain global work area memory, aborting!\n");
     exit( EXIT_FAILURE);
  }
  memset( pGlob, 0x0000, sizeof( struct snys_glob));
 // see doConnect for addrinfo dependency
  if ( sizeof( pGlob->work) < sizeof( struct addrinfo)) {  // should not occur
     doMessage( pGlob, 9, "global area [ work] too small for addrinfo struct");
     exit( EXIT_FAILURE);
  }

 // Get and Init a connection block

  if (!(pConn = (struct snys_conn *)malloc(sizeof( struct snys_conn)))) {
     printf( "Unable to obtain connection work area memory, aborting!\n");
     exit( EXIT_FAILURE);
  }
  memset( pConn, 0x0000, sizeof( struct snys_conn));
  pConn->pGlob = pGlob;
 // copy in pre-compile #define'd values (note: these may only be defaults)
  if ( dump_data)
     pConn->status |= conn_status_dump;         // see #define for dump_data
  strcpy( pConn->serverName, server_ipaddr);    // see #define for server_ipaddr
  strcpy( pConn->serverService, server_service);// see #define for server_service

 // Get and Init a data buffer

  if (!(pBuf = (struct snys_buf *)malloc(sizeof( struct snys_buf)))) {
     printf( "Unable to obtain data buffer memory, aborting!\n");
     exit( EXIT_FAILURE);
  }
  memset( pBuf, 0x0000, sizeof( struct snys_buf));
  strcpy( pBuf->data, server_data);             // copy in #define'd data to be sent
  pBuf->dlen = strlen( pBuf->data);             // set data length in buffer
  pConn->pSndBuf = pBuf;                        // save in snys_conn

 // Update log with date time started

  time( &rawtime);
  startTime = localtime( &rawtime);
  strftime( date, 16, "%a %d %b %G", startTime);

  doMessage( pGlob, 1, date);
  return pConn;
}

/***
  Loop on the results from getaddrinfo to find working connection to target server.
  Open the socket within this loop to allow different inet family addresses eg. ipv4
  and ipv6. Bypass this open if subsequent addresses are in the same family.
***/
void doConnect( struct snys_conn *pConn) {
  struct snys_glob *pGlob;
  struct addrinfo  *pAddrFiltr;
  struct addrinfo *pAddrList, *pAddr;
  int i = 0, rc, old_aiFamily = 0;

  pGlob = pConn->pGlob;
  memset( pGlob->work, 0x0000, sizeof( pGlob->work));// clear addrinfo area
  pAddrFiltr = (struct addrinfo *)&pGlob->work;      // cast a new pointer
  pAddrFiltr->ai_family = AF_UNSPEC;                 // Allow IPv4 or IPv6
  pAddrFiltr->ai_socktype = SOCK_STREAM;             // TCP stream socket

  rc = getaddrinfo((const char *)&pConn->serverName, (const char *)&pConn->serverService,
                      pAddrFiltr, &pAddrList);
  if ( rc < 0) {
     doMessage( pGlob, 2, "getaddrinfo", rc, gai_strerror(rc));
     return;
  }

  for (pAddr =pAddrList; (pAddr != NULL) && (!( pConn->status & conn_status_conn));
          pAddr = pAddr->ai_next) {
                                               // check for re-use of an open socket
     if ( pAddr->ai_family != old_aiFamily) {  // if prev addr family not same as current
        if (old_aiFamily != 0) {               // and not the first addrinfo struct
           close( pConn->fd);                        // close the socket
           pConn->status &= 0xFF - conn_status_open; // reset open status
           doMessage( pGlob, 3, pConn->fd, "closed");// successful close
        }
                                               // open socket for current addr family
        if (( pConn->fd = socket( pAddr->ai_family, pAddr->ai_socktype,
                                    pAddr->ai_protocol)) >= 0) {
           doMessage( pGlob, 3, pConn->fd, "open");  // successful open
           pConn->status |= conn_status_open;        // set status open
           if ( connect( pConn->fd, pAddr->ai_addr, pAddr->ai_addrlen) >= 0 ) {
              doMessage( pGlob, 4, pConn->serverName);
              pConn->status |= conn_status_conn;
           } else {
              strcpy( pGlob->work, "Connect to Host at ");
              strcat( pGlob->work, pConn->serverName);
              doMessage( pGlob, 2, pGlob->work, errno, strerror(errno));
           }
        }
     }
  }
  if (!(pConn->status & conn_status_conn)) {         // no address succeeded
     doMessage( pGlob, 9, "Could not connect, addrinfo list exhausted");
     return;
  }
  freeaddrinfo(pAddrList);                           // free addrinfo list
}

/***
  Send all the data in the send buffer associated with the
  passed connection
***/
int doSend( struct snys_conn *pConn) {
  struct snys_glob *pGlob;
  struct snys_buf *pBuf;
  int nBytes = 0;
  int nTotal;
  ssize_t rc = 0;

  pGlob = pConn->pGlob;
  pBuf = pConn->pSndBuf;
  nTotal = pBuf->dlen;

  while ( nBytes < nTotal) {
     rc = send( pConn->fd, pBuf->data + pBuf->pad, (size_t)pBuf->dlen, 0);
     if ( rc < 0) {
        doMessage( pGlob, 2, "send", errno, strerror(errno));
        return -1;
     }
     nBytes += rc;
     doMessage( pGlob, 5, nBytes, nTotal, &pConn->serverName);
     if ( pConn->status & conn_status_dump)
        doDump( pGlob, pBuf->data+pBuf->pad, rc);
     pBuf->pad += rc;
     pBuf->dlen -= rc;
  }
  return 0;
}

/***
  Receive all data from server i.e. until connection terminated (HTTP 1.0 protocol).
  Note all data is expected to fit within the available buffer.
***/
int doRecv( struct snys_conn *pConn) {
  struct snys_glob *pGlob;
  struct snys_buf *pBuf;
  ssize_t rc = 1;                                    // =1 forces entry into while loop
  char *p1, *p2;
  char needle[3] = "\x0A";

  pGlob = pConn->pGlob;
  pBuf = pConn->pRcvBuf;

  while ( rc > 0) {
     rc = recv( pConn->fd, pBuf->data + pBuf->pad, (size_t)(sizeof( pBuf->data) - pBuf->pad), 0);
     if ( rc > 0) {
        doMessage( pGlob, 6, rc, pBuf->pad + rc, &pConn->serverName);
        if ( pConn->status & conn_status_dump)
           doDump( pGlob, pBuf->data + pBuf->pad, rc );
        p1 = pBuf->data+pBuf->pad;
        while ( p2 = strstr( p1, needle)) {
           if ( p2[-1] == 0x0D)
              p2[-1] = 0x00;
           else
              p2[0] = 0x00;
           printf( "     %s\n", p1);
           p1 = p2 + 1;
           if (p1 >= pBuf->data+pBuf->pad+rc)
              break;
        }
        pBuf->pad += rc;
        if ( pBuf->pad >= sizeof( pBuf->data)) {  // check for space in the receive buffer
           rc = -1;                               // none: break out of while loop
        }
     }
  }
  pBuf->dlen = pBuf->pad;
  pBuf->pad = 0;
  if ( rc < 0) {
      if ( pBuf->dlen >= sizeof( pBuf->data))     // check for space in the receive buffer
          doMessage( pGlob, 8);                   // None issue message 8
      else                                        // other error issue message 2
          doMessage( pGlob, 2, "receive", errno, strerror(errno));
      return -1;
  } else
      doMessage( pGlob, 7, &pConn->serverName);
  pConn->status &= 0xFF - conn_status_conn;       // set status NOT connected
  return 0;
}

/***
  Call doInit() to obtain storage areas and show example socket calls (send and receive via
  calls to doSend() and doRecv() above).
***/
int main( int argc, char *argv[]) {
  struct snys_glob *pGlob;
  struct snys_conn *pConn;
  struct snys_buf *pBuf;

  pConn = doInit();                               // Initialize environment
  pGlob = pConn->pGlob;                           // set global work area pointer

 // Overrides for target server information e.g. command server_name server_service
  if ( argc > 1) {
     if ( strlen( argv[1]) > sizeof( pConn->serverName)-1) {
        doMessage( pGlob, 9, "Server name (URL) too long");
        exit ( EXIT_FAILURE);
     }
     strcpy( pConn->serverName, argv[1]);
     if (argc > 2) {
        if ( strlen( argv[2]) > sizeof( pConn->serverService)-1) {
           doMessage( pGlob, 9, "Service name too long");
           exit ( EXIT_FAILURE);
        }
        strcpy( pConn->serverService, argv[2]);
     }
  }

// Connect to the target system 

  doConnect( pConn);

// Send and receive data 

  if ( pConn->status & conn_status_conn) {    // if connected
     if ( doSend( pConn) >= 0) {              // doSend()
        pBuf = pConn->pSndBuf;                // pick up buffer addr
        pConn->pRcvBuf = pBuf;                // if that succeeded ?
        pConn->pSndBuf = 0;                   // move sndBuf to RcvBuf
        pBuf->dlen = 0;                       // and
        pBuf->pad = 0;                        //
        doRecv( pConn);                       // doRecv()
     }
     if ( pConn->status & conn_status_conn) { // if still connected
        shutdown( pConn->fd, 1);              // send shutdown
        doMessage( pGlob, 9, "shutdown sent");
        sleep(1);
     }
  }

// Termination 

  if ( pConn->status & conn_status_open) {    // if socket open
     close( pConn->fd);                       // then close it
     doMessage( pGlob, 3, pConn->fd, "closed");
  }

  if ( pConn->pSndBuf)
     free( pConn->pSndBuf);
  if ( pConn->pRcvBuf)
     free( pConn->pRcvBuf);
  free( pConn);
  free( pGlob);

  exit( EXIT_SUCCESS);
}