UNIX Domain Sockets

来源:互联网 发布:linux qt多进程 编辑:程序博客网 时间:2021/09/25 17:15

socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。


UNIX Domain Sockets 的使用参考



17.3. UNIX Domain Sockets

UNIX domain sockets are used to communicate with processes running on the same machine. Although Internet domain sockets can be used for this same purpose, UNIX domain sockets are more efficient. UNIX domain sockets only copy data; they have no protocol processing to perform, no network headers to add or remove, no checksums to calculate, no sequence numbers to generate, and no acknowledgements to send.

UNIX domain sockets provide both stream and datagram interfaces. The UNIX domain datagram service is reliable, however. Messages are neither lost nor delivered out of order. UNIX domain sockets are like a cross between sockets and pipes. You can use the network-oriented socket interfaces with them, or you can use the socketpair function to create a pair of unnamed, connected, UNIX domain sockets.

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


Returns: 0 if OK, 1 on error


Although the interface is sufficiently general to allow socketpair to be used with arbitrary domains, operating systems typically provide support only for the UNIX domain.

Examples_pipe Function Using UNIX Domain Sockets

Figure 17.13 shows the socket-based version of the s_pipe function previously shown in Figure 17.6. The function creates a pair of connected UNIX domain stream sockets.

Some BSD-based systems use UNIX domain sockets to implement pipes. But when pipe is called, the write end of the first descriptor and the read end of the second descriptor are both closed. To get a full-duplex pipe, we must call socketpair directly.

Figure 17.13. Socket version of the s_pipe function
#include "apue.h"#include <sys/socket.h>/* * Returns a full-duplex "stream" pipe (a UNIX domain socket) * with the two file descriptors returned in fd[0] and fd[1]. */ints_pipe(int fd[2]){    return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));}

17.3.1. Naming UNIX Domain Sockets

Although the socketpair function creates sockets that are connected to each other, the individual sockets don't have names. This means that they can't be addressed by unrelated processes.

In Section 16.3.4, we learned how to bind an address to an Internet domain socket. Just as with Internet domain sockets, UNIX domain sockets can be named and used to advertise services. The address format used with UNIX domain sockets differs from Internet domain sockets, however.

Recall from Section 16.3 that socket address formats differ from one implementation to the next. An address for a UNIX domain socket is represented by a sockaddr_un structure. On Linux 2.4.22 and Solaris 9, the sockaddr_un structure is defined in the header <sys/un.h> as follows:

   struct sockaddr_un {        sa_family_t sun_family;      /* AF_UNIX */        char        sun_path[108];   /* pathname */   };


On FreeBSD 5.2.1 and Mac OS X 10.3, however, the sockaddr_un structure is defined as

   struct sockaddr_un {        unsigned char  sun_len;         /* length including null */        sa_family_t    sun_family;      /* AF_UNIX */        char           sun_path[104];   /* pathname */   };


The sun_path member of the sockaddr_un structure contains a pathname. When we bind an address to a UNIX domain socket, the system creates a file of type S_IFSOCK with the same name.

This file exists only as a means of advertising the socket name to clients. The file can't be opened or otherwise used for communication by applications.

If the file already exists when we try to bind the same address, the bind request will fail. When we close the socket, this file is not automatically removed, so we need to make sure that we unlink it before our application exits.


The program in Figure 17.14 shows an example of binding an address to a UNIX domain socket.

When we run this program, the bind request succeeds, but if we run the program a second time, we get an error, because the file already exists. The program won't succeed again until we remove the file.

$ ./a.out                                       run the programUNIX domain socket bound$ ls -l foo.socket                              look at the socket filesrwxrwxr-x 1 sar        0 Aug 22 12:43 foo.socket$ ./a.out                                       try to run the program againbind failed: Address already in use$ rm foo.socket                                 remove the socket file$ ./a.out                                       run the program a third timeUNIX domain socket bound                        now it succeeds


The way we determine the size of the address to bind is to determine the offset of the sun_path member in the sockaddr_un structure and add to this the length of the pathname, not including the terminating null byte. Since implementations vary in what members precede sun_path in the sockaddr_un structure, we use the offsetof macro from <stddef.h> (included by apue.h) to calculate the offset of the sun_path member from the start of the structure. If you look in <stddef.h>, you'll see a definition similar to the following:

#define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)


The expression evaluates to an integer, which is the starting address of the member, assuming that the structure begins at address 0.

Figure 17.14. Binding an address to a UNIX domain socket
#include "apue.h"#include <sys/socket.h>#include <sys/un.h>intmain(void){    int fd, size;    struct sockaddr_un un;    un.sun_family = AF_UNIX;    strcpy(un.sun_path, "foo.socket");    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)        err_sys("socket failed");    size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);    if (bind(fd, (struct sockaddr *)&un, size) < 0)        err_sys("bind failed");    printf("UNIX domain socket bound/n");    exit(0);}

17.3.2. Unique Connections

A server can arrange for unique UNIX domain connections to clients using the standard bind, listen, and accept functions. Clients use connect to contact the server; after the connect request is accepted by the server, a unique connection exists between the client and the server. This style of operation is the same that we illustrated with Internet domain sockets in Figures 16.14 and 16.15.

Figure 17.15 shows the UNIX domain socket version of the serv_listen function.

Figure 17.15. The serv_listen function for UNIX domain sockets
#include "apue.h"#include <sys/socket.h>#include <sys/un.h>#include <errno.h>#define QLEN 10/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */intserv_listen(const char *name){    int                 fd, len, err, rval;    struct sockaddr_un  un;    /* create a UNIX domain stream socket */    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)       return(-1);    unlink(name);   /* in case it already exists */    /* fill in socket address structure */    memset(&un, 0, sizeof(un));    un.sun_family = AF_UNIX;    strcpy(un.sun_path, name);    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);    /* bind the name to the descriptor */    if (bind(fd, (struct sockaddr *)&un, len) < 0) {        rval = -2;        goto errout;    }    if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */        rval = -3;        goto errout;    }    return(fd);errout:    err = errno;    close(fd);    errno = err;    return(rval);}


First, we create a single UNIX domain socket by calling socket. We then fill in a sockaddr_un structure with the well-known pathname to be assigned to the socket. This structure is the argument to bind. Note that we don't need to set the sun_len field present on some platforms, because the operating system sets this for us using the address length we pass to the bind function.

Finally, we call listen (Section 16.4) to tell the kernel that the process will be acting as a server awaiting connections from clients. When a connect request from a client arrives, the server calls the serv_accept function (Figure 17.16).

Figure 17.16. The serv_accept function for UNIX domain sockets
#include "apue.h"#include <sys/socket.h>#include <sys/un.h>#include <time.h>#include <errno.h>#define STALE   30  /* client's name can't be older than this (sec) *//* * Wait for a client connection to arrive, and accept it. * We also obtain the client's user ID from the pathname * that it must bind before calling us. * Returns new fd if all OK, <0 on error */intserv_accept(int listenfd, uid_t *uidptr){    int                 clifd, len, err, rval;    time_t              staletime;    struct sockaddr_un  un;    struct stat         statbuf;    len = sizeof(un);    if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)        return(-1);     /* often errno=EINTR, if signal caught */    /* obtain the client's uid from its calling address */    len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */    un.sun_path[len] = 0;           /* null terminate */    if (stat(un.sun_path, &statbuf) < 0) {        rval = -2;        goto errout;    }#ifdef S_ISSOCK     /* not defined for SVR4 */    if (S_ISSOCK(statbuf.st_mode) == 0) {        rval = -3;      /* not a socket */        goto errout;    }#endif    if ((statbuf.st_mode & (S_IRWXG | S_IRWXO)) ||        (statbuf.st_mode & S_IRWXU) != S_IRWXU) {          rval = -4;     /* is not rwx------ */          goto errout;    }    staletime = time(NULL) - STALE;    if (statbuf.st_atime < staletime ||        statbuf.st_ctime < staletime ||        statbuf.st_mtime < staletime) {          rval = -5;    /* i-node is too old */          goto errout;    }    if (uidptr != NULL)        *uidptr = statbuf.st_uid;   /* return uid of caller */    unlink(un.sun_path);        /* we're done with pathname now */    return(clifd);errout:    err = errno;    close(clifd);    errno = err;    return(rval);}


The server blocks in the call to accept, waiting for a client to call cli_conn. When accept returns, its return value is a brand new descriptor that is connected to the client. (This is somewhat similar to what the connld module does with the STREAMS subsystem.) Additionally, the pathname that the client assigned to its socket (the name that contained the client's process ID) is also returned by accept, through the second argument (the pointer to the sockaddr_un structure). We null terminate this pathname and call stat. This lets us verify that the pathname is indeed a socket and that the permissions allow only user-read, user-write, and user-execute. We also verify that the three times associated with the socket are no older than 30 seconds. (Recall from Section 6.10 that the time function returns the current time and date in seconds past the Epoch.)

If all these checks are OK, we assume that the identity of the client (its effective user ID) is the owner of the socket. Although this check isn't perfect, it's the best we can do with current systems. (It would be better if the kernel returned the effective user ID to accept as the I_RECVFD ioctl command does.)

The client initiates the connection to the server by calling the cli_conn function (Figure 17.17).

Figure 17.17. The cli_conn function for UNIX domain sockets
#include "apue.h"#include <sys/socket.h>#include <sys/un.h>#include <errno.h>#define CLI_PATH    "/var/tmp/"      /* +5 for pid = 14 chars */#define CLI_PERM    S_IRWXU          /* rwx for user only *//* * Create a client endpoint and connect to a server. * Returns fd if all OK, <0 on error. */intcli_conn(const char *name){    int                fd, len, err, rval;    struct sockaddr_un un;    /* create a UNIX domain stream socket */    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)        return(-1);    /* fill socket address structure with our address */    memset(&un, 0, sizeof(un));    un.sun_family = AF_UNIX;    sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);    unlink(un.sun_path);        /* in case it already exists */    if (bind(fd, (struct sockaddr *)&un, len) < 0) {        rval = -2;        goto errout;    }    if (chmod(un.sun_path, CLI_PERM) < 0) {        rval = -3;        goto errout;    }    /* fill socket address structure with server's address */    memset(&un, 0, sizeof(un));    un.sun_family = AF_UNIX;    strcpy(un.sun_path, name);    len = offsetof(struct sockaddr_un, sun_path) + strlen(name);    if (connect(fd, (struct sockaddr *)&un, len) < 0) {        rval = -4;        goto errout;    }    return(fd);errout:    err = errno;    close(fd);    errno = err;    return(rval);}


We call socket to create the client's end of a UNIX domain socket. We then fill in a sockaddr_un structure with a client-specific name.

We don't let the system choose a default address for us, because the server would be unable to distinguish one client from another. Instead, we bind our own address, a step we usually don't take when developing a client program that uses sockets.

The last five characters of the pathname we bind are made from the process ID of the client. We call unlink, just in case the pathname already exists. We then call bind to assign a name to the client's socket. This creates a socket file in the file system with the same name as the bound pathname. We call chmod to turn off all permissions other than user-read, user-write, and user-execute. In serv_accept, the server checks these permissions and the user ID of the socket to verify the client's identity.

We then have to fill in another sockaddr_un structure, this time with the well-known pathname of the server. Finally, we call the connect function to initiate the connection with the server.