0%

accept和accept4的区别

accept4()的一些东西

  nginx中有用到accept4(),但有什么用呢?

  accept4()不是标准的Linux扩展,在Linux 2.6.28之后才被支持,跟accept()的区别仅仅在于对文件描述符的行为控制上。下面两个函数的原型:

1
2
3
4
5
6
7
8
9
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);

  可以看到两者的区别仅仅在于accept4()有第四个参数flags,这个参数如果为0,就跟accept()一样;下面的两个参数可以用按位OR来获取不同的行为:

SOCK_NONBLOCK:为新打开的文件描述符设置O_NONBLOCK标志位,这跟用fcntl()设置的效果是一样的,区别就是用fcntl()的话需要多调用个函数。

SOCK_CLOEXEC: 为新打开的文件描述符设置FD_CLOEXEC标志位,该标志位的作用是在进程使用fork()加上execve()的时候自动关闭打开的文件描述符。其实使用fcntl()设置FD_CLOEXEC标志位(也就是用open()的时候设置的O_CLOEXEC标志位)也能达到同样的效果,但跟fcntl()有什么不同呢?在多线程环境中,如果使用fcntl()会多出一步操作,这样就可能形成竞争;而使用accept4()就可以直接在打开的文件描述符上设置,可以消除竞争的问题。(原则上该竞争在那些新建文件描述符的调用中都存在,所以很多linux的系统调用都做了类似的处理。)

  可以知道两个函数最主要的区别其实是在SOCK_CLOEXEC上,现在就可以知道nginx的accept4()是干嘛用的了。其实nginx主要是多进程的,加上该函数并没有做多余的处理:

1
2
3
4
5
6
7
8
9
#if (NGX_HAVE_ACCEPT4)
if (use_accept4) {
s = accept4(lc->fd, (struct sockaddr *) sa, &socklen,SOCK_NONBLOCK);
} else {
s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
}
#else
s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
#endif

只是设置了SOCK_NONBLOCK标志位,而在其后又调用fcntl()去设置O_NONBLOCK标志位。另外,对新建的TCP套接字压根没用SOCK_CLOEXEC,也是因为这些套接字都只是在一个进程中创建、使用和销毁,并未涉及到多进程。当然,nginx对那些打开的文件还是应用了FD_CLOEXEC的。

参考:

  1. stack overflow: what the difference between accept4 and accept
  2. man page: accept
  3. man page: fcntl
  4. man page: open
  5. nginx中http request处理的流程