Inter-Process Communication with Sockets

Client-Server Model

A standard model for distributed applications is the client-server model. A server is a process that is waiting to be contacted by a client process so that the server can do something or the client. A typical scenario is as follows:

Socket definition

A communication between two processes running on two computer systems can be completely specified by the association: {protocol, local-address, local-process, remote-address, remote-process} We also define a half association as either {protocol, local-address, local-process} or {protocol, remote-address, remote-process}, which specify half of a connection. This half association is also called socket, or transport address. The term socket has been popularized by the Berkeley Unix networking system, where it is "an end point of communication", which corresponds to the definition of half association.

System calls related to sockets

For a UNIX file, there are six system calls for input/output (I/O): open, create, close, read, write and lseek. All these system calls work with a file descriptor, a number corresponding to a certain file. It would be nice if the interface to the network facilities maintained the file descriptor semantics of the Unix file system, but network I/O involves more details and options than the file input/output. However, the difference is not extremely big.

This figure shows a time line of a typical scenario that takes place for a connection-oriented transfer. First the server is started, then sometimes later a client is started that connects to the server.

To do network I/O, the first thing a process must do is call the socket (see the man page for the system calls presented, e.g. man -s3n socket) system call, specifying the type of communication protocol desired.

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

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

for our lab, we will use only the internet protocols and stream socket type, so the function call will look like: sockfd = socket(AF_INET, SOCK_STREAM, 0); The socket system call returns a small integer value, similar to a file descriptor. We call this a socket descriptor, or a sockfd.

Up to now, for the association {protocol, local-address, local-process, remote-address, remote-process} we have only specified the protocol. In order to fill the local-address and local-process elements of the association we will use the bind system call.

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

int bind(int sockfd, struct sockaddr *myaddr, int addrlen);

The second argument of bind is a pointer to a protocol-specific address, and the third argument is the size of the address structure. bind tells the system "this is my address and any messages received for this address are to be given to me".

Then, the server indicates that is willing to receive connections:

 
int listen(int sockfd, int backlog); 

The backlog argument specifies how many connection requests can be queued by the system while it waits for the server to execute the accept system call.

After executing the listen system call, an actual connection is waited for by having the server execute the accept system call.

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

int accept(int sockfd, struct sockaddr *peer, int *addrlen);

accept takes the first connection request from the queue, and creates another socket with the same proprieties as sockfd. When a connection request is received and accepted, the new socket descriptor returned by accept refers to a complete association {protocol, remote-address, remote-process} with the last two fields filled with information from the client. The client's address is also set in the second (*peer) parameter together with its length, *addrlen;

A client process connects to a socket descriptor following the socket system call to establish a connection with a server:

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

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

The sockfd is a socket descriptor that was returned by the socket system call. The second and the third argument are a pointer to a socket address, and its size.

From now on, the server and the client can use the write/read system calls for file descriptors in order to communicate. Stream sockets exhibit a behaviour with the read and write system calls that differs from normal file I/O. A read or a write on a socket might input or output fewer bytes than requested, but this is not an error condition. The reason is that buffer limits might be reached for the socket in the kernel and all that is required is for the caller to invoke the read or write system call again, for the remaining bytes.

Many of the socket system calls require a pointer to a socket address structure as an argument. The definition of this structure is in

<sys/socket.h>:

struct sockaddr {
	u_short 	sa_family; /* address_family: AF_xxx value */	
	char 		sa_data[14];
};

The contents of the 14 bytes of protocol-specific address are interpreted according to the type of address. For the Internet family, the following structures are defined in <netinet/in.h>:

struct in_addr {
	u_long 		s_addr; /* 32 bit netid/hostid */
};

struct sockaddr_in {
	short 		sin_family; 		/* AF_INET */
	u_short 	sin_port; 		/* 16 bit port number, network byte ordered */
	srtuct in_addr 	sin_addr; 		/* 32 bit netid/hostid, network byte ordered */
	char 		sin_zero[8]; 		/* unused */
}

Both the client and the server close their sockets using the close function call.

Example

This is a small "Hello Word!" example, consisting of: