非阻塞I/O

I/O缓冲区特性可整理如下:

  • I/O缓冲区在每个TCP套接字中单独存在;
  • I/O缓冲区在创建套接字时自动生成;
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
  • 关闭套接字将丢失输入缓冲区中的数据。

阻塞/非阻塞读

阻塞读(read, recv, msgrcv的行为):

  • 如果没有数据,会一直等待;
  • 有数据时候会读到用户指定的缓存区,但是如果数据量比较少,少于参数指定的大小,read也会立即返回,而不会一直等到数据足够。

阻塞读的原则:数据不超过指定长度的时候,有多少读多少,没有数据就会一直等待
所以一般情况下,都需要采用循环读的方式,因为一次read不能保证读完需要的全部数据。

非阻塞读(read, recv, msgrcv的行为)

  • 没有数据,就立即返回;
  • 有数据,也是采用有多少读多少的方式来处理。

所以,read完一次,要判断读到的数据长度或者错误码再决定是否再次读取。注意这里的EAGAIN(或EWOULDBLOCK)错误码是需要继续读取,而返回0是对方已关闭连接。

阻塞/非阻塞写

阻塞写(write/send/msgsnd的行为)
与阻塞读有多少读多少不同的是,阻塞写会一直阻塞,直到所有数据都完成,再返回。

这是因为,读的时候不知道需要读多少,防止一直等不到足够的数据;而写的时候是知道要写多少数据的。不过也可能被中断,大多数情况是能够写完的。

非阻塞写(write/send/msgsnd的行为)
非阻塞写,就是有多少写多少。能够写多少是根据本地网络拥塞情况为标准的,当网络拥塞严重的时候,网络层没有足够的内存来进行写操作,就会出现写不完的情况;

如果在open一个设备时指定了O_NONBLOCK标志,read/write就不会阻塞。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备。非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者需要反复查询做无用功,如果阻塞在那里,操作系统可以调度别的进程执行,就不会做无用功了。

非阻塞connect

当在一个非阻塞的套接字上调用connect时,connect将立即返回一个EINPROGRCESS错误,不过已发起的TCP三路握手继续进行。我们接着使用select检测这个连接成功或失败的已建立条件。

非阻塞connect的用途:

  • 可以把三次握手叠加在其它处理上。完成一个connect要花一个RTT时间,这段时间内我们可以执行其它的处理工作
  • 可以同时建立多个连接
  • 可以使用select设置时间限制,缩短connect的超时

需要处理的细节:

  • 尽管连接是非阻塞的,如果服务器在同一台主机上,连接通常会立即建立,需要处理这种情当连接成功建立时,套接字变得可写;
  • 当连接建立遇到错误时,套接字变得既可读又可写
  • 调用select之前有可能连接已建立并有来自对端的数据到达。这种情况下即使套接字上不发生错误,套接字也是既可读又可写的。因此需使用getsockopt的SO_ERROR选项,判断是否建立连接发生了错误
int
connect_nonb(int sockfd, const 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(sockfd, F_SETFL, flags | O_NONBLOCK);
       error = 0;
       if ( (n = connect(sockfd, saptr, salen)) < 0)
              if (errno != EINPROGRESS)
                     return(-1);
       /* Do whatever we want while the connect is taking place. */
       if (n == 0)
              goto done;    /* connect completed immediately */    //同一台主机上
       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) {    //无设置时间则使用默认时间
              close(sockfd);             /* timeout */
              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);                /* Solaris pending error */
       } else
              err_quit("select error: sockfd not set");
done:
       Fcntl(sockfd, F_SETFL, flags);    /* restore file status flags */
       if (error) {
              close(sockfd);             /* just in case */
              errno = error;
              return(-1);
       }
       return(0);
}

被中断的connect

若connect调用在TCP三次握手完成前被中断,假设被中断的connect不由内核自动重启,那么它将返回EINTR。我们不能再次调用connect等待未连接的连接完成,这样会导致返回EADDRINUSE错误。应像上述情况一样,使用select,连接建立成功时,select返回套接字可写,连接建立失败时,套接字既可读又可写。

非阻塞accept

  • 当使用select获悉某个监听套接上有已完成连接准备好被accept时,总是把这个监听套接字设置为非阻塞
  • 在后续的accept调用中,忽略以下错误:EWOULDBLOCK(Berkeley,客户中断连接时)、ECONNABORTED(POSIX,客户中断连接时)、EPROTO(SVR4,客户中断连接时)、EINTR(有信号被捕获)。

SO_LINGER套接字选项

本选项指定close函数面对面向连接的协议,该如何操作。本选项要求用户进程与内核间传递如下结构:

structstruct linger {
       int l_onoff;
       int l_linger;
};
  • 若l_onoff为0,则关闭本选项,使用默认的TCP设置,若有数据残留在套接字发送缓冲区中,系统将试着把这些数据发送给对端
  • 若l_onoff为非0,l_linger为0。当close某个连接时,TCP将中止该连接,也就是说TCP将丢弃保留在发送缓冲区的任何数据,并发送一个RST给对端,也就是没有四次挥手过程,这样一来避免了TCP的TIME_WAIT状态。
struct linger        ling;
ling.l_onoff = 1;          /* cause RST to be sent on close() */
ling.l_linger = 0;
Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
Close(sockfd);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值