原始套接口有很多缺陷:易错、过度复杂、不可移植……
看下面的例子:
0 // This example contains bugs! Do not copy this example!
1 #include <sys/types.h>
2 #include <sys/socket.h>
3
4 const int PORT_NUM = 10000;
5
6 int echo server()
7 {
8 struct sockaddr_in addr;
9 int addr_len;
10 char buf[BUFSIZ];
11 int n_handle;
12 // Create the local endpoint.
13 int s_handle = socket (PF_UNIX, SOCK_DGRAM, 0);
14 if (s_handle == -1) return -1;
15
16 // Set up the address information where the server listens.
17 addr.sin_family = AF_INET;
18 addr.sin_port = PORT_NUM;
19 addr.sin_addr.addr = INADDR_ANY;
20
21 if (bind (s_handle, (struct sockaddr *) &addr,
22 sizeof addr) == -1)
23 return -1;
24
25 // Create a new communication endpoint.
26 if (n_handle = accept (s_handle, (struct sockaddr *) &addr,
27 &addr_len) != -1) {
28 int n;
29 while ((n = read (s_handle, buf, sizeof buf)) > 0)
30 write (n_handle, buf, n);
31
32 close (n handle);
33 }
34 return 0;
35 }
This function contains at least 10 subtle and all-too-common bugs that occur when using the Socket API. See if you can locate all 10 bugs while reading the code above, then read our dissection of these flaws below. The numbers in parentheses are the line numbers where errors occur in the
echo_server() function.
1.
Forgot to initialize an important variable. (8?) The
addr_len variable must be set to
sizeof (addr). Forgetting to initialize this variable will cause the
accept() call on line 26 to fail at run-time.
2.
Use of nonportable handle datatype. (11?4) Although these lines look harmless enough, they are also fraught with peril. This code isn't portable to Windows Sockets (WinSock) platforms, where socket handles are type SOCKET, not type
int. Moreover, WinSock failures are indicated via a non-standard macro called INVALID_SOCKET_HANDLE rather than by returning
?. The other bugs in the code fragment above aren't obvious until we examine the rest of the function.
The next three network addressing errors are subtle, and show up only at run time.
3.
Unused struct members not cleared. (17?9) The entire
addr structure should have been initialized to 0 before setting each address member. The Socket API uses one basic addressing structure (
sockaddr) with different overlays, depending on the address family, for example,
sockaddr_in for IPv4. Without initializing the entire structure to 0, parts of the fields may have indeterminate contents, which can yield random run-time failures.
4.
Address/protocol family mismatch. (17) The
addr.sin_family field was set to AF_NET, which designates the Internet addressing family. It will be used with a socket (
s_handle) that was created with the UNIX protocol family, which is inconsistent. Instead, the protocol family type passed to the
socket() function should have been PF_INET.
5.
Wrong byte order. (18) The value assigned to
addr.sin_port is not in network byte order; that is, the programmer forgot to use
htons() to convert the
port number from host byte order into network byte order. When run on a computer with little-endian byte order, this code will execute without error; however, clients will be unable to connect to it at the expected port number.
If these network addressing mistakes were corrected, lines 21?3 would actually work!
There's an interrelated set of errors on lines 25?7. These exemplify how hard it is to locate errors before run-time when programming directly to the C Socket API.
6.
Missing an important API call. (25) The
listen() function was omitted accidentally. This function must be called before
accept() to set the socket handle into so-called "passive mode."
7.
Wrong socket type for API call. (26) The
accept() function was called for
s_handle, which is exactly what should be done. The
s_handle was created as a SOCK_DGRAM-type socket, however, which is an illegal socket type to use with
accept(). The original
socket() call on line 13 should therefore have been passed the SOCK_STREAM flag.
8.
Operator precedence error. (26?7) There's one further error related to the
accept() call. As written,
n_handle will be set to 1 if
accept() succeeds and 0 if it fails. If this program runs on UNIX (and the other bugs are fixed), data will be written to either
stdout or
stdin, respectively, instead of the connected socket. Most of the errors in this example can be avoided by using the ACE wrapper facade classes, but this bug is simply an error in operator precedence, which can't be avoided by using ACE, or any other library. It's a common pitfall [
Koe88] with C and C++ programs, remedied only by knowing your operator precedence. To fix this, add a set of parentheses around the assignment expression, as follows:
9. if ((n_handle = accept (s_handle, (struct sockaddr *) &addr,
10. &addr_len)) != -1) {
Better yet, follow the convention we use in ACE and put the assignment to
n_handle on a separate line:
n_handle = accept (s_handle,
(struct sockaddr *) &addr, &addr_len);
if (n_handle != -1) ( {
This way, you don't have to worry about remembering the arcane C++ operator precedence rules!
We're almost finished with our
echo_server() function, but there are still several remaining errors.
11.
Wrong handle used in API call. (29) The
read() function was called to receive up to
sizeof buf bytes from
s_handle, which is the passive-mode listening socket. We should have called
read() on
n_handle instead. This problem would have manifested itself as an obscure run-time error and could not have been detected at compile time since socket handles are weakly typed.
12.
Possible data loss. (30) The return value from
write() was not checked to see if all
n bytes (or any bytes at all) were written, which is a possible source of data loss. Due to socket buffering and
flow control, a write() to a bytestream mode socket may only send part of the requested number of bytes, in which case the rest must be sent later.
Overly Complex APIs
The Socket API provides a single interface that supports multiple:
·
Protocol families, such as TCP/IP, IPX/SPX, X.25, ISO OSI, ATM, and UNIX-domain sockets
·
Communication/connection roles, such as
active connection establishment versus passive connection establishment versus data transfer
·
Communication optimizations, such as the gather-write function,
writev(), that sends multiple buffers in a single system function and
·
Options for less common functionality, such as broadcasting, multicasting, asynchronous I/O, and urgent data delivery.