Section 17.4. Unix Domain Sockets

   


17.4. Unix Domain Sockets

Unix domain sockets are the simplest protocol family available through the sockets API. They do not actually represent a network protocol; they can connect only to sockets on the same machine. Although this restricts their usefulness, they are used by many applications because they provide a flexible IPC mechanism. Their addresses are pathnames that are created in the file system when a socket is bound to the pathname. Socket files, which represent Unix domain addresses, can be stat() ed but cannot be opened through open(); the socket API must be used instead.

The Unix domain provides both datagram and stream interfaces. The datagram interface is rarely used and is not discussed here. The stream interface, which is discussed here, is similar to named pipes. Unix domain sockets are not identical to named pipes, however.

When multiple processes open a named pipe, any of the processes may read a message sent through the pipe by another process. Each named pipe is like a bulletin board. When a process posts a message to the board, any other process (with sufficient permission) may take the message from the board.

Unix domain sockets are connection-oriented; each connection to the socket results in a new communication channel. The server, which may be handling many simultaneous connections, has a different file descriptor for each. This property makes Unix domain sockets much better suited to many IPC tasks than are named pipes. This is the primary reason they are used by many standard Linux services, including the X Window System and the system logger.

17.4.1. Unix Domain Addresses

Addresses for Unix domain sockets are pathnames in the file system. If the file does not already exist, it is created as a socket-type file when a socket is bound to the pathname through bind(). If a file (even a socket) exists with the pathname being bound, bind() fails and returns EADDRINUSE.bind() sets the permissions of newly created socket files to 0666, as modified by the current umask.

To connect() to an existing socket, the process must have read and write permissions for the socket file.[10]

[10] For both bind() and connect(), the process must have execute permission for the directories traversed during the pathname lookup, just as with opening normal files.

Unix domain socket addresses are passed through a struct sockaddr_un structure.

 #include <sys/socket.h> #include <sys/un.h> struct sockaddr_un {     unsigned short sun_family;     /* AF_UNIX */     char sun_path[UNIX_PATH_MAX];  /* pathname */ }; 


In the Linux 2.6.7 kernel, UNIX_PATH_MAX is 108, but that may change in future versions of the Linux kernel.

The first member, sun_family, must contain AF_UNIX to indicate that the structure contains a Unix domain address. The sun_path holds the pathname to use for the connection. When the size of the address is passed to and from the socket-related system calls, the passed length should be the number of characters in the pathname plus the size of the sun_family member. The sun_path does not need to be '\0' terminated, although it usually is.

17.4.2. Waiting for a Connection

Listening for a connection to be established on a Unix domain socket follows the procedure we described earlier: Create the socket, bind() an address to the socket, tell the system to listen() for connection attempts, and then accept() the connection.

Here is a simple server that repeatedly accepts connections on a Unix domain socket (the file sample-socket in the current directory) and reads all the data available from the socket, displaying it on standard output:

  1: /* userver.c */  2:  3: /* Waits for a connection on the ./sample-socket Unix domain  4:    socket. Once a connection has been established, copy data  5:    from the socket to stdout until the other end closes the  6:    connection, and then wait for another connection to the  7:    socket. */  8:  9: #include <stdio.h> 10: #include <sys/socket.h> 11: #include <sys/un.h> 12: #include <unistd.h> 13: 14: #include "sockutil.h"         /* some utility functions */ 15: 16: int main(void) { 17:     struct sockaddr_un address; 18:     int sock, conn; 19:     size_t addrLength; 20: 21:     if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 22:         die("socket"); 23: 24:     /* Remove any preexisting socket (or other file) */ 25:     unlink("./sample-socket"); 26: 27:     address.sun_family = AF_UNIX; /* Unix domain socket */ 28:     strcpy(address.sun_path, "./sample-socket"); 29: 30:     /* The total length of the address includes the sun_family 31:        element */ 32:     addrLength = sizeof(address.sun_family) + 33:                  strlen(address.sun_path); 34: 35:     if (bind(sock, (struct sockaddr *) &address, addrLength)) 36:         die("bind"); 37: 38:     if (listen(sock, 5)) 39:         die("listen"); 40: 41:     while ((conn = accept(sock, (struct sockaddr *) &address, 42:                           &addrLength)) >= 0) { 43:         printf("---- getting data\n"); 44:         copyData(conn, 1); 45:         printf("---- done\n"); 46:         close(conn); 47:     } 48: 49:     if (conn < 0) 50:         die("accept"); 51: 52:     close(sock); 53:     return 0; 54: } 


Although this program is small, it illustrates how to write a simple server process. This server is an iterative server because it handles one client at a time. Servers may also be written as concurrent servers, which handle multiple clients simultaneously.[11]

[11] While it may seem like most real-world server programs would need to be concurrent, many of them are actually written as iterative servers. Many Web servers, for example, handle only a single connection at a time per process. To allow more clients to connect, the server is run as many individual processes. This makes the Web server much simpler to write, and if a bug causes one of those processes to terminate it affects only a single client connection.

Notice the unlink() call before the socket is bound. Because bind() fails if the socket file already exists, this allows the program to be run more than once without requiring that the socket file be manually removed.

The server code typecasts the struct sockaddr_un pointer passed to both bind() and accept() to a (struct sockaddr *). All the various socket-related system calls are prototyped as taking a pointer to struct sockaddr; the typecast keeps the compiler from complaining about pointer type mismatches.

17.4.3. Connecting to a Server

Connecting to a server through a Unix domain socket consists of creating a socket and connect() ing to the desired address. Once the socket is connected, it may be treated like any other file descriptor.

The following program connects to the same socket that the example server uses and copies its standard input to the server:

  1: /* uclient.c */  2:  3: /* Connect to the ./sample-socket Unix domain socket, copy stdin  4:    into the socket, and then exit. */  5:  6: #include <sys/socket.h>  7: #include <sys/un.h>  8: #include <unistd.h>  9: 10: #include "sockutil.h"         /* some utility functions */ 11: 12: int main(void) { 13:     struct sockaddr_un address; 14:     int sock; 15:     size_t addrLength; 16: 17:     if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) 18:         die("socket"); 19: 20:     address.sun_family = AF_UNIX; /* Unix domain socket */ 21:     strcpy(address.sun_path, "./sample-socket"); 22: 23:     /* The total length of the address includes the sun_family 24:        element */ 25:     addrLength = sizeof(address.sun_family) + 26:                  strlen(address.sun_path); 27: 28:     if (connect(sock, (struct sockaddr *) &address, addrLength)) 29:         die("connect"); 30: 31:     copyData(0, sock); 32: 33:     close(sock); 34: 35:     return 0; 36: } 


The client is not much different than the server. The only changes were replacing the bind(), listen(), accept() sequence with a single connect() call and copying a slightly different set of data.

17.4.4. Running the Unix Domain Examples

The previous two example programs, one a server and the other a client, are designed to work together. Run the server from one terminal, then run the client from another terminal (but in the same directory). As you type lines into the client, they are sent through the socket to the server. When you exit the client, the server waits for another connection. You can transmit files through the socket by redirecting the input to the client program.

17.4.5. Unnamed Unix Domain Sockets

Because Unix domain sockets have some advantages over pipes (such as being full duplex), they are often used as an IPC mechanism. To facilitate this, the socketpair() system call was introduced.

 #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sockfds[2]); 


The first three parameters are the same as those passed to socket(). The final parameter, sockfds(), is filled in by socketpair() with two file descriptors, one for each end of the socket. A sample application of socketpair() is shown on page 425.

17.4.6. Passing File Descriptors

Unix domain sockets have a unique ability: File descriptors can be passed through them. No other IPC mechanism supports this facility. It allows a process to open a file and pass the file descriptor to another possibly unrelated process. All the access checks are done when the file is opened, so the receiving process gains the same access rights to the file as the original process.

File descriptors are passed as part of a more complicated message that is sent using the sendmsg() system call and received using recvmsg().

 #include <sys/socket.h> int sendmsg(int fd, const struct msghdr * msg, unsigned int flags); int recvmsg(int fd, struct msghdr * msg, unsigned int flags); 


The fd parameter is the file descriptor through which the message is transmitted; the second parameter is a pointer to a structure describing the message. The flags are not usually used and should be set to zero for most applications. More advanced network programming books discuss the available flags [Stevens, 2004].

A message is described by the following structure:

 #include <sys/socket.h> #include <sys/un.h> struct msghdr {     void * msg_name;             /* optional address */     unsigned int msg_namelen;    /* size of msg_name */     struct iovec * msg_iov;      /* scatter/gather array */     unsigned int msg_iovlen;     /* number of elements in msg_iov */     void * msg_control;          /* ancillary data */     unsigned int msg_controllen; /* ancillary data buffer len */     int msg_flags;               /* flags on received message */ }; 


The first two members, msg_name and msg_namelen, are not used with stream protocols. Applications that send messages across stream sockets should set msg_name to NULL and msg_namelen to zero.

msg_iov and msg_iovlen describe a set of buffers that are sent or received. Scatter/gather reads and writes, as well as struct iovec, are discussed on pages 290-291. The final member of the structure, msg_flags, is not currently used and should be set to zero.

The two members we skipped over, msg_control and msg_controllen, provide the file descriptor passing ability. The msg_control member points to an array of control message headers; msg_controllen specifies how many bytes the array contains. Each control message consists of a struct cmsghdr followed by extra data.

 #include <sys/socket.h> struct cmsghdr {     unsigned int cmsg_len;     /* length of control message */     int cmsg_level;            /* SOL_SOCKET */     int cmsg_type;             /* SCM_RIGHTS */     int cmsg_data[0];          /* file descriptor goes here */ }; 


The size of the control message, including the header, is stored in cmsg_len. The only type of control message currently defined is SCM_RIGHTS, which passes file descriptors.[12] For this message type, cmsg_level and cmsg_type must be set to SOL_SOCKET and SCM_RIGHTS, respectively. The final member, cmsg_data, is an array of size zero. This is a gcc extension that allows an application to copy data to the end of the structure (see the following program for an example of this).

[12] This is sometimes called passing access rights.

Receiving a file descriptor is similar. Enough buffer space must be left for the control message, and a new file descriptor follows each struct cmsghdr that arrives.

To illustrate the use of these nested structures, we wrote an example program that is a fancy cat. It takes a file name as its sole argument, opens the specified file in a child process, and passes the resulting file descriptor to the parent through a Unix domain socket. The parent then copies the file to standard output. The file name is sent along with the file descriptor for illustrative purposes.

   1: /* passfd.c */   2:   3: /* We behave like a simple /bin/cat, which only handles one   4:    argument (a file name). We create Unix domain sockets through   5:    socketpair(), and then fork(). The child opens the file whose   6:    name is passed on the command line, passes the file descriptor   7:    and file name back to the parent, and then exits. The parent   8:    waits for the file descriptor from the child, then copies data   9:    from that file descriptor to stdout until no data is left. The  10:    parent then exits. */  11:  12: #include <alloca.h>  13: #include <fcntl.h>  14: #include <stdio.h>  15: #include <string.h>  16: #include <sys/socket.h>  17: #include <sys/uio.h>  18: #include <sys/un.h>  19: #include <sys/wait.h>  20: #include <unistd.h>  21:  22: #include "sockutil.h"           /* simple utility functions */  23:  24: /* The child process. This sends the file descriptor. */  25: int childProcess(char * filename, int sock) {  26:     int fd;  27:     struct iovec vector;        /* some data to pass w/ the fd */  28:     struct msghdr msg;          /* the complete message */  29:     struct cmsghdr * cmsg;      /* the control message, which */  30:                                 /* wil linclude the fd */  31:  32:     /* Open the file whose descriptor will be passed. */  33:     if ((fd = open(filename, O_RDONLY)) < 0) {  34:         perror("open");  35:         return 1;  36:     }  37:  38:     /* Send the file name down the socket, including the trailing  39:        '\0' */  40:     vector.iov_base = filename;  41:     vector.iov_len = strlen(filename) + 1;  42:  43:     /* Put together the first part of the message. Include the  44:        file name iovec */  45:     msg.msg_name = NULL;  46:     msg.msg_namelen = 0;  47:     msg.msg_iov = &vector;  48:     msg.msg_iovlen = 1;  49:  50:     /* Now for the control message. We have to allocate room for  51:        the file descriptor. */  52:     cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd));  53:     cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd);  54:     cmsg->cmsg_level = SOL_SOCKET;  55:     cmsg->cmsg_type = SCM_RIGHTS;  56:  57:     /* copy the file descriptor onto the end of the control  58:        message */  59:     memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));  60:  61:     msg.msg_control = cmsg;  62:     msg.msg_controllen = cmsg->cmsg_len;  63:  64:     if (sendmsg(sock, &msg, 0) != vector.iov_len)  65:         die("sendmsg");  66:  67:     return 0;  68: }  69:  70: /* The parent process. This receives the file descriptor. */  71: int parentProcess(int sock) {  72:     char buf[80];               /* space to read file name into */  73:     struct iovec vector;        /* file name from the child */  74:     struct msghdr msg;          /* full message */  75:     struct cmsghdr * cmsg;      /* control message with the fd */  76:     int fd;  77:  78:     /* set up the iovec for the file name */  79:     vector.iov_base = buf;  80:     vector.iov_len = 80;  81:  82:     /* the message we're expecting to receive */  83:  84:     msg.msg_name = NULL;  85:     msg.msg_namelen = 0;  86:     msg.msg_iov = &vector;  87:     msg.msg_iovlen = 1;  88:  89:     /* dynamically allocate so we can leave room for the file  90:        descriptor */  91:     cmsg = alloca(sizeof(struct cmsghdr) + sizeof(fd));  92:     cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(fd);  93:     msg.msg_control = cmsg;  94:     msg.msg_controllen = cmsg->cmsg_len;  95:  96:     if (!recvmsg(sock, &msg, 0))  97:         return 1;  98:  99:     printf("got file descriptor for '%s'\n", 100:            (char *) vector.iov_base); 101: 102:     /* grab the file descriptor from the control structure */ 103:     memcpy(&fd, CMSG_DATA(cmsg), sizeof(fd)); 104: 105:     copyData(fd, 1); 106: 107:     return 0; 108: } 109: 110: int main(int argc, char ** argv) { 111:     int socks[2]; 112:     int status; 113: 114:     if (argc != 2) { 115:         fprintf(stderr, "only a single argument is supported\n"); 116:         return 1; 117:     } 118: 119:     /* Create the sockets. The first is for the parent and the 120:        second is for the child (though we could reverse that 121:        if we liked. */ 122:     if (socketpair(PF_UNIX, SOCK_STREAM, 0, socks)) 123:         die("socketpair"); 124: 125:     if (!fork()) { 126:         /* child */ 127:         close(socks[0]); 128:         return childProcess(argv[1], socks[1]); 129:     } 130: 131:     /* parent */ 132:     close(socks[1]); 133:     parentProcess(socks[0]); 134: 135:     /* reap the child */ 136:     wait(&status); 137: 138:     if (WEXITSTATUS(status)) 139:         fprintf(stderr, "child failed\n"); 140: 141:     return 0; 142: } 



       
    top
     


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168

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