文件描述符
简介
文件描述符:File Descriptor,简称FD,当应用程序请求内核打开/新建一个文件时,内核会返回一个文件描述符用于对应这个打开/新建的文件。FD本质上就是一个非负整数的索引值。
每一个进程只能看到自己的文件描述符,每个进程的文件描述符的编号都是从0开始,进程启动,默认都会打开标准输入(fd=0),标准输出(fd=1),标准错误这三个文件(fd=2),之后再打开的文件的描述符从编号3开始
最大个数限制
按照对于文件描述符概念的理解,其最大个数限制应该取决于系统资源的使用情况。但是内核通常会有系统级限制,对单个进程打开最大文件数做限制(通常是1024,可以通过 ulimit -n
命令查看)
在使用完文件描述符之后需要将其释放(close函数)给操作系统,否则文件描述符将一直存在。
操作函数
1 | sizeof(struct fd_set) = 1024 // 按bit操作 |
网络文件描述符
在网络编程中,对于服务器端有两类文件描述符:监听文件描述符(Listen FD)
通信文件描述符(Communication FD)
,而对于每一种文件描述符又对应两个读写缓冲区:Read Buffer、Write Buffer
Listen FD | Communication FD | |
---|---|---|
作用 | 监听客户端的连接请求,检测到之后调用 accept 就可以建立新的连接 | 负责和建立连接的客户端数据通信 |
个数 | 1个 | 取决于与服务器建立连接的客户端(N个) |
流程 | 调用accept函数,监听FD的读缓冲区是否有数据。 - 有数据说明客户端有请求,接触阻塞,建立连接 - 没有数据,阻塞,继续监听 |
1. 发送数据(write / send) 将数据写入对应的文件描述符对应的写缓冲区,内核检测到写缓冲区有数据,将数据发送到客户端 2. 接收数据(read / recv) 始终监听对应的文件描述符的读缓冲区,当检测到有数据,读出! |
IO多路复用方法对比
select | poll | epoll | |
---|---|---|---|
底层原理 | 线性表(轮询) | 线性表(轮询) | 红黑树(事件通知触发) |
效率 | 较低 | 较低 | 最高 |
连接上限 | 1024 | 取决于系统 | 取决于系统 |
平台限制 | 跨平台(linux / window / mac…) | linux | linux |
select函数
1 |
|
参数
nfds(指示内核需要检测最大文件描述符个数+1)
select默认最大检测个数是1024,这是由于内核进程最大可以维护1024个文件描述符(可以修改)。但是指定第一个参数select函数在遍历文件描述符的时候不用全部遍历,只需要遍历nfds个即可。在windows中,这个参数是无效的,指定-1即可
readfds(读缓冲区的文件描述符的个数)
只检测这个文件描述符的读缓冲区是否可读
writefds(写缓冲区的文件描述符的个数)
只检测这个文件描述符的读缓冲区是否可写
exceptfds(需要异常检测文件描述符的个数)
timeout(超时时长)
该函数本身是阻塞的,当加入timeout参数之后即使没有可读、可写、异常的文件描述符,达到timeout时长函数也将解除阻塞返回。当被设置为NULL,将阻塞直至检测到文件描述符状态改变
返回值
readfds、writefds、execptfds是传入传出参数。传入fd_set类型指针,最终可读、可写、异常的文件描述符同样写回用户传入的指针指向的空间内(一定小于等于传入文件描述符的个数)
- 大于0:表示三个集合中总共被置位的位数之和
- 等于0:timeout=0 && 三个集合没有被检测到
- 小于0:select出错,根据errno判断出错原因
流程
- 初始化read、write、except三个文件描述符集合(FD_ZERO)
- 将文件描述符设置到select函数中,交付内核检测
- 内核首先一份需要检测的文件描述符,在检测对应的读写缓冲区
- 当检测到可读/可写将文件描述符对应的标志位写回fd_set对应的文件描述符集合中
- 用户处理
poll函数
1 |
|
参数
- struct pollfd fds
- fd:被检测的文件描述符
- events:委托给内核检测的事件
- revents:实际发生的事件,内核的返回值
- nfds:fds数组中的结构体数量。
POLLIN
:表示有数据可读(输入事件)。POLLOUT
:表示可以写数据(输出事件)。POLLERR
:表示发生错误。POLLHUP
:表示发生挂起事件。POLLNVAL
:表示文件描述符无效。
- timeout::指定超时时间(ms)
- -1:永久阻塞,直到有事件发生。
- 0:立即返回,无论是否有事件发生。
- 大于0:表示超时时间,poll函数会等待指定的毫秒数,如果超过该时间还没有事件发生,则返回。
epoll
流程
- 创建epoll实例(epoll_create)
- 注册文件描述符和事件(epoll_ctl)
- 等待事件发生(epoll_wait)
- 处理事件(自定义)
相关函数及结构体
epoll_create
1
2
3
int epoll_create(int size);- 作用:创建一个epoll实例,也就是一颗红黑树
- 参数size:Linux 2.6.8之后这个参数被忽略,大于0即可。之前的版本该参数指定了红黑树节点个数
- 返回值:一个epoll实例
epoll_ctl
1
2
3
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);- 作用:将要监视的文件描述符和关注的事件注册到epoll实例中
- 参数:
- epfd:epoll实例,即通过epoll_create创建出来的返回值
- op:操作类型,可以是
EPOLL_CTL_ADD
(添加文件描述符)、EPOLL_CTL_MOD
(修改文件描述符)或EPOLL_CTL_DEL
(删除文件描述符) - fd:要操作的文件描述符
- event:指定关注的事件类型(见下文)
epoll_wait
1
2
3
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);作用:等待事件的发生,该函数会阻塞程序执行,直到有事件发生或超时。
参数
- epfd:epoll_create实例的文件描述符
- events::指向struct epoll_event结构体数组的指针,用于存储事件的结果(传出参数)
- maxevents:events数组的大小,表示最大可以存储多少个事件。
- timeout:等待超时时间(ms)。传入-1表示永久等待,传入0表示立即返回,传入正整数表示等待指定的毫秒数。
struct epoll_event
1
2
3
4
5
6
7
8
9
10
11
12struct epoll_event {
uint32_t events; // 事件类型
epoll_data_t data; // 用户自定义数据
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;- 参数
- events:事件类型,EPOLLIN / EPOLLOUT / EPOLLRDHUP / EPOLLPRI / EPOLLERR
- data:用户自定义数据(联合体结构,只能使用一个,通常使用fd)。这个参数必须是用户来指定,最终返回的时候,可以根据传入参数判断文件描述符的index或者其它数据类型
- 参数