By default, sockets are blocking. This means that when we issue a socket call that cannot be completed immediately, our process is put to sleep, waiting for the condition to be true. We can divide the socket calls that may block into four categories:

  1. Input operations— These include the read, readv, recv, recvfrom, and recvmsg functions. If we call one of these input functions for a blocking TCP socket (the default), and there is no data available in the socket receive buffer, we are put to sleep until some data arrives. Since TCP is a byte stream, we will be awakened when "some" data arrives: It could be a single byte of data, or it could be a full TCP segment of data. If we want to wait until some fixed amount of data is available, we can call our own function readn (Figure 3.15) or specify the MSG_WAITALL flag (Figure 14.6).

    Since UDP is a datagram protocol, if the socket receive buffer is empty for a blocking UDP socket, we are put to sleep until a UDP datagram arrives.

    With a nonblocking socket, if the input operation cannot be satisfied (at least one byte of data for a TCP socket or a complete datagram for a UDP socket), we see an immediate return with an error of EWOULDBLOCK.

  2. Output operations— These include the write, writev, send, sendto, and sendmsg functions. For a TCP socket, we said in Section 2.11 that the kernel copies data from the application's buffer into the socket send buffer. If there is no room in the socket send buffer for a blocking socket, the process is put to sleep until there is room.

    With a nonblocking TCP socket, if there is no room at all in the socket send buffer, we return immediately with an error of EWOULDBLOCK. If there is some room in the socket send buffer, the return value will be the number of bytes the kernel was able to copy into the buffer. (This is called a short count.)

    We also said in Section 2.11 that there is no actual UDP socket send buffer. The kernel just copies the application data and moves it down the stack, prepending the UDP and IP headers. Therefore, an output operation on a blocking UDP socket (the default) will not block for the same reason as a TCP socket, but it is possible for output operations to block on some systems due to the buffering and flow control that happens within the networking code in the kernel.

  3. Accepting incoming connections— This is the accept function. If accept is called for a blocking socket and a new connection is not available, the process is put to sleep.

    If accept is called for a nonblocking socket and a new connection is not available, the error EWOULDBLOCK is returned instead.

  4. Initiating outgoing connections— This is the connect function for TCP. (Recall that connect can be used with UDP, but it does not cause a "real" connection to be established; it just causes the kernel to store the peer's IP address and port number.) We showed in Section 2.6 that the establishment of a TCP connection involves a three-way handshake and the connect function does not return until the client receives the ACK of its SYN. This means that a TCP connect always blocks the calling process for at least the RTT to the server.

    If connect is called for a nonblocking TCP socket and the connection cannot be established immediately, the connection establishment is initiated (e.g., the first packet of TCP's three-way handshake is sent), but the error EINPROGRESS is returned. Notice that this error differs from the error returned in the previous three scenarios. Also notice that some connections can be established immediately, normally when the server is on the same host as the client. So, even with a nonblocking connect, we must be prepared for connect to return successfully. We will show an example of a nonblocking connect in Section 16.3.

     

    Traditionally, System V has returned the error EAGAIN for a nonblocking I/O operation that cannot be satisfied, while Berkeley-derived implementations have returned the error EWOULDBLOCK. Because of this history, the POSIX specification says either may be returned for this case. Fortunately, most current systems define these two error codes to be the same (check your system's <sys/errno.h> header), so it doesn't matter which one we use. In this text, we use EWOULDBLOCK.

     

Section 6.2 summarized the different models available for I/O and compared nonblocking I/O to other models. In this chapter, we will provide examples of all four types of operations and develop a new type of client, similar to a Web client, that initiates multiple TCP connections at the same time using a nonblocking connect.

 

socket默认是阻塞的。如果我们对一个socket的操作不能立即返回,当前的进程就会被挂起,等待操作完成。

我们可以把socket操作分为4类:

1. 输入操作:函数read, readv, recv, recvfrom,和recvmsg。

  对一个阻塞的TCP套接字调用这几个函数中的任意一个,如果套接字的接收缓存没有任何数据,当前线程将被挂起,直到有新的数据到达缓存。

   对于非阻塞套接字,如果TCP套接字的接收缓存中没有数据或者UDP套接字的缓存中没有一个完整的数据报,输入操作函数将会立即返回错误码EWOULDBLOCK。

 

2. 输出操作:函数write, writev, send, sendtosendmsg。

  对于TCP套接字,内核将应用层缓存中的数据拷贝到套接字的发送缓存中。如果阻塞套接字的发送缓存中没有剩余空间,线程将被挂起,直到发送缓冲区有空闲。

   对非阻塞的TCP套接字,如果发送缓冲区没有空闲,输出函数立即返回错误码EWOULDBLOCK。如果发送缓冲区有少量空闲(小于需要发送的长度),函数返回值是内核从应用层缓存拷贝到套接字发送缓存的字节数。

   UDP套接字没有发送缓存,内核只是将数据拷贝到协议栈,追加上UDP和IP头,因此阻塞UDP套接字并不会因为发送缓存不足而被阻塞,但是输出操作函数可能因为某些系统的缓存或者内核代码流程被阻塞。

 

3. 接收入向连接accpet函数。

    如果对一个阻塞套接字调用accept函数,在没有新的连接请求时,线程将被挂起。

    如果对一个非阻塞套接字调用accept函数,在没有新的连接请求时,函数将返回EWOULDBLOCK。

 

4. 发起外向连接:TCP的connect函数。

   阻塞的TCP连接的建立需要进行“三次握手”,connect在收到服务器端的SYN ACK之前不会返回(见上图)。

   非阻塞的TCP连接,如果连接没有立即建立,会返回错误码EINPROGRESS(这个错误码与前面三种场景返回的错误码不同)。另外,无关服务器和客户端在同一个机器,连接会马上建立,因此,对非阻塞TCP连接,

   需要处理函数返回时连接建立的场景。

 

通常,System V的非阻塞IO函数返回错误码EAGAIN;Berkeley-derived 系统返回错误码EWOULDBLOCK。

幸运的时,现代的系统把两个错误码定义为一样的。

posted on 2014-04-26 16:11  escoffier  阅读(349)  评论(0编辑  收藏  举报