创建若干 socketfd
,绑定并监听,现在我们拥有若干 socketfd
事件的触发方式
accept()
得到 fd,保存到 fd 数组中,记录最大 fd 序号 max_fd
。fd_set
集合(bitmap)fd_set
传入 select()
函数,并限制监听 fd 范围(0-max_fd)fd_set
,并进行监听,发生事件时,覆盖用户态的 fd_set
select()
返回fd_set
fd_set
中是否置位,并进行处理// fd_set 全部置零
int FD_ZERO(fd_set *fdset);
// 清零某一位
int FD_CLR(int fd, fd_set *fdset);
// 置1某一位
int FD_SET(int fd, fd_set *fd_set);
// 判断是否置1
int FD_ISSET(int fd, fd_set *fdset);
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)
// 参数: 最大fd 监听读事件的fd集合 监听写事件的fd集合 监听异常的fd集合 超时时间
// 超时时间:NULL-无限等待, 0-立刻返回,n-等待时间。等待可以被中断信号打断
// select 返回准备好的 fd 数量,超时返回0,出错返回-1(等待时被信号打断也会返回-1)
fd_set
会被内核修改,每次需要重新赋值fd_set
用户态拷贝到内核态,有开销select
返回后,需要遍历所有 fd 来判断真正可用的 fdaccept()
得到 fd,为每个 fd 创建 pollfd
结构体,存入数组pollfd[]
数组传入 poll()
函数poll()
阻塞当前进程,直到返回(发生事件或超时)poll()
返回revents
revents
),并进行处理int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
// 参数 poll 数组 数组长度 超时时间
struct pollfd {
int fd;
short events; // 监听的事件(事件掩码)
short revents; // 实际发生的事件,只关注 events 设置的事件
}
// POLLIN 有数据可读
// POLLRDNORM 有普通数据可读
// POLLRDBAND 有优先数据可读
// POLLPRI 紧迫数据可读
// POLLOUT 写数据,不会导致阻塞
// POLLWRNORM 写普通数据,不会导致阻塞
// POLLWRBAND 写优先数据,不会导致阻塞
// POLLMSGSIGPOLL 消息可用
// POLLER 指定的 fd 发生错误
// POLLHUP 指定的 fd 挂起事件
// POLLNVAL 指定的 fd 非法
pollfd
可重用pollfds[]
用户态拷贝到内核态,有开销poll()
返回后,需要遍历所有 fd 来判断真正可用的 fdepoll_create()
创建 epoll
对象(eventpoll),得到描述符(epfd)accept()
得到 fd,将 fd 保存到 epoll_event
中epoll_ctl()
添加 epoll_event
到 epoll
对象中epoll_wait()
阻塞并等待事件发生epoll_ctl
拷贝一次,后续复用,不需要每次都拷贝epoll_wait()
返回可用的 fd// 创建一个 epoll 对象,返回该对象的描述符
// 注意要使用 close 关闭该描述符
int epoll_create(int size);
// 操作控制 epoll 对象
// 主要涉及 epoll 红黑树上节点的一些操作,比如添加节点,删除节点,修改节点事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op:EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD
// 阻塞一段时间并等待事件发生,返回事件集合
// 也就是获取内核的事件通知。即遍历双向链表,把双向链表里的节点数据拷贝出来,拷贝完毕后就从双向链表移除
int epoll_wait(int epid, struct epoll_event *events, int maxevents, int timeout);
// epid:epoll_create 返回的 epoll 对象描述符
// events:存放就绪的事件集合,这个是传出参数
// maxevents:代表可以存放的事件个数,也就是 events 数组的大小
// timeout:阻塞等待的时间长短,以毫秒为单位,如果传入 -1 代表阻塞等待
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; // Epoll 事件
epoll_data_t data; // 用户数据
};
// EPOLLIN: 监听 fd 的读事件。举例:如果客户端发送消息过来,代表服务器收到了可读事件
// EPOLLOUT: 监听 fd 的写事件。如果 fd 对应的发数据内核缓冲区不为满,只要监听了写事件,就会触发可写事件
// EPOLLRDHUP: 监听套接字关闭或半关闭事件,Linux 内核 2.6.17 后可用
// EPOLLPRI: 监听紧急数据可读事件
epoll
仅拷贝一次到内核态,后续复用epoll_wait()
会将当前的事件和用户传入的数据都 copy 给用户态