function

Team-FLY

18.7 Socket Implementation of UICI

The first socket interface originated with 4.1cBSD UNIX in the early 1980s. In 2001, POSIX incorporated 4.3BSD sockets and an alternative, XTI. XTI (X/ Open Transport Interface) also provides a connection-oriented interface that uses TCP. XTI's lineage can be traced back to AT&T UNIX System V TLI (Transport Layer Interface). This book focuses on socket implementations . (See Stevens [115] for an in-depth discussion of XTI.)

This section introduces the main socket library functions and then implements the UICI functions in terms of sockets. Section 18.9 discusses a thread-safe version of UICI. Appendix C gives a complete unthreaded socket implementation of UICI as well as four alternative thread-safe versions. The implementations of this chapter use IPv4 (Internet Protocol version 4). The names of the libraries needed to compile the socket functions are not yet standard. Sun Solaris requires the library options -lsocket and -lnsl . Linux just needs -lnsl , and Mac OS X does not require that any extra libraries be specified. The man page for the socket functions should indicate the names of the required libraries on a particular system. If unsuccessful , the socket functions return “1 and set errno .

Table 18.2. Overview of UICI API implementation using sockets with TCP.

UICI

socket functions

action

u_open

socket

bind

listen

create communication endpoint

associate endpoint with specific port

make endpoint passive listener

u_accept

accept

accept connection request from client

u_connect

socket

connect

create communication endpoint

request connection from server

Table 18.2 shows the socket functions used to implement each of the UICI functions. The server creates a handle ( socket ), associates it with a physical location on the network ( bind ), and sets up the queue size for pending requests ( listen ). The UICI u_open function, which encapsulates these three functions, returns a file descriptor corresponding to a passive or listening socket. The server then listens for client requests ( accept ).

The client also creates a handle ( socket ) and associates this handle with the network location of the server ( connect ). The UICI u_connect function encapsulates these two functions. The server and client handles, sometimes called communication or transmission endpoints , are file descriptors. Once the client and server have established a connection, they can communicate by ordinary read and write calls.

18.7.1 The socket function

The socket function creates a communication endpoint and returns a file descriptor. The domain parameter selects the protocol family to be used. We use AF_INET , indicating IPv4. A type value of SOCK_STREAM specifies sequenced , reliable, two-way, connection-oriented byte streams and is typically implemented with TCP. A type value of SOCK_DGRAM provides connectionless communication by using unreliable messages of a fixed length and is typically implemented with UDP. (See Chapter 20.) The protocol parameter specifies the protocol to be used for a particular communication type . In most implementations, each type parameter has only one protocol available (e.g., TCP for SOCK_STREAM and UDP for SOCK_DGRAM ), so protocol is usually 0.

  SYNOPSIS  #include <sys/socket.h>   int socket(int domain, int type, int protocol);  POSIX  

If successful, socket returns a nonnegative integer corresponding to a socket file descriptor. If unsuccessful, socket returns “1 and sets errno . The following table lists the mandatory errors for socket .

errno

cause

EAFNOSUPPORT

implementation does not support specified address family

EMFILE

no more file descriptors available for process

ENFILE

no more file descriptors available for system

EPROTONOSUPPORT

protocol not supported by address family or by implementation

EPROTOTYPE

socket type not supported by protocol

Example 18.22

The following code segment sets up a socket communication endpoint for Internet communication, using a connection-oriented protocol.

 int sock; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)    perror("Failed to create socket"); 

18.7.2 The bind function

The bind function associates the handle for a socket communication endpoint with a specific logical network connection. Internet domain protocols specify the logical connection by a port number. The first parameter to bind , socket , is the file descriptor returned by a previous call to the socket function. The *address structure contains a family name and protocol-specific information. The address_len parameter is the number of bytes in the *address structure.

  SYNOPSIS  #include <sys/socket.h>   int bind(int socket, const struct sockaddr *address,            socklen_t address_len);  POSIX  

If successful, bind returns 0. If unsuccessful, bind returns “1 and sets errno . The following table lists the mandatory errors for bind that are applicable to all address families.

errno

cause

EADDRINUSE

specified address is in use

EADDRNOTAVAIL

specified address not available from local machine

EAFNOSUPPORT

invalid address for address family of specified socket

EBADF

socket parameter is not a valid file descriptor

EINVAL

socket already bound to an address, protocol does not support binding to new address, or socket has been shut down

ENOTSOCK

socket parameter does not refer to a socket

EOPNOTSUPP

socket type does not support binding to address

The Internet domain uses struct sockaddr_in for struct sockaddr . POSIX states that applications should cast struct sockaddr_in to struct sockaddr for use with socket functions. The struct sockaddr_in structure, which is defined in netinet/in.h , has at least the following members expressed in network byte order.

 sa_family_t     sin_family;   /* AF_NET */ in_port_t       sin_port;     /* port number */ struct in_addr  sin_addr;     /* IP address */ 

For Internet communication, sin_family is AF_INET and sin_port is the port number. The struct in_addr structure has a member, called s_addr , of type in_addr_t that holds the numeric value of an Internet address. A server can set the sin_addr.s_addr field to INADDR_ANY , meaning that the socket should accept connection requests on any of the host's network interfaces. Clients set the sin_addr.s_addr field to the IP address of the server host.

Example 18.23

The following code segment associates the port 8652 with a socket corresponding to the open file descriptor sock .

 struct sockaddr_in server; int sock; server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = htons((short)8652); if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1)    perror("Failed to bind the socket to port"); 

Example 18.23 uses htonl and htons to reorder the bytes of INADDR_ANY and 8652 to be in network byte order. Big-endian computers store the most significant byte first; little-endian computers store the least significant byte first. Byte ordering of integers presents a problem when machines with different endian architectures communicate, since they may misinterpret protocol information such as port numbers . Unfortunately, both architectures are common ”the SPARC architecture (developed by Sun Microsystems) uses big-endian, whereas Intel architectures use little-endian. The Internet protocols specify that big-endian should be used for network byte order , and POSIX requires that certain socket address fields be given in network byte order. The htonl function reorders a long from the host's internal order to network byte order. Similarly, htons reorders a short to network byte order. The mirror functions ntohl and ntohs reorder integers from network byte order to host order.

18.7.3 The listen function

The socket function creates a communication endpoint, and bind associates this endpoint with a particular network address. At this point, a client can use the socket to connect to a server. To use the socket to accept incoming requests, an application must put the socket into the passive state by calling the listen function.

The listen function causes the underlying system network infrastructure to allocate queues to hold pending requests. When a client makes a connection request, the client and server network subsystems exchange messages (the TCP three-way handshake ) to establish the connection. Since the server process may be busy, the host network subsystem queues the client connection requests until the server is ready to accept them. The client receives an ECONNREFUSED error if the server host refuses its connection request. The socket value is the descriptor returned by a previous call to socket , and the backlog parameter suggests a value for the maximum allowed number of pending client requests.

  SYNOPSIS  #include <sys/socket.h>   int listen(int socket, int backlog);  POSIX  

If successful, listen returns 0. If unsuccessful, listen returns “1 and sets errno . The following table lists the mandatory errors for listen .

errno

cause

EBADF

socket is not a valid file descriptor

EDESTADDRREQ

socket is not bound to a local address and protocol does not allow listening on an unbound socket

EINVAL

socket is already connected

ENOTSOCK

socket parameter does not refer to a socket

EOPNOTSUPP

socket protocol does not support listen

Traditionally, the backlog parameter has been given as 5. However, studies have shown [115] that the backlog parameter should be larger. Some systems incorporate a fudge factor in allocating queue sizes so that the actual queue size is larger than backlog . Exercise 22.14 explores the effect of backlog size on server performance.

18.7.4 Implementation of u_open

The combination of socket , bind and listen establishes a handle for the server to monitor communication requests from a well-known port. Program 18.6 shows the implementation of u_open in terms of these socket functions.

Program 18.6 u_open.c

A socket implementation of the UICI u_open .

 #include <errno.h> #include <netdb.h> #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include "uici.h" #define MAXBACKLOG 50 int u_ignore_sigpipe(void); int u_open(u_port_t port) {    int error;    struct sockaddr_in server;    int sock;    int true = 1;    if ((u_ignore_sigpipe() == -1)          ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1))       return -1;    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&true,                   sizeof(true)) == -1) {       error = errno;       while ((close(sock) == -1) && (errno == EINTR));       errno = error;       return -1;    }    server.sin_family = AF_INET;    server.sin_addr.s_addr = htonl(INADDR_ANY);    server.sin_port = htons((short)port);    if ((bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1)          (listen(sock, MAXBACKLOG) == -1)) {       error = errno;       while ((close(sock) == -1) && (errno == EINTR));       errno = error;       return -1;    }    return sock; } 

If an attempt is made to write to a pipe or socket that no process has open for reading, write generates a SIGPIPE signal in addition to returning an error and setting errno to EPIPE . As with most signals, the default action of SIGPIPE terminates the process. Under no circumstances should the action of a client cause a server to terminate. Even if the server creates a child to handle the communication, the signal can prevent a graceful termination of the child when the remote host closes the connection. The socket implementation of UICI handles this problem by calling u_ignore_sigpipe to ignore the SIGPIPE signal if the default action of this signal is in effect.

The htonl and htons functions convert the address and port number fields to network byte order. The setsockopt call with SO_REUSEADDR permits the server to be restarted immediately, using the same port. This call should be made before bind .

If setsockopt , bind or listen produces an error, u_open saves the value of errno , closes the socket file descriptor, and restores the value of errno . Even if close changes errno , we still want to return with errno reporting the error that originally caused the return.

18.7.5 The accept function

After setting up a passive listening socket ( socket , bind and listen ), the server handles incoming client connections by calling accept . The parameters of accept are similar to those of bind . However, bind expects *address to be filled in before the call, so that it knows the port and interface on which the server will accept connection requests. In contrast, accept uses *address to return information about the client making the connection. In particular, the sin_addr member of the struct sockaddr_in structure contains a member, s_addr , that holds the Internet address of the client. The value of the *address_len parameter of accept specifies the size of the buffer pointed to by address . Before the call, fill this with the size of the *address structure. After the call, *address_len contains the number of bytes of the buffer actually filled in by the accept call.

  SYNOPSIS  #include <sys/socket.h>   int accept(int socket, struct sockaddr *restrict address,              socklen_t *restrict address_len);  POSIX  

If successful, accept returns the nonnegative file descriptor corresponding to the accepted socket. If unsuccessful, accept returns “1 and sets errno . The following table lists the mandatory errors for accept .

errno

cause

EAGAIN or EWOULDBLOCK

O_NONBLOCK is set for socket file descriptor and no connections are present to be accepted

EBADF

socket parameter is not a valid file descriptor

ECONNABORTED

connection has been aborted

EINTR accept

interrupted by a signal that was caught before a valid connection arrived

EINVAL

socket is not accepting connections

EMFILE

OPEN_MAX file descriptors are currently open in calling process

ENFILE

maximum number of file descriptors in system are already open

ENOTSOCK

socket does not refer to a socket

EOPNOTSUPP

socket type of specified socket does not support the accepting of connections

Example 18.24

The following code segment illustrates how to restart accept if it is interrupted by a signal.

 int len = sizeof(struct sockaddr); int listenfd; struct sockaddr_in netclient; int retval; while (((retval =        accept(listenfd, (struct sockaddr *)(&netclient), &len)) == -1) &&       (errno == EINTR))    ; if (retval == -1)    perror("Failed to accept connection"); 

18.7.6 Implementation of u_accept

The u_accept function waits for a connection request from a client and returns a file descriptor that can be used to communicate with that client. It also fills in the name of the client host in a user -supplied buffer. The socket accept function returns information about the client in a struct sockaddr_in structure. The client's address is contained in this structure. The socket library does not have a facility to convert this binary address to a host name. UICI calls the addr2name function to do this conversion. This function takes as parameters a struct in_addr from a struct sockaddr_in , a buffer and the size of the buffer. It fills this buffer with the name of the host corresponding to the address given. The implementation of this function is discussed in Section 18.8.

Program 18.7 implements the UICI u_accept function. The socket accept call waits for a connection request and returns a communication file descriptor. If accept is interrupted by a signal, it returns “1 with errno set to EINTR . The UICI u_accept function reinitiates accept in this case. If accept is successful and the caller has furnished a hostn buffer, then u_accept calls addr2name to convert the address returned by accept to an ASCII host name.

Program 18.7 u_accept.c

A socket implementation of the UICI u_accept function .

 #include <errno.h> #include <netdb.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include "uiciname.h" int u_accept(int fd, char *hostn, int hostnsize) {    int len = sizeof(struct sockaddr);    struct sockaddr_in netclient;    int retval;    while (((retval =            accept(fd, (struct sockaddr *)(&netclient), &len)) == -1) &&           (errno == EINTR))       ;    if ((retval == -1)  (hostn == NULL)  (hostnsize <= 0))       return retval;    addr2name(netclient.sin_addr, hostn, hostnsize);    return retval; } 
Exercise 18.25

Under what circumstances does u_accept return an error caused by client behavior?

Answer:

The conditions for u_accept to return an error are the same as for accept to return an error except for interruption by a signal. The u_accept function restarts accept when it is interrupted by a signal (e.g., errno is EINTR ). The accept function may return an error for various system-dependent reasons related to insufficient resources. The accept function may also return an error if the client disconnects after the completion of the three-way handshake. A server that uses accept or u_accept should be careful not to simply exit on such an error. Even an error due to insufficient resources should not necessarily cause the server to exit, since the problem might be temporary.

18.7.7 The connect function

The client calls socket to set up a transmission endpoint and then uses connect to establish a link to the well-known port of the remote server. Fill the struct sockaddr structure as with bind .

  SYNOPSIS  #include <sys/socket.h>   int connect(int socket, const struct sockaddr *address,              socklen_t address_len);  POSIX  

If successful, connect returns 0. If unsuccessful, connect returns “1 and sets errno . The following table lists the mandatory errors for connect that are applicable to all address families.

errno

cause

EADDRNOTAVAIL

specified address is not available from local machine

EAFNOSUPPORT

specified address is not a valid address for address family of specified socket

EALREADY

connection request already in progress on socket

EBADF

socket parameter not a valid file descriptor

ECONNREFUSED

target was not listening for connections or refused connection

EINPROGRSS

O_NONBLOCK set for file descriptor of the socket and connection cannot be immediately established, so connection shall be established asynchronously

EINTR

attempt to establish connection was interrupted by delivery of a signal that was caught, so connection shall be established asynchronously

EISCONN

specified socket is connection mode and already connected

ENETUNREACH

no route to network is present

ENOTSOCK

socket parameter does not refer to a socket

EPROTOTYPE

specified address has different type than socket bound to specified peer address

ETIMEDOUT

attempt to connect timed out before connection made

18.7.8 Implementation of u_connect

Program 18.8 shows u_connect , a function that initiates a connection request to a server. The u_connect function has two parameters, a port number ( port ) and a host name ( hostn ), which together specify the server to connect to.

Program 18.8 u_connect.c

A socket implementation of the UICI u_connect function .

 #include <ctype.h> #include <errno.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/select.h> #include <sys/socket.h> #include <sys/types.h> #include "uiciname.h" #include "uici.h" int u_ignore_sigpipe(void); int u_connect(u_port_t port, char *hostn) {    int error;    int retval;    struct sockaddr_in server;    int sock;    fd_set sockset;    if (name2addr(hostn,&(server.sin_addr.s_addr)) == -1) {       errno = EINVAL;       return -1;    }    server.sin_port = htons((short)port);    server.sin_family = AF_INET;    if ((u_ignore_sigpipe() == -1)          ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1))       return -1;    if (((retval =        connect(sock, (struct sockaddr *)&server, sizeof(server))) == -1) &&        ((errno == EINTR)  (errno == EALREADY))) {          /* asynchronous */        FD_ZERO(&sockset);        FD_SET(sock, &sockset);        while (((retval = select(sock+1, NULL, &sockset, NULL, NULL)) == -1)            && (errno == EINTR)) {           FD_ZERO(&sockset);           FD_SET(sock, &sockset);        }    }    if (retval == -1) {         error = errno;         while ((close(sock) == -1) && (errno == EINTR));         errno = error;         return -1;    }    return sock; } 

The first step is to verify that hostn is a valid host name and to find the corresponding IP address using name2addr . The u_connect function stores this address in a struct sockaddr_in structure. The name2addr function, which takes a string and a pointer to in_addr_t as parameters, converts the host name stored in the string parameter into a binary address and stores this address in the location corresponding to its second parameter. Section 18.8 discusses the implementation of name2addr .

If the SIGPIPE signal has the default signal handler, u_ignore_sigpipe sets SIGPIPE to be ignored. (Otherwise, the client terminates when it tries to write after the remote end has been closed.) The u_connect function then creates a SOCK_STREAM socket. If any of these steps fails, u_connect returns an error.

The connect call can be interrupted by a signal. However, unlike other library functions that set errno to EINTR , connect should not be restarted, because the network subsystem has already initiated the TCP 3-way handshake. In this case, the connection request completes asynchronously to program execution. The application must call select or poll to detect that the descriptor is ready for writing. The UICI implementation of u_connect uses select and restarts it if interrupted by a signal.

Exercise 18.26

How would the behavior of u_connect change if

 if ((u_ignore_sigpipe() != 0)       ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1))     return -1; 

were replaced by the following?

 if (((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)     (u_ignore_sigpipe() != 0) )    return -1; 

Answer:

If u_ignore_sigpipe() fails, u_connect returns with an open file descriptor in sock . Since the calling program does not have the value of sock , this file descriptor could not be closed.

Exercise 18.27

Does u_connect ever return an error if interrupted by a signal?

Answer:

To determine the overall behavior of u_connect , we must analyze the response of each call within u_connect to a signal. The u_ignore_sigpipe code of Appendix C only contains a sigaction call, which does not return an error when interrupted by a signal. The socket call does not return an EINTR error, implying that it either restarts itself or blocks signals. Also, name2addr does not return EINTR . An arriving signal is handled, ignored or blocked and the program continues (unless of course a handler terminates the program). The connect call can return if interrupted by a signal, but the implementation then calls select to wait for asynchronous completion. The u_connect function also restarts select if it is interrupted by a signal. Thus, u_connect should never return because of interruption by a signal.

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net