堵塞和非堵塞
阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。
比如: 堵塞的accept
函数,在内核没有缓冲区数据的时候,会被堵塞;直到内核缓冲区有数据的时候,才会返回,线程才会继续往下执行
非阻塞:非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
比如:非堵塞的accept
函数,在内核没有缓冲区数据的时候,可以直接返回一个错误码给当前线程。
accept
函数
应用层的 accept 的调用逻辑如下,最终调到 inet_csk_accept
:
sock->ops->accept
|->inet_steam_ops->accept(inet_accept)
/* 由一开始的sock图可知sk_prot=tcp_prot */
|->sk1->sk_prot->accept
|->inet_csk_accept
看内核函数inet_csk_accept
:
如下:除去省略的代码,
1. 可以看到主要的堵塞代码:error = inet_csk_wait_for_connect(sk, timeo);
,作用:睡眠等待,线程挂起,【堵塞】等待有新连接进入队列。
2. 但如果将文件描述符的属性flags
设为 O_NONBLOCK
,那么 timeo
将为 空,接着 if (!timeo) goto out_err;
直接返回。
/*
* This will accept the next outstanding connection.
*/
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
...
/* Find already established connection */
// 如果队列为空
if (reqsk_queue_empty(queue)) {
// 【非堵塞】如果文件描述符的 flags 的标记位是 O_NONBLOCK,那么直接返回。
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
/* If this is a non blocking socket don't sleep */
error = -EAGAIN;
if (!timeo)
goto out_err;
// 【堵塞】等待有新连接进入队列
error = inet_csk_wait_for_connect(sk, timeo);
if (error)
goto out_err;
}
req = reqsk_queue_remove(queue);
newsk = req->sk;
...
out:
...
out_err:
...
}
EXPORT_SYMBOL(inet_csk_accept);
其中,inet_csk_wait_for_connect
的堵塞逻辑,是一个 for(;;)
,它里面比较高级一些,可以设置超时时间
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
for (;;) {
/* 通过增加EXCLUSIVE标志使得在BIO中调用accept中不会产生惊群效应 */
prepare_to_wait_exclusive(sk_sleep(sk), &wait,
TASK_INTERRUPTIBLE);
if (reqsk_queue_empty(&icsk->icsk_accept_queue))
timeo = schedule_timeout(timeo);
.......
err = -EAGAIN;
/* 这边accept超时,返回的是-EAGAIN */
if (!timeo)
break;
}
finish_wait(sk_sleep(sk), &wait);
return err;
}
通过上面就可以看出,默认的 socket
是堵塞的,不带 O_NONBLOCK
属性。
可以调用 fcntl
,将文件描述符的 flags
设为O_NONBLOCK
,这样就可以在 accept
的时候非阻塞
flags &= O_NONBLOCK;
if(fcntl(fd, F_SETFL, flags) < 0)
{
perror("fcntl");
return -1;
}