Socket Programming using TCP in C

  • Post author:
  • Post last modified:February 28, 2024
  • Reading time:30 mins read

Socket programming involves development of programs for communication between processes running on remote hosts connected over the network. The Transmission Control Protocol (TCP) is for connection-oriented reliable communication between processes running on hosts over the network.

1.0 Client-server model

Client Server System

Client server model is a software architecture paradigm prevalent in distributed applications. A server has information resources and processes that provide answers to queries and other services to remote clients over the network. Some of the examples of these services are booking an airline ticket and/or a room in a hotel, sending a file to a client, sending a mail message to another person (client), etc. As host computers have a loopback network interface and processes on a host can also use network protocols for communication among themselves, a client can also reside on the same host as the server. In this post we will look at the Transmission Control Protocol (TCP) for full-duplex communication between a server and its clients.

2.0 Network addresses and ports

How does a client identify the server with which it wants to communicate? A client needs to know two things about the server – the Internet Protocol (IP) address of the host computer on which the server resides and the port number on that host for the server process. The IP address can be a traditional IPv4 32 bit address written, for example, as 203.0.113.10, where each number represents the contents of a byte. Or, the IP address can, alternately, be a 128-bit newer IPv6 address, expressed, for example, as 2001:db8::1:2:3:4, where colons are used as punctuation to improve readability and each character is a hexadecimal digit representing four bits of address. Leading zeros are omitted which means that, as an example, db8 is actually 0db8 and 1 is 0001. Missing digits between two adjacent colons are zeroes. In order to have unambiguous representation, there can be only one occurrence of adjacent colons in an IPv6 address.

An IP address identifies a host on the network. Next, the client needs to identify the server process on that host. More specifically, the client needs to know the port number for the server process. A process, wishing to communicate over the network is linked to a unique port number on the host on which it is running. A port number is a 16 bit integer. Port numbers less than 1024 are well known port numbers and identify popular services. For example, port numbers 25 and 587 are for SMTP, 80 is for HTTP, 443 is for HTTPS, port 993 is for IMAPS, etc. A list of reserved port numbers is available in the file, /etc/services.

3.0 Sockets

The client and server processes communicate over sockets. A socket is a network communication end point at a host. A socket is initialized with the socket system call. After initialization, it is built up as a network communication end point using calls like bind, listen, connect, accept, etc. Once a socket is fully built up as a network communication endpoint, we can use the socket id like a file descriptor in read and write system calls. Then, there are specialized calls like send, recv, sendto and recvfrom, that only work for sockets.

4.0 System calls and library functions

4.1 getaddrinfo, freeaddrinfo, gai_strerror

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo (const char *node, const char *service,
                 const struct addrinfo *hints,
                 struct addrinfo **res);

void freeaddrinfo (struct addrinfo *res);

const char *gai_strerror (int errcode);

Where, the struct addrinfo is,

struct addrinfo { 
    int              ai_flags;  
    int              ai_family; 
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};         

The getaddrinfo function translates a host name and a service into a struct addrinfo which contains an Internet address that can be passed as a parameter in bind and connect calls. The host name, node could be a domain name or an IP address. If node is NULL and AI_PASSIVE flag in ai_flags member of hints is set, the returned socket addresses would be suitable for use in the bind call for a socket that will accept connections from clients. The service is the name of a service from the /etc/services file or simply a port number passed as a string. The data element address family specifies the network address family. The values are AF_INET for IPv4, AF_INET6 for IPv6 and AF_UNSPEC for unspecified address family, which is the case when system may use either AF_INET or AF_INET6. Then, there are two types of sockets, stream sockets (SOCK_STREAM) and datagram sockets (SOCK_DGRAM). Stream sockets provide reliable, error-free, full duplex connected communication streams, guaranteeing delivery of packets in sequential chronological order. Datagram sockets provide connection-less communication where packets may arrive at the destination out of order. That is, a later packet might arrive before an earlier packet. Or, a packet might get lost in transit. However, if a packet arrives, it is guaranteed to be error-free.

The argument, hints in the getaddrinfo function is used for specifying the desired address family and the socket type. These are specified in the ai_family and ai_socktype members of hints. Also, if getaddrinfo is being called from a server, the AI_PASSIVE flag in ai_flags member of hints must be set so that the returned address can be used in an accept call. However, if the call is from a client, AI_PASSIVE flag should be clear which will result in socket addresses that will be suitable for use in connect, sendto and sendmsg calls.

getaddrinfo makes a linked list of struct addrinfo based on hints and returns a pointer to the start of the list in location pointed by res. The calling program can scan through this linked list and select an address.

getaddrinfo returns 0 on success. If the return value is non-zero, the error can be printed by using the gai_strerror function. The return value of getaddrinfo needs to be passed to the gai_strerror function and the latter prints the error string. The freeaddrinfo function frees the memory occupied by the linked list pointed by res.

4.2 socket

#include <sys/types.h>
#include <sys/socket.h>

int socket (int domain, int type, int protocol);

The socket system call creates a communication endpoint and returns a descriptor which can be used to refer to the socket in further calls. The first parameter, domain can be either AF_INET for IPv4 or AF_INET6 for IPv6 protocol family. There are other protocol families, but they are outside the scope of this tutorial. The second parameter, type indicates the socket type which could be SOCK_STREAM, for stream sockets which provide sequenced, reliable, full duplex, connection-based byte streams, or SOCK_DGRAM, for datagram sockets which provide connection-less unreliable delivery of datagrams. Stream sockets use the underlying Transmission Control Protocol (TCP), whereas the datagram sockets use the User Datagram Protocol (UDP). There are more socket types but outside the scope of this post. The last parameter, protocol specifies a particular protocol to be used with the socket. In most cases, there is one protocol for a given socket type and hence protocol may be specified as 0. However, in some cases, there might be multiple protocols and in that case a protocol has to be specified. However, as shown in the example at the end of this post, the values returned by the getaddrinfo function are passed to the socket system call.

The socket call returns the file descriptor for the socket on success. If the call fails, -1 is returned and errno is set accordingly.

4.3 getsockopt, setsockopt

#include <sys/types.h>
#include <sys/socket.h>

int getsockopt (int sockfd, int level, int optname,
                void *optval, socklen_t *optlen);
int setsockopt (int sockfd, int level, int optname,
                const void *optval, socklen_t optlen);

getsockopt and setsockopt are for query and setting of options associated with a socket respectively. The socket is identified by the descriptor, sockfd. The level needs to be SOL_SOCKET for manipulating options at the socket API level. The next three parameters are for the option name, option value and the length of option value. The socket options can be listed with the man 7 socket command.

4.4 bind

#include <sys/types.h>
#include <sys/socket.h>

int bind (int sockfd, const struct sockaddr *addr,
          socklen_t addrlen);

The bind system call assigns a network address to a socket. sockfd is the socket file descriptor, whereas the pointer addr and the length of the address are the values obtained from the getaddrinfo function. bind returns 0 on success and -1 on error.

4.5 listen

#include <sys/types.h>
#include <sys/socket.h>

int listen (int sockfd, int backlog);

listen marks a socket identified by the descriptor, sockfd as a passive socket. This means that it will accept connections using the accept system call. backlog is the maximum number of connection request that can be there in the queue. listen returns 0 on success. If there is an error, -1 is returned and errno is set accordingly.

4.6 accept

#include <sys/types.h>
#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *addr, socklen_t *addrlen);

The accept system call takes the first connect request from the queue on a listening socket and creates a connection with the client that sent the request. accept takes in three parameters. The first, sockfd is the listening socket file descriptor. The second parameter, addr is pointer to a buffer for the client socket address. The client address is filled in by the accept call. The third parameter, addrlen is the length of the addr buffer, which is modified by accept to contain the length of data written at addr. accept returns a new socket file descriptor for further communication (send and recv calls) with the calling client. The original listening socket file descriptor is not affected and is used for accepting further connections from other clients.

If there are no connection requests and the socket has not been marked non-blocking, accept blocks till a connection request comes. If the socket is marked non-blocking and there are no connection requests, accept returns with error EAGAIN or EWOULDBLOCK.

Normally, we wish to write code that works for both IPv4 and IPv6. However, the socket address lengths are different for the two cases. So, we need to pass a buffer of adequate size in the accept call. It happens that there is struct sockaddr_storage which would do the job just fine.

// sockaddr.h
...
typedef unsigned short int sa_family_t;

/* This macro is used to declare the initial common members
   of the data types used for socket addresses, `struct sockaddr',
   `struct sockaddr_in', `struct sockaddr_un', etc.  */

#define __SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

#define __SOCKADDR_COMMON_SIZE  (sizeof (unsigned short int))

/* Size of struct sockaddr_storage.  */
#define _SS_SIZE 128

// socket.h
...
/* Structure describing a generic socket address.  */
struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];           /* Address data.  */
  };


/* Structure large enough to hold any socket address (with the historical
   exception of AF_UNIX).  */
#define __ss_aligntype  unsigned long int
#define _SS_PADSIZE \
  (_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype))

struct sockaddr_storage
  {
    __SOCKADDR_COMMON (ss_);    /* Address family, etc.  */
    char __ss_padding[_SS_PADSIZE];
    __ss_aligntype __ss_align;  /* Force desired alignment.  */
  };

//  netinet/in.h
...
/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };
...
/* Type to represent a port.  */
typedef uint16_t in_port_t;
...
/* IPv6 address */
struct in6_addr
  {
    union
      {
        uint8_t __u6_addr8[16];
#ifdef __USE_MISC
        uint16_t __u6_addr16[8];
        uint32_t __u6_addr32[4];
#endif
      } __in6_u;
#define s6_addr                 __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16              __in6_u.__u6_addr16
# define s6_addr32              __in6_u.__u6_addr32
#endif
  };
...
/* Structure describing an Internet socket address.  */
struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;                 /* Port number.  */
    struct in_addr sin_addr;            /* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                           __SOCKADDR_COMMON_SIZE -
                           sizeof (in_port_t) -
                           sizeof (struct in_addr)];
  };
...
struct sockaddr_in6
  {
    __SOCKADDR_COMMON (sin6_);
    in_port_t sin6_port;        /* Transport layer port # */
    uint32_t sin6_flowinfo;     /* IPv6 flow information */
    struct in6_addr sin6_addr;  /* IPv6 address */
    uint32_t sin6_scope_id;     /* IPv6 scope-id */
  };
#endif /* !__USE_KERNEL_IPV6_DEFS */

Instead of pointer to struct sockaddr, a pointer to struct sockaddr_storage is passed (after type casting to the former) in the accept call. The first member of struct sockaddr_storage, ss_family, is checked. If ss_family is AF_INET, the structure pointer is type cast to struct sockaddr_in pointer. Otherwise, if ss_family is AF_INET6, the structure pointer is type cast to struct sockaddr_in6 pointer. Then, further processing is done as per IPv4 or IPv6.

4.7 connect

#include <sys/types.h>
#include <sys/socket.h>

int connect (int sockfd, const struct sockaddr *addr,
             socklen_t addrlen);

The connect system call is called by a client to connect the socket identified by sockfd to the server address specified by addr. The third parameter, addrlen specifies the length of addr. If connect is successful, it returns 0. In case of failure -1 is returned and errno is set accordingly.

4.8 send

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send (int sockfd, const void *buf, size_t len, int flags);

The send call is used to send a message on a connected socket, identified by the file descriptor sockfd. The message is pointed by buf and len specifies the length of the message. The value of flags is bit-wise OR of zero or more flags, which are, MSG_CONFIRM, MSG_DONTROUTE, MSG_DONTWAIT, MSG_EOR, MSG_MORE, MSG_NOSIGNAL and MSG_OOB. MSG_CONFIRM asks the data link layer to report progress in sending the message. MSG_DONTROUTE flag indicates that message should only be sent to directly connected networks and should not be sent to a gateway. MSG_DONTWAIT is for the non-blocking sending of message. If the operation would involve blocking, an error is returned. MSG_EOR is for indicating end of record and is relevant for certain special cases. MSG_MORE indicates that there is more data to be sent. MSG_NOSIGNAL tells to not to generate SIGPIPE signal if the peer has closed the connection. However, the EPIPE error is still returned. Finally, MSG_OOB is for sending out of band data.

4.9 recv

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv (int sockfd, void *buf, size_t len, int flags);

The recv call is for receiving messages on a connected socket identified by the file descriptor sockfd. buf points to the buffer for storing the message and len is the length of the buffer. The value of flags is bit-wise OR of zero or more flags. The recv call, by default, blocks for a message. recv returns the number of bytes actually received on success. In case of error, -1 is returned and errno is set appropriately. The flags may well be 0 and not that rarely used flags are, MSG_DONTWAIT, MSG_OOB, MSG_PEEK and MSG_WAITALL. The flag, MSG_DONTWAIT, causes a non-blocking operation and if no message is available, recv returns -1 with errno set to EAGAIN or EWOULDBLOCK. The MSG_OOB flag is for receiving out of band data. With the MSG_PEEK flag, the data received is not removed from the queue and, hence, the same message would again be received in the next call. The MSG_WAITALL flag causes the call to block until the entire data requested is received. However, less data might still get returned because an error occurred or a signal got caught.

4.10 Byte order

#include <arpa/inet.h>

uint32_t htonl (uint32_t hostlong);

uint16_t htons (uint16_t hostshort);

uint32_t ntohl (uint32_t netlong);

uint16_t ntohs (uint16_t netshort);

Little-endian computers store the lower byte of numbers first, followed by the higher byte, whereas big-endian computers do just the opposite. So, if we just send integers from a little-endian to big-endian system, or vice-versa, there would be chaos. To prevent that, there is a network byte order, which is big-endian. All numbers should be transmitted on the network in the network byte order. The above functions convert long and short integers between host to network and network to host byte orders.

4.11 inet_ntop, inet_pton

#include <arpa/inet.h>

const char *inet_ntop (int af, const void *src,
                       char *dst, socklen_t size);

int inet_pton (int af, const char *src, void *dst);

The function inet_ntop converts an IPv4 or IPv6 binary host address to a printable one like 203.0.113.10 or 2001:db8::1:2:3:4. The parameter, af is either AF_INET or AF_INET6. src is a pointer to either struct in_addr for IPv4 or struct in_addr6 for IPv6. dst points to a buffer, where the printable string is stored by the function and size is the size of that buffer. The size of buffer should be INET6_ADDRSTRLEN, so that both IPv4 and IPv6 address strings can be accommodated. inet_ntop returns dst parameter on success, or NULL on failure, with errno indicating the error.

Similarly, the function, inet_pton converts a printable IPv4 or Ipv6 address string to the corresponding binary format. af is either AF_INET or AF_INET6. src points to a printable IPv4 or IPv6 address string and dst is a pointer to struct in_addr or struct in6_addr, where the result is stored. inet_pton returns 1 on success, 0 if input is not a valid IP address and -1 on error, with errno indicating the error.

5.0 An Example: Quote server and client programs

We have an example client-server system programs. Our server is a quotation server. It has a collection of quotations. Clients connect to the server to get quotations, which they can print for end-user perusal.

5.1 The server program

/* 
 *           quote_server.c: gives a quotation to clients
 *                           after receiving a message 
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <pthread.h>
#include <syslog.h>
#include <unistd.h>

#define QUOTE_SERVER_PORT     "4356"
#define BACKLOG                10
#define MAX_MESSAGE_SIZE       256
#define QUOTATION_PLEASE       1
#define YOUR_QUOTATION_PLEASE  2
#define QUIT                   0

#define MAX_CLIENTS 200

typedef enum {false, true} bool;

void error (char *msg);
void *handle_client (void *arg);

// struct for passing data to client handling thread
struct client_info {
    bool is_a_client;
    pthread_t tid;
    int accepted_sock_id;
    struct sockaddr_storage client;
};

struct client_info clients [MAX_CLIENTS];

struct message {
    long message_id;
    char buffer [MAX_MESSAGE_SIZE];
};

static char *quote [] = {
    "A meeting is an event at which minutes are kept and hours are lost.",
    "The best time to plant a tree was twenty years ago. The second best time is now.",
    "When all efforts fail, read the instructions.",
    "An expert is the one who predicts that the job will take the longest and cost the most.",
    "An expert is one who knows more and more about less and less until he knows absolutely everything about nothing.",
    "They said I was gullible and I believed them.",
    "Confidence is the feeling you have before you fully understand the situation.",
    "With word processing, any idea can be right justified, even those which can not be justified on any other ground.",
    "Ever since we had the stress management program, the production has been slipping and no one is bothered.",
    "Self deprecating humor can be disastrous if people miss the humor in it.",
    "To a quick question, give a slow answer."
};

const int num_quotes = sizeof (quote) / sizeof (char *);

pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER;

int main (int argc, char **argv)
{
    const char * const ident = "quote_server";
    pthread_attr_t thread_attr;

    for (int i = 0; i < MAX_CLIENTS; i++)
        clients [i].is_a_client = false;

    openlog (ident, LOG_CONS | LOG_PID | LOG_PERROR, LOG_USER);
    syslog (LOG_USER | LOG_INFO, "%s", "Hello world!");
    
    if (pthread_attr_init (&thread_attr) != 0)
        error ("pthread_attr_init");
    if (pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_DETACHED) != 0)
        error ("pthread_attr_setdetachstate");

    time_t now;

    if ((now = time (NULL)) == ((time_t) -1))
        error ("time");

    unsigned int seed = now % 47;

    srand (seed);

    struct addrinfo hints;
    memset(&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = AF_UNSPEC;    /* allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM; /* Stream socket */
    hints.ai_flags = AI_PASSIVE;    /* for wildcard IP address */

    struct addrinfo *result;
    int s; 
    if ((s = getaddrinfo (NULL, QUOTE_SERVER_PORT, &hints, &result)) != 0) {
        fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
        exit (EXIT_FAILURE);
    }

    /* Scan through the list of address structures returned by 
       getaddrinfo. Stop when the the socket and bind calls are successful. */

    int sock_fd, optval = 1;
    socklen_t length;
    struct addrinfo *rptr;
    for (rptr = result; rptr != NULL; rptr = rptr -> ai_next) {
        sock_fd = socket (rptr -> ai_family, rptr -> ai_socktype,
                       rptr -> ai_protocol);
        if (sock_fd == -1)
            continue;

        if (setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof (int)) == -1)
            error("setsockopt");

        if (bind (sock_fd, rptr -> ai_addr, rptr -> ai_addrlen) == 0)  // Success
            break;

        if (close (sock_fd) == -1)
            error ("close");
    }

    if (rptr == NULL) {               // Not successful with any address
        fprintf(stderr, "Not able to bind\n");
        exit (EXIT_FAILURE);
    }

    freeaddrinfo (result);

    // Mark socket for accepting incoming connections using accept
    if (listen (sock_fd, BACKLOG) == -1)
        error ("listen");

    socklen_t addrlen;
    int i, r;

    while (1) {
         addrlen = sizeof (struct sockaddr_storage);

         // find an empty slot for client

	 // lock mutex
	 if ((r = pthread_mutex_lock (&client_mutex)) != 0) {
	     fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	     exit (1);
         }
				     
	     for (i = 0; i < MAX_CLIENTS; i++) {
                 if (!clients [i].is_a_client)
                     clients [i].is_a_client = true;
		     break;
	     }

        // Unlock mutex
	if ((r = pthread_mutex_unlock (&client_mutex)) != 0) {
	    fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	    exit (1);
         }

         if (i == MAX_CLIENTS) {
             fprintf (stderr, "Can't handle more than %d clients. Quitting\n", MAX_CLIENTS);
	     exit (EXIT_FAILURE);
	 }

         if ((clients [i].accepted_sock_id = accept (sock_fd, (struct sockaddr *) &clients [i].client, &addrlen)) == -1) {
             if (errno == EINTR) {

	         // lock mutex
	         if ((r = pthread_mutex_lock (&client_mutex)) != 0) {
	             fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	             exit (1);
                 }

                     clients [i].is_a_client = false;

                // Unlock mutex
	        if ((r = pthread_mutex_unlock (&client_mutex)) != 0) {
	            fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	            exit (1);
                 }

                 continue;
	     }
             else
	     {
                 error ("accept");
	     }
         }
         
         if ((r = pthread_create (&clients [i].tid, NULL, handle_client, (void *) &i)) != 0) {
             fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); exit (1);
         }

    }
    syslog (LOG_USER | LOG_INFO, "%s", "Bye.");
    closelog ();

    exit (EXIT_SUCCESS);
}

void *handle_client (void *arg)
{
    struct client_info this_client;
    char str [INET6_ADDRSTRLEN];
    struct message send_message, recv_message;
    bool over = false;
    struct sockaddr_in  *ptr;
    struct sockaddr_in6  *ptr1;
    int r, i;

    i = *((int *) arg);

    // lock mutex
    if ((r = pthread_mutex_lock (&client_mutex)) != 0) {
        fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
        exit (1);
    }
				     
        this_client = clients [i];

    // Unlock mutex
    if ((r = pthread_mutex_unlock (&client_mutex)) != 0) {
        fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
        exit (1);
    }

    // print IP address of the client

    if ((this_client.client) . ss_family == AF_INET) {
        ptr = (struct sockaddr_in *) &(this_client.client);
        inet_ntop (AF_INET, &(ptr -> sin_addr), str, sizeof (str));
    }
    else if ((this_client.client) . ss_family == AF_INET6) {
        ptr1 = (struct sockaddr_in6 *) &(this_client.client);
	inet_ntop (AF_INET6, &(ptr1 -> sin6_addr), str, sizeof (str));
    }
    else
    {
        ptr = NULL;
        fprintf (stderr, "Address family is neither AF_INET nor AF_INET6\n");
    }

    if (ptr) 
        syslog (LOG_USER | LOG_INFO, "%s %s", "Connection from client", str);

    // Handle client messages
    while (!over) {
        // receive message
        if (recv (this_client.accepted_sock_id, &recv_message, sizeof (struct message), 0) <= 0) {
             over = true;

	     // lock mutex
	     if ((r = pthread_mutex_lock (&client_mutex)) != 0) {
	         fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	         exit (1);
             }

                 clients [i].is_a_client = false;

             // Unlock mutex
	     if ((r = pthread_mutex_unlock (&client_mutex)) != 0) {
	         fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	         exit (1);
             }

             break;
        }

        // process 
        switch (ntohl (recv_message.message_id)) {
            case QUOTATION_PLEASE: // choose a quote
                                   send_message.message_id = htonl (YOUR_QUOTATION_PLEASE);
                                   int i = rand () % num_quotes;
                                   strcpy (send_message.buffer, quote [i]);
                                   size_t msg_len = sizeof (long) + strlen (quote [i]) + 1;
                                   if (send (this_client.accepted_sock_id, &send_message, msg_len, 0) == -1)
                                       error ("send");
                                   break;
            case QUIT:
                       over = true;

	               // lock mutex
	               if ((r = pthread_mutex_lock (&client_mutex)) != 0) {
	                   fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	                   exit (1);
                       }
          
                           clients [i].is_a_client = false;

                       // Unlock mutex
	               if ((r = pthread_mutex_unlock (&client_mutex)) != 0) {
	                   fprintf (stderr, "Error = %d (%s)\n", r, strerror (r)); 
	                   exit (1);
                       }

                       continue;

        }
    }
    
    return (NULL);
}

void error (char *msg)
{
    perror (msg);
    exit (1);
}

5.2 The client program

/* 
 *           client.c : get a quotation from server
 *
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#define QUOTE_SERVER_PORT     "4356"
#define MAX_MESSAGE_SIZE       256
#define QUOTATION_PLEASE       1
#define YOUR_QUOTATION_PLEASE  2
#define QUIT                   0

typedef enum {false, true} bool;

int get_option ();
void error (char *msg);

struct message {
    long message_id;
    char buffer [MAX_MESSAGE_SIZE];
};

int main (int argc, char **argv)
{
    if (argc != 2) {
        fprintf (stderr, "Usage: client hostname\n");
        exit (EXIT_FAILURE);
    }

    struct addrinfo hints;
    memset(&hints, 0, sizeof (struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    struct addrinfo *result;
    int s; 
    if ((s = getaddrinfo (argv [1], QUOTE_SERVER_PORT, &hints, &result)) != 0) {
        fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
        exit (EXIT_FAILURE);
    }

    /* Scan through the list of address structures returned by 
       getaddrinfo. Stop when the the socket and connect calls are successful. */

    int sock_fd;
    socklen_t length;
    struct addrinfo *rptr;
    for (rptr = result; rptr != NULL; rptr = rptr -> ai_next) {
        sock_fd = socket (rptr -> ai_family, rptr -> ai_socktype,
                       rptr -> ai_protocol);
        if (sock_fd == -1)
            continue;

        if (connect (sock_fd, rptr -> ai_addr, rptr -> ai_addrlen) == -1) {
            if (close (sock_fd) == -1)
                error ("close");
            continue;
        }
        
        break;
    }

    if (rptr == NULL) {               // Not successful with any address
        fprintf(stderr, "Not able to connect\n");
        exit (EXIT_FAILURE);
    }

    freeaddrinfo (result);

    int option;
    struct message message;

    while (1) {
         option = get_option ();
         // send request to server
         message.message_id = htonl (option);
         if (send (sock_fd, &message, sizeof (long), MSG_NOSIGNAL) == -1)
             error ("send");

         if (option == QUIT)
             break;

         // receive response from server
         if (recv (sock_fd, &message, sizeof (struct message), 0) == -1)
             error ("recv");

         // display 
         if (ntohl (message.message_id) == YOUR_QUOTATION_PLEASE) 
             printf ("\n%s\n\n", message.buffer);

    }

    exit (EXIT_SUCCESS);
}

int get_option ()
{
    int option;

    while (1) {
        printf ("Get a Quote\n\n");
        printf ("\tNew Quote\t1\n");
        printf ("\tQuit\t\t0\n\n");
        printf ("Your option: ");
        scanf ("%d", &option);
        if (option == 0 || option == 1)
            return option;
        else
            printf ("Illegal option, try again\n\n");
    }
}

void error (char *msg)
{
    perror (msg);
    exit (1);
}

We can compile the server and client and run the server.

$ gcc quote_server.c -o quote_server -lpthread
$ gcc client.c -o client
$ ./quote_server &
[1] 23244
$ quote_server[23244]: Hello world!

Once the server is running, we can run client from different terminals on the same system or from different systems connected on the network and get quotations.

Clients communicating with server using TCP

6.0 See also

Share

Karunesh Johri

Software developer, working with C and Linux.
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments