CS 3733 Operating Systems Notes: USP Chapter 18 - Connection-Oriented Communication



The client-server model is used in many types of network communication including mail, ftp, telnet, rlogin, http, and nfs.

In this model, the server waits for requests and the client makes requests for service from the server.

Figure 18.1 on page 611 shows two clients making a request to a server through a named pipe. The pipe acts as a one-way communication channel.

Figure 18.1 (page 611): Multiple clients write requests to a shared one-way communication channel.

Over the Internet, clients and servers differentiate communication channels using port numbers. A communication endpoint is specified by a host address and a port number. Standard servers use well-known port numbers that allow clients to request services. For example:

In connectionless communication, the client makes a request to an enpoint on a server and the server can respond to the client's endpoint (that is included in the client request).

In connection-oriented communication, the client sets up a connection using the server's well-known port number and then communicates over a private communications channel as shown in Figure 18.2.

Figure 18.2 (page 613): Schematic of connection-oriented client-server communication.

Many clients can use the same communication endpoint on the server as shown in Figure 18.3.

Figure 18.3 (page 613): Many clients can request connections to the same communication endpoint.

If the server needs to handle several concurrent requests, it can do so by forking a child to handle each request that comes in.

Figure 18.4 (page 615): A parent-server strategy for client-serveri connection-oriented communication.


UICI: A simple connection-oriented client-server implementation
The server does the following:

A client does the following:
UICI Prototype Description
int u_open(u_port_t port) Creates a TCP socket bound to port and sets the socket to be passive.
Returns a file descriptor for the socket.
int u_accept(int fd, char *hostn, int hostnsize) Waits for connection request on fd.
On successful return, hostn has the first hostnsize-1 characters of the client's host name.
Returns a communication file descriptor.
int u_connect(u_port_t port, char *hostn) Initiates a connection to server on port port and host hostn.
Returns a communication file descriptor.

Table 18.1 (page 618): The UICI API. If unsuccessful, UICI functions return -1 and set errno.

The following figure shows the interaction between a simple UICI server and client.
The server handles a single request with a single response.

Figure 18.6 (page 619): A typical interaction of a UICI client and server.


Error Handling
All UICI routines return -1 on error with errno set.

Reading and writing
After a connection is set up between the client and the server, read and write can be used. It is recommended that you use r_read and r_write instead. These behave better in the presence of signals and r_write will write the number of bytes requested or return an error.
When doing network communication it is not unusual for a call to write to return with fewer bytes written than requested.


UICI Implementation of Different Server Strategies
Program 18.1 on page 621 shows a simple serial server. The copyfile function from the restart library copies everything that comes in over the network to standard output.

After the connection has been closed on the client end, the server waits for another connection request.

Program 18.2 on page 623 implements a simple parent server. The parent program forks a child to handle the communication and immediately goes back to wait for another request from a client.


UICI Clients
Program 18.3 on page 624 shows a client that can be used to communicate with these servers. It takes a host name and port number as command line arguments and copies everything from standard input to the remote host.


Compiling network code under Linux and Solaris

To compile network code under Linux, include the nsl library by putting -lnsl at the end of the compile line as in:
cc -o server server.c restart.c uici.c uiciname.c -lnsl

To compile under Solaris, you also need to include the socket library:
cc -o server server.c restart.c uici.c uiciname.c -lnsl -lsocket


Socket Implementation of UICI
The following table shows the socket function needed to implement each UICI function.
UICISocketsaction
u_opensocketcreate communication endpoint
bindassociate endpoint with a specific port
listenmake endpoint passive listener
u_acceptacceptaccept connection request from client
u_connectsocketcreate communication endpoint
connectrequest connection from server

All socket system calls return -1 and set errno.

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
int bind(int s, const struct sockaddr *address, size_t address_len);
int listen(int s, int backlog);
int accept(int s, struct sockaddr *restrict address, int *restrict address_len);

socket:
domain is AF_UNIX orAF_INET
type is SOCK_STREAM for connection-oriented TCP or SOCK_DGRAM for connectionless UDP
protocol is usually 0

bind:
s is the file descriptor returned by socket
address contains info about the family, port and machine
address_len is the size of the structure used for the address

The format of the address is determined by the address family (domain).
For AF_INET it is a struct sockaddr_in which is defined in netinet/in.h and has at least the following members in network byte order:

        sa_family_t     sin_family;
        in_port_t       sin_port;
        struct  in_addr sin_addr;

listen:
This is used to set the socket to accept incoming requests and to set the number of unaccepted outstanding requests.

The implementation of u_open is shown in Program 18.6 on page 634.

accept:
The accept call fill the second parameter with information about the remote host. You fill in the size of the buffer used for the second parameter and on return the third parameter contains the actual size needed to contain this information. The UICI name resolution routines discussed later can convert the binary values stored in the second parameter to an ASCII host name.

The implementation of u_accept is shown in Program 18.7 on page 637.
This essentially just calls accept and then calls addr2name, a UICI routine described later to convert the address of the remote host from a binary value to a readable host name.

connect:
You specify the address and port number of the remote host to connect to in the second parameter. The third parameter contains the length of the buffer that you are using for the second parameter. The UICI name resolution routines discussed later can be used to do this.

The implementation of u_connect is shown in Program 18.8 on page 639.

One complication has to do with what happens when connect is interrupted by a signal. When this happens, the connect function returns but the connection request continues asynchronously.
We use select to wait until the connection request has completed.


Host Names and IP Addresses
UICI needs to convert between host names and host addresses.
There are several ways to do this, none of which is entirely satisfatory under all circumstances.
UICI implements the following two functions to do the translation:

   int name2addr(const char *name, in_addr_t *addrp);
   void addr2name(struct in_addr addr, char *name, int namelen);
name2addr:

addr2name:

In UICI, hosts can be specified either by name or address.
An address is specified as a string in dotted-decimal notation, e.g. "129.115.28.1"
Two system routines are used to convert between dotted-decimal notation and binary address:
  #include <arpa/inet.h>

  in_addr_t inet_addr(const char *cp);
  char *inet_ntoa(const struct in_addr in);

There are several ways to convert between name and address.
We discuss only one standard way using the following routines:

  #include <netdb.h>

  struct hostent {
     char    *h_name;         /* canonical name of host */
     char    **h_aliases;     /* alias list */
     int     h_addrtype;      /* host address type */
     int     h_length;        /* length of address */
     char    **h_addr_list;   /* list of addresses */
  };

  struct hostent *gethostbyname(const char *name);
  struct hostent *gethostbyaddr(const void *addr,
                                socklen_t len, int type);
When using IPv4, the type should be AF_INET and the addr should point to a struct in_addr.

In the struct hostent structure, the h_addr_list is an array of pointers to addresses in network byte order.

Program 18.9 on page 645 gives an implementation of name2addr using gethostbyname

Program 18.10 on page 646 gives an implementation of addr2name using gethostbyaddr