21-非阻塞accept

1. 回忆accept函数

 

之前在10-在accept之前中止连接(连接异常)这一篇中已经讨论过在accept之前中止连接的情况了,不过从最终的结果来看,accept并没有返回错误,而是之后调用read读取已连接套接字时产生了错误。

另外,当一个已完成连接正等待被服务端accept时,select会把该连接的套接字作为读描述符并返回。这意味着之后的accept就不应该阻塞,但是会引发一个bug:当客户端跟服务器建立连接之后发送了一个RST包,这时accept会阻塞,直到有下一个已完成的连接准备好被accept为止。

 

2. accept引发的问题

 

为了说明这种情况,修改之前TCP服务器的代码:

//select会返回已连接的描述符
select();
if(FD_ISSET(listenfd , &rset)){

   //sleep是为了模拟accept阻塞的情况
   sleep(5);
   client_len = sizeof(client_addr);
   connfd = accept(listenfd , (struct sockaddr *)&client_addr , &client_len);
}

如上所示,select返回已连接的描述符之后,接着就阻塞了,导致无法调用accept,通常情况下服务器是没有问题的。考虑这么一种情况:如果在建立tcp连接之后,客户端又马上发送了RST,就出现了问题。这意味着客户端在服务器调用accept之前中止了这个连接,但是Berkeley版本的linux不会把这个中止的连接返回给服务端,其他linux版本可能返回EPROTO错误,而不会返回ECONNABORTED错误。

因为客户端发送了RST后,这个已完成的连接被服务器tcp从已完成连接队列中删除掉了,我们假设此时队列中没有任何其他已完成的连接,那么之后服务器调用accept就会阻塞,直到已完成连接队列不为空为止,换句话说,服务器在aceept处阻塞期间是无法处理其他事情的。

 

3. 非阻塞accept实现

为了防止accept阻塞,当select监听的某个套接字有一个已完成连接正等待被accept时,把监听的套接字设置为非阻塞,然后调用accept忽略以下错误:

  1. EWOULDBLOCK (Berkeley实现,客户端中止连接时)、 ECONNABORTED (POSIX实现,客户中止连接时)
  2. EPROTO(SVR4实现,客户端中止连接时) 和 EINTR(如果信号被捕获)

 

实现accept非阻塞:

//设置套接字非阻塞
int flags = fcntl(sock, F_GETFL, 0);
fcntl(listenfd , F_SETFL , flags|O_NONBLOCK);


while(1){
    //调用select函数
    FD_SET(listenfd , &rset);
    select(listenfd +1 , &rfds , NULL , NULL , ...);
    if(FD_ISSET(listenfd , &rset)){
    client_len = sizeof(client_addr);
    connfd = accept(listenfd , (struct sockaddr *)&client_addr , &client_len);
    if(connfd < 0){
        //忽略EWOULDBLOCK错误,继续循环
        if(errno == EWOULDBLOCK)
            continue;
        perror("accept");
        exit(-1);
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值