Tutorial 10

In this tutorial, we are going to familiarize ourselves with inter-process communication by sockets. We will have some hands-on experience with ready to use C programs. Use Vi, Emacs or Pico for editing the files. Login to one of the following pcs:

cs1.utdallas.edu

cs2.utdallas.edu

Use PuTTY or Xmanager for logging on as you learnt in Tutorial 1.

Socket programming

A socket is an interface between an application and the network. The application creates a socket. The socket type dictates the style of communication. Socket types can be reliable vs. best effort or connection-oriented vs. connectionless. Once configured, the application can pass data to the socket for network transmission and receive data from the socket (transmitted through the network by some other host).

There are two essential type of sockets:

SOCK_STREAM

TCP sockets

reliable delivery
in-order guaranteed
connection-oriented
bidirectional

 

SOCK_DGRAM


UDP sockets
unreliable delivery
no order guarantees
no notion of “connection”
can send or receive
 

 

Creating a TCP socket in C requires calling a fixed set of functions in a fixed sequence. The following image illustrates it:

 

A socket is created by calling the C API socket.

pint s = socket(domain, type, protocol);

s: socket descriptor, an integer (like a file-handle)

 

domain: integer, communication domain, e.g., AF_INET (IPv4 protocol) typically used


type: communication type

protocol: The protocol specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0. As there is only one protocol associated with SOCK_STREAM and SOCK_DGRAM, we will use 0.
 

int hSocket; /* handle to socket */

/* make a socket */
hSocket = socket(AF_INET, SOCK_STREAM, 0);

if(hSocket == -1)
{
        printf("\nCould not make a socket\n");
        return -1;
}

 

After creation, the socket is bound to an address.

To understand addresses, ports and socket and how they fit together, we can think on an analogy:

pLike apartments and mailboxes

 

      You are the application

When we associate a socket with an address by bind() system call, it reserves the port for exclusive use by the socket. The bind function is described below:
 

pint status = bind(sockid, &addrport, size);

 

 

The addrport is a structure of type "sockaddr". This structure holds the information of the IP address and port. It is described below for internet-specific use:

pThe Internet-specific:

struct sockaddr_in {
    short                sin_family;
    u_short            sin_port;
    struct in_addr   sin_addr;
    char                 sin_zero[8];
};

sin_family = AF_INET (Communication Domain IPv4 protocol)
sin_port: port # (0-65535)
sin_addr: IP-address
sin_zero: unused

Here is a sample server code below, it has code up to the bind call:

int hServerSocket; /* handle to socket */
struct sockaddr_in Address; /* Internet socket address stuct */
int nAddressSize = sizeof(struct sockaddr_in); /* Size of sockaddr_in structure */
int nHostPort; /* The port server will bind to */

printf("\nStarting server");

printf("\nMaking socket");
/* make a socket */
hServerSocket = socket(AF_INET, SOCK_STREAM, 0);

if(hServerSocket == -1)
{
        printf("\nCould not make a socket\n");
        return -1;
}

/* fill address struct */
Address.sin_addr.s_addr = INADDR_ANY;  /*
INADDR_ANY allows us to work without knowing the IP address of the machine the program is running  on, or, in the case of a machine with multiple network interfaces, it allows our server to receive packets destined to any of the interfaces. */
Address.sin_port = htons(nHostPort); /* The function htons() converts a short variable from host byte order to network byte order as is required for sin_port field */
Address.sin_family = AF_INET; /* AF_INET represents the address family INET for Internet sockets. */

printf("\nBinding to port %d", nHostPort);

/* bind to a port */
if(bind(hServerSocket, (struct sockaddr *) &Address, sizeof(Address)) == -1)
{
        printf("\nCould not connect to host\n");
        return -1;
}

 

A connection occurs between two kinds of participants:

passive: waits for an active participant to request connection

active: initiates connection request to passive side

Once connection is established, passive and active participants are “similar”:

both can send & receive data
 

either can terminate the connection

  • Passive participant (Server)
     

step 1: listen (for incoming requests)
step 3: accept (a request)

step 4: data transfer

The accepted connection is on a new socket
The old socket continues to listen for other active participants

  • Active participant (Client)

    step 2: request & establish connection
    step 4: data transfer

Following are the functions called by the server to establish connection:

int status = listen(sock, queuelen);

status: 0 if listening, -1 if error
sock: integer, socket descriptor
queuelen: integer, # of active participants that can “wait” for a connection
listen is non-blocking: returns immediately
 

int s = accept(sock, &name, &namelen);
 

s: integer, the new socket (used for data-transfer)
sock: integer, the orig. socket (being listened on)
name: struct sockaddr, address of the active participant
namelen: sizeof(name): value/result parameter
accept is blocking: waits for connection before returning
 

Note that accept returns a new socket for each connection with client. So, at any time, the server will have one socket for listening to connection requests and for each connection with client created, there will be one new socket.

The corresponding code in our example is:

#include <sys/socket.h>

if(listen(hServerSocket, 1) == -1)
{
        printf("\nCould not listen\n");
        return -1;
}

printf("\nWaiting for a connection\n");
/* get the connected socket */
hSocket = accept(hServerSocket, (struct sockaddr *) &Address,(socklen_t *) &nAddressSize); /* socklen_t is socket address length type, defined in sys/socket.h */

 

The function called by the client to establish connection is the following:

int status = connect(sock, &name, namelen);
 

status: 0 if successful connect, -1 otherwise
sock: integer, socket to be used in connection
name: struct sockaddr: address of passive participant
namelen: integer, sizeof(name)
connect is blocking

To write a client code, we will also need a structure called "hostent":

struct hostent

This data type is used to represent an entry in the hosts database. It has the following members:
char *h_name
This is the "official" name of the host.
char **h_aliases
These are alternative names for the host, represented as a null-terminated vector of strings.
int h_addrtype
This is the host address type; in practice, its value is always either AF_INET or AF_INET6, with the latter being used for IPv6 hosts. In principle other kinds of addresses could be represented in the database as well as Internet addresses; if this were done, you might find a value in this field other than AF_INET or AF_INET6.
int h_length
This is the length, in bytes, of each address.
char **h_addr_list
This is the vector of addresses for the host. (The host might be connected to multiple networks and have different addresses on each one.) The vector is terminated by a null pointer.
char *h_addr
This is a synonym for h_addr_list[0]; in other words, it is the first host address.

We will use the following function to populate the structure:

struct hostent * gethostbyname (const char *name)

The gethostbyname function returns information about the host named name. If the lookup fails, it returns a null pointer.

Here is an example client code up to connect call:

int hSocket; /* handle to socket */
struct hostent* pHostInfo; /* a pointer to hostent structure, the structure holds info about the host we will connect to */
struct sockaddr_in Address; /* Internet socket address stuct */
long nHostAddress; /* A long type variable to hold host address */
char pBuffer[100]; /* A character buffer to send data from and receive data to */
unsigned nReadAmount; /* Amount of data read */
char strHostName[255]; /* The string holding the hostname to connect to */
int nHostPort; /* The port server will bind to */

printf("\nMaking a socket");
/* make a socket */
hSocket=socket(AF_INET, SOCK_STREAM, 0);

if(hSocket == -1)
{
        printf("\nCould not make a socket\n");
        return -1;
}

/* get IP address from name */
pHostInfo = gethostbyname(strHostName);
/* copy address into long */
memcpy(&nHostAddress, pHostInfo->h_addr, pHostInfo->h_length);

/* fill address struct */
Address.sin_addr.s_addr = nHostAddress;
Address.sin_port = htons(nHostPort); /* The function htons() converts a short variable from host byte order to network byte order as is required for sin_port field */
Address.sin_family = AF_INET; /* AF_INET represents the address family INET for Internet sockets. */

printf("\nConnecting to %s on port %d", strHostName, nHostPort);

/* connect to host */
if(connect(hSocket, (struct sockaddr*) &Address, sizeof(Address)) == -1)
{
        printf("\nCould not connect to host\n");
        return -1;
}

 

After establishing connection, data transfer starts with it. Transfer is done by the following two functions:

int count = write(sock, &buf, len);

count: # bytes transmitted (-1 if error)
buf: char[], buffer to be transmitted
len: integer, length of buffer (in bytes) to transmit

int count = read(sock, &buf, len);

count: # bytes received (-1 if error)
buf: void[], stores received bytes
len: # bytes received
Calls are blocking [returns only after data is sent (to socket buf) / received]
 

After communication is done, one should close connection by the following API:

status = close(s);

status: 0 if successful, -1 if error
s: the file descriptor (socket being closed)

Closing a socket
closes a connection (for SOCK_STREAM)
frees up the port used by the socket

A complete example

Here is a complete example for a server and a client. The client connects to the server, taking input from user it sends a sequence number to server for which it wants to know the Fibonacci number, the server calculates and replies back. Client then prints the number to the terminal. Once started, server enters an infinite loop and replies to clients requests and waits for another client. To stop the server Ctrl + C needs to be pressed. First is the server code:
 

// server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int Fibonacci(int);

int main(int argc, char* argv[])
{
        int hSocket, hServerSocket; /* handle to sockets, hSocket will hold the socket communicating with client, hServerSocket will hold the server listening socket */
        struct hostent* pHostInfo; /* holds info about a machine */
        struct sockaddr_in Address; /* Internet socket address stuct */
        char pBuffer[100]; /* A character buffer to send data from and receive data to */
        int nAddressSize = sizeof(struct sockaddr_in); /* Size of sockaddr_in structure */
        int nHostPort; /* The port server will bind to */
        int n; /* will contain the serial number for which we want to know Fibonacci number */

        if(argc < 2)
        {
                printf("\nUsage: host-port\n");
                return -1;
        }
        else
        {
                nHostPort = atoi(argv[1]); /* Function atoi converts strings to integer value e.g. the string "123" will be converted to integer value 123 */
        }

        printf("\nStarting server");

        printf("\nMaking socket");
        /* make a socket */
        hServerSocket = socket(AF_INET, SOCK_STREAM, 0);

        if(hServerSocket == -1)
        {
                printf("\nCould not make a socket\n");
                return -1;
        }

        /* fill address struct */
        Address.sin_addr.s_addr = INADDR_ANY;  /*
INADDR_ANY allows us to work without knowing the IP address of the machine the program is running  on, or, in the case of a machine with multiple network interfaces, it allows our server to receive packets destined to any of the interfaces. */
        Address.sin_port = htons(nHostPort); /* The function htons() converts a short variable from host byte order to network byte order as is required for sin_port field */
        Address.sin_family = AF_INET; /* AF_INET represents the address family INET for Internet sockets. */

        printf("\nBinding to port %d\n", nHostPort);

        /* bind to a port */
        if(bind(hServerSocket, (struct sockaddr *) &Address, sizeof(Address))  == -1)
        {
                printf("\nCould not connect to host\n");
                return -1;
        }

        printf("Opened socket as fd (%d) on port (%d) for stream i/o\n", hServerSocket, ntohs(Address.sin_port));

        printf("Server\n\
                sin_family = %d\n\
                sin_addr.s_addr = %d\n\
                sin_port = %d\n"
                , Address.sin_family
                , Address.sin_addr.s_addr
                , ntohs(Address.sin_port)
        );

        printf("\nMaking a listen queue of %d elements", 10);
        /* establish listen queue */
        if(listen(hServerSocket, 10) == -1)
        {
                printf("\nCould not listen\n");
                return -1;
        }

        while(1)
        {
                printf("\nWaiting for a connection\n");
                /* get the connected socket */
                hSocket = accept(hServerSocket, (struct sockaddr *) &Address,(socklen_t *) &nAddressSize);  /* socklen_t is socket address length type, defined in sys/socket.h */
                printf("Connected to %s:%d\n", inet_ntoa(Address.sin_addr), ntohs(Address.sin_port)); /* The inet_ntoa() function converts the Internet host address Address.sin_addr given in network byte order to a string in standard numbers-and-dots notation. */
                /* number returned by read() and write() is the number of bytes
                ** read or written, with -1 being that an error occured
                ** write what we received back to the server */
                /* read from socket into buffer */
                n = read(hSocket, pBuffer, 100);
                pBuffer[n] = '\0'; /* Append null character at the end of buffer */

                n = atoi(pBuffer); /* Function atoi converts strings to integer value e.g. the string "123" will be converted to integer value 123 */
                sprintf(pBuffer, "%d", Fibonacci(n)); /* sprintf prints to a char buffer instead of terminal, the character pointer to the buffer is the first argument */
                write(hSocket, pBuffer, strlen(pBuffer) + 1);

                printf("\nClosing the socket");
                /* close socket */
                if(close(hSocket) == -1)
                {
                        printf("\nCould not close socket\n");
                        return -1;
                }
        }
        return 0;
}

int Fibonacci(int n)
{
        int i, a, b, fib;

        if(n < 2)
                return n;
        else
        {
                a = 0;
                b = 1;
                for(i = 2; i <= n; i++)
                {
                        fib = b + a;
                        a = b;
                        b = fib;
                }
                return fib;
        }
}

 

Here is the client code:

 

// client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
        int hSocket; /* handle to socket */
        struct hostent* pHostInfo; /* holds info about a machine */
        struct sockaddr_in Address; /* Internet socket address stuct */
        long nHostAddress; /* long address of the host we want to connect to */
        char pBuffer[100]; /* Buffer from which we will send data as well as to which we will receive data */
        unsigned nReadAmount; /* Amount of data read */
        char strHostName[255]; /* Holds the hostname to which we want to connect in string format */
        int nHostPort; /* The port server will bind to */
        int n; /* The serial number of the Fibonacci number in the series which we want to know */

        if(argc < 3)
        {
                printf("\nUsage: host-name host-port\n");
                return -1;
        }
        else
        {
                strcpy(strHostName, argv[1]);
                nHostPort = atoi(argv[2]); /* Function atoi converts strings to integer value e.g. the string "123" will be converted to integer value 123 */
        }

        printf("\nMaking a socket");
        /* make a socket */
        hSocket=socket(AF_INET, SOCK_STREAM, 0);

        if(hSocket == -1)
        {
                printf("\nCould not make a socket\n");
                return -1;
        }

        /* get IP address from name */
        pHostInfo = gethostbyname(strHostName);
        /* copy address into long */
        memcpy(&nHostAddress, pHostInfo->h_addr, pHostInfo->h_length);

        /* fill address struct */
        Address.sin_addr.s_addr = nHostAddress;
        Address.sin_port = htons(nHostPort); /* The function htons() converts a short variable from host byte order to network byte order as is required for sin_port field */
        Address.sin_family = AF_INET; /* AF_INET represents the address family INET for Internet sockets. */

        printf("\nConnecting to %s on port %d", strHostName, nHostPort);

        /* connect to host */
        if(connect(hSocket, (struct sockaddr*) &Address, sizeof(Address)) == -1)
        {
                printf("\nCould not connect to host\n");
                return -1;
        }

        printf("\n\nWhich fibonacci number you need to know? ");
        scanf("%d", &n);
        sprintf(pBuffer, "%d", n); /* sprintf prints to a buffer instead of terminal, the first argument is a pointer to the buffer */
        write(hSocket, pBuffer, strlen(pBuffer));
        nReadAmount = read(hSocket, pBuffer, 100);
        printf("\nFibonacci %d: %s",n, pBuffer);

        printf("\nClosing socket\n");
        /* close socket */
        if(close(hSocket) == -1)
        {
                printf("\nCould not close socket\n");
                return -1;
        }

        return 0;
}

 

Save the server as server.c and client as client.c. Compile them with the following commands:

{cs1:~/} gcc -o server server.c -lsocket -lnsl
{cs1:~/} gcc -o client client.c -lsocket -lnsl

Open a new terminal to run the server on cs2.utdallas.edu (or the client). Run the server:

{cs2:~/} ./server 4444

Starting server
Making socket
Binding to port 4444
opened socket as fd (3) on port (4444) for stream i/o
Server
sin_family = 2
sin_addr.s_addr = 0
sin_port = 4444

Making a listen queue of 1 elements
Waiting for a connection

Run the client:

{cs1:~/TA/PrinciplesOfUnix/Tutorials/Tutorial5} ./client cs1.utdallas.edu 4444

Making a socket
Connecting to cs1.utdallas.edu on port 4444

Which fibonacci number you need to know? 10

Fibonacci 10: 55
Closing socket

After giving the input "10", server's printout will be like this:

Waiting for a connection
Connected to 129.110.97.29:57739

Closing the socket
Waiting for a connection

You can run the client few more times if you wish. To close the server press Ctrl + C.