IPC Using Socketpair
As a warmup, we begin our exploration of sockets with the generation of a pair of UNIX domain sockets. The socketpair call, shown in Table 10.1, is used to create the pair of sockets.
The socketpair call takes four arguments. The first argument, d , is an integer value that specifies the domain. In general, the domains for socket-based calls should be specified as one of the protocol family-defined constants found in the header file . As in previous examples, the programmer does not directly include this file, since the file, which must be included, includes this more system-specific header file. When we look in the file, we find two sets of similar defined constants. One set of constants begins with AF_ (denoting address family) and the second set begins with PF_ (indicating protocol family). At one time the PF_ constants were defined in terms of the AF_ constants. Now the AF_ set of constants is defined in terms of the PF_ constants. This mishmash occurs as the concept of address families preceded that of protocol families. As we are heading toward the use of protocol families, the PF_ designated constants are more appropriate to use when generating a socket. The current set of all defined protocol families, as found in the file, is shown in Table 10.2.
Table 10.1. Summary of the socketpair System Call
Include File(s) |
Manual Section |
2 |
||
Summary |
int socketpair( int d, int type, int protocol, int sv[2] ); |
|||
Return |
Success |
Failure |
Sets errno |
|
0 and two open socket descriptors |
-1 |
Yes |
Table 10.2. Protocol Family Constants.
Constant |
Value |
Reference |
---|---|---|
PF_UNSPEC |
Unspecified. |
|
PF_LOCAL |
1 |
Local to host (pipes and file-domain). |
PF_UNIX |
PF_LOCAL |
Old BSD name for PF_LOCAL. |
PF_FILE |
PF_LOCAL |
Another nonstandard name for PF_LOCAL. |
PF_INET |
2 |
IP protocol family. |
PF_AX25 |
3 |
Amateur Radio AX.25. |
PF_IPX |
4 |
Novell Internet Protocol. |
PF_APPLETALK |
5 |
Appletalk DDP. |
PF_NETROM |
6 |
Amateur radio NetROM. |
PF_BRIDGE |
7 |
Multiprotocol bridge. |
PF_ATMPVC |
8 |
ATM PVCs. |
PF_X25 |
9 |
Reserved for X.25 project. |
PF_INET6 |
10 |
IP version 6. |
PF_ROSE |
11 |
Amateur Radio X.25 PLP. |
PF_DECnet |
12 |
Reserved for DECnet project. |
PF_NETBEUI |
13 |
Reserved for 802.2LLC project. |
PF_SECURITY |
14 |
Security callback pseudo AF. |
PF_KEY |
15 |
PF_KEY key management API. |
PF_NETLINK |
16 |
|
PF_ROUTE |
PF_NETLINK |
Alias to emulate 4.4BSD. |
PF_PACKET |
17 |
Packet family. |
PF_ASH |
18 |
Ash. |
PF_ECONET |
19 |
Acorn Econet. |
PF_ATMSVC |
20 |
ATM SVCs. |
PF_SNA |
22 |
Linux SNA Project |
PF_IRDA |
23 |
IRDA sockets. |
PF_PPPOX |
24 |
PPPoX sockets. |
PF_MAX |
32 |
For now. |
Note that most of the socket-based calls only work with a limited subset of address/protocol families. The socketpair call is only implemented for the PF_LOCAL (PF_UNIX) family, thus restricting it to same-host communications.
The second argument for the socketpair call, type , indicates the socket type. The defined constant SOCK_STREAM can be used to indicate a stream socket or the defined constant SOCK_DGRAM to indicate a datagram-based socket. The third argument, protocol , is used to indicate the protocol within the specified family. In most cases, this argument is set to 0, which indicates to the system that it should select the protocol. With Internet domain communications, the system will use, by default, UDP for connectionless sockets and TCP for connection-oriented sockets. If necessary, the IPPROTO_TCP or IPPROTO_UDP constants found in the header file can be used to directly select the protocol within a specific family. The fourth argument, sv , is the base address of an integer array that references the two socket descriptors that are created if the call is successful. Each descriptor is bidirectional and is available for both reading and writing. If the socketpair call fails, it returns a -1 and sets errno . The value errno may take, and an interpretation of each value, is shown in Table 10.3.
Table 10.3. socketpair Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
14 |
EFAULT |
Bad address |
sv references an illegal address. |
24 |
EMFILE |
Too many open files |
This process has reached the limit for open file descriptors. |
93 |
EPROTONOSUPPORT |
Protocol not supported |
Requested protocol not supported on this system. |
95 |
EOPNOTSUPPORT |
Operation not supported |
Specified protocol does not support socket pairs. |
97 |
EAFNOSUPPORT |
Address family not supported by protocol |
Cannot use the indicated address family with specified protocol family. |
Program 10.1 creates a socket pair, forks a child process, and uses the created sockets to communicate information between the parent and child processes.
Program 10.1 Creating and using a socket pair.
File : p10.1.cxx /* Creating a socket pair */ #include + #include #include #include using namespace std; const int BUF_SZ = 10; 10 int main( ) { int sock[2], // The socket pair i; static char buf[BUF_SZ]; // Temporary buffer for message + if (socketpair(PF_LOCAL, SOCK_DGRAM, 0, sock) < 0) { perror("Generation error"); return 1; } switch (fork( )) { 20 case -1: perror("Bad fork"); return 2; case 0: // The child process close(sock[1]); + for (i = 0; i < 10; i += 2) { sleep(1); sprintf(buf, "c: %d", i); write(sock[0], buf, sizeof(buf)); read( sock[0], buf, BUF_SZ); 30 cout << "c-> " << buf << endl; // Message from parent } close(sock[0]); break; default: // The parent process + close(sock[0]); for (i = 1; i < 10; i += 2) { sleep(1); read( sock[1], buf, BUF_SZ); cout << "p-> " << buf << endl; // Message from child 40 sprintf(buf, "p: %d", i); write(sock[1], buf, sizeof(buf)); } close(sock[1]); } + return 0; }
The program starts by creating, with a single call in line 15, a pair of local UNIX datagram sockets. The program then forks, producing a child process. When in the child, the program closes the socket descriptor referenced as sock[1] . It then enters a loop, from 0 to 9, counting in steps of 2, where it does the following. The process sleeps for one second to slow down its output display sequence. It then creates, in a temporary buffer, a message to be sent to the parent process. The message contains the character sequence c: , to label it as from the child, followed by the current integer loop counter value. The contents of the temporary buffer are then written to socket descriptor 0 (sock[0]) using the write system call. Following the write to the socket, the child process reads from the same socket descriptor to obtain the message generated by the parent process. The child process then displays the message from the parent on the screen.
The parent process follows a similar set of steps. However, it closes sock[0] and does its socket reading and writing from sock[1] . When this program is run, it will produce the output as shown in Figure 10.4.
Figure 10.4 The output of Program 10.1.
linux$ p10.1 p-> c: 0 c-> p: 1 p-> c: 2 c-> p: 3 p-> c: 4 c-> p: 5 p-> c: 6 c-> p: 7 p-> c: 8 c-> p: 9
Before the process forks, both sock[0] and sock[1] descriptors are available in the parent for reading and writing. After the fork, the child process closes sock[1] and reads and writes using sock[0] . The parent process closes sock[0] and reads and writes using sock[1] . At the kernel level the sockets are still one and the same. Thus, what the child process writes to sock[0] can be read by the parent process from sock[1] and vice versa. Figure 10.5 presents a diagrammatic representation of this relationship.
Figure 10.5. The socketpair before and after the process forks.