4 accept()
用于接收监听套接字中保存的套接字连接。它提取监听套接字 sockfd 的挂起连接队列上的第一个连接请求,创建一个新的已连接套接字,并返回引用该套接字的新文件描述符。accept()执行的系统调用并不会对原始的监听套接字产生任何其他影响。
4.1 包含头文件
#include <sys/types.h>
#include <sys/socket.h>
4.2 函数主体
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
参数解释:
- int sockfd
参数sockfd必须是指向执行listen()调用以后的监听套接字
- struct sockaddr *addr
参数 addr 是指向 sockaddr 结构的指针。 此结构用已知的对等套接字的地址填充。返回地址 addr 的确切格式由套接字的地址系列决定(在socket()函数中指定的)。需要注意的是,该地址返回的是客户端的外网IP,而客户端使用的IP一般均为内网IP,所以我们根据这个地址返回的IP是访问不到客户端主机的。如果addr设置为NULL时,表示不关心客户端的地址,addrlen也应该设置为NULL。
- socklen_t *addrlen
addrlen类一个特殊的参数,传入参数为调用者提供的缓存区,而传出参数为客户端地址结构体的实际长度,因此,每次调用accept()之前都应该为该参数赋初值。如果提供的缓冲区太小,则返回的地址将被截断;在这种情况下,addrlen 将返回一个大于提供给调用的值。
- int flags
这个参数时accept4()所特有的,当flags = 0时,accept()和accept4()是完全相同的。但该参数还可以传递以下两个参数,以实现对socket()的进一步控制:SOCK_NONBLOCK、SOCK_CLOEXECS,这两个参数的含义在第1小节socket()中已经说明,此处不再赘述。
对accept()作进一步补充:
- accept()为阻塞型函数,如果队列上不存在挂起的连接,并且套接字未标记为非阻塞,则 accept() 将阻塞调用进程,直到存在连接为止。 如果套接字标记为非阻塞,并且队列上不存在挂起的连接,则 accept() 将失败,并显示错误 EAGAIN 或 EWOULDBLOCK。
- 如果不想设置套接字为非阻塞但又需要在调用accept()时避免对进程造成阻塞,则可以选择使用 select()、poll() 或 epoll(),这些函数会有新连接传入是通知调用者有可读事件,然后就可以调用accept()函数了。对于select()、poll() 或 epoll()函数会在IO复用专栏进行说明。
4.3 返回值
调用成功时函数返回一个非负整数,该整数是已接受套接字的文件描述符。调用失败时,返回-1,错误类型如下:
错误类型 | 解释 |
---|---|
EAGAIN或者EWOULDBLOCK | 套接字被标记为非阻塞,且当前没有可接收的连接. |
EBADF | 描述符非法 |
ENOTSOC | 描述符指向一个文件,而不是一个套接字 |
EOPNOTSUPP | 作为参数的套接字不是 **SOCK_STREAM.**类型 |
EFAULT | 参数 addr 不在用户可写地址空间之内. |
EPERM | 防火墙规则禁止连接 |
ENOBUFS,ENOMEM | 没有足够内存. 这个错误一般来说意味着内存分配受套接字缓冲区所限, 而不是没有系统内存. |
EINVAL | 非监听套接字,或者 addrlen 无效(例如,为负数)。 |
ECONNABORTED | 连接以中止 |
4.4 结束
在 Linux 上,accept() 返回的新套接字不会从侦听套接字继承文件状态标志,如O_NONBLOCK和O_ASYNC。若对返回的新套接字有相关要要求,需要重新设置所有必需的标志。本接提到的内容代码实现可以参考第2节bind()中代码。
另外,最后对可能出现所有错误类新做一个总结,可以在文件 /usr/include/sys/errno.h 中找到 Errno。