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.
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.
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 program UNIX domain socket bound $
ls -l foo.socket
look at the socket file srwxrwxr-x 1 sar 0 Aug 22 12:43 foo.socket $
./a.out
try to run the program again bind failed: Address already in use $
rm foo.socket
remove the socket file $ ./a.out
run the program a third time UNIX 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.
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.
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).
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).
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.