1. 概述
套接字的默认状态是阻塞的,即当发出一个不能立即完成的套接字调用时,其进程被置于休眠状态。可能阻塞的套接字调用可分为如下四类:
- 输入操作:read、readv、recv、recvfrom和recvmsg等。对一个阻塞的TCP套接字调用这些函数时,而该套接字的接收缓冲区中没有数据可读,该进程将被置于休眠状态,直到有数据到达。既然TCP是字节流协议,该进程的唤醒就是只要有一些数据到达。
- 输出操作:write、writev、send、sendto、sendmsg等。对于一个TCP套接字,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据。若发送缓冲区无空间,进程将被置于休眠状态,直到有空间为止。【若发送缓冲区空间为0,则返回EWOULDBLOCK错误;若有小部分缓冲空间,则返回剩余空间的字节数(称为不足计数)】。
- 接受外来连接,即accept函数。若尚无外来连接,则调用进程将被置于休眠状态。
- 发起外出连接,即TCP的connect函数。TCP连接的建立涉及三路握手过程,而且connect函数一直要等到客户收到对于自己的SYN的ACK为止才返回, 即connect总会阻塞其调用进程至少一个服务器的RTT时间。【图2-2】
1.1 非阻塞读和写:str_cli函数
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, val, stdineof;
ssize_t n, nwritten;
fd_set rset, wset;
char to[MAXLINE], fr[MAXLINE];
char *toiptr, *tooptr, *friptr, *froptr;
// 使用Fcntl把所用3个描述符都设置为非阻塞
val = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDIN_FILENO, F_GETFL, 0);
Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK);
val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK);
toiptr = toiptr = to; // 初始化指针
froptr = friptr = fr; // 初始化指针
stdineof = 0;
maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
for (; ; )
{
FD_ZERO(&rset);
FD_ZERO(&wset);
if (stdineof == 0 && toiptr < &to[MAXLINE])
// 打开描述符集中对应标准输入的位
FD_SET(STDIN_FILENO, &rset); // 从标准输入流读取数据
if (friptr < &fr[MAXLINE])
FD_SET(sockfd, &rset); // 从socket读取数据
if (tooptr != toiptr)
FD_SET(sockfd, &wset);
if (froptr != friptr)
FD_SET(STDOUT_FILENO, &wset);
// 等待4个可能条件中任何一个变为真
Select(maxfdp1, &rset, &wset, NULL, NULL);
if (FD_ISSET)
{
/* code */
}
/* 省略..... */
}
}
1.2 非阻塞connect函数
int connect_nonb(int sockfd, SA *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = Fcntl(sockfd, F_GETFL, 0);
// Fcntl函数把套接字设置为非阻塞
Fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ( (n = connect(sockfd, saptr, salen)) < 0)
if (error != EINPROGRESS)
return(-1);
// Do whatever we want while the connect is taking place
if (n == 0) // connect completed immediately
goto done;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = Select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) // TIMEOUT
{
close(sockfd);
errno = ETIMEDOUT;
return(-1);
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset))
{
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return(-1);
}
else
err_quit("select error: sockfd not set");
done:
Fcntl(sockfd, F_SETFL, flags); // restore file status flags
if (error)
{
close(sockfd);
errno = error;
return(-1);
}
return(0);
}
1.3 非阻塞accept
int main(int argc, char const *argv[])
{
int sockfd;
struct linger ling;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
// 设置
ling.l_onoff = 1;
ling.l_linger = 0;
Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
Close(sockfd);
exit(0);
return 0;
}