UNIX 原始Socket 的缺陷

来源:互联网 发布:windows 系统修复 编辑:程序博客网 时间:2024/03/29 18:12
 
原始套接口有很多缺陷:易错、过度复杂、不可移植……
看下面的例子:
 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.