!!! tip I/O复用基本概念

  • I/O(多路)复用,采用单个进/线程就可以管理多个socket。
  • I/O复用有三种方案:select,poll,epoll,各有优缺点,各有应用场景,必须全部掌握。
    !!!

select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// select函数详解    头文件::sys/select.h
// 头文件::
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

// fd_set (socket的集合);
void FD_CLR(int fd, fd_set *set); //清除集合中的一个socket
int FD_ISSET(int fd, fd_set *set); //判断socket是否在集合中,失败返回0
void FD_SET(int fd, fd_set *set); //加一个socket到集合中
void FD_ZERO(fd_set *set); //初始化socket集合

// select函数中的socket集合应为副本,因为select函数会修改socket集合;
int select(int maxfdp, fd_set *readfds, fd_set*writefds, fd_set *errorfds, struct timeval *timeout);
// int maxfdp::是指集合中所有文件描述符的范围;必须加1;
// fd_set *readfds::(读的socket集合)socket的集合副本(select函数会改变socket集合里的内容所以必须是副本,否则达不到预期的效果)
// fd_set *writefds::(写的socket集合)一般不用
// fd_set *errorfds::监视产生异常的socket集合(一般不用)
// struct timeval *timeout::超时的机制

struct timeval(结构体原型)
{
long tv_sec; //秒
long tv_usec; //微妙
}
// 调用结构体直接给秒或微妙赋值,传给select结构体的地址,超过时间select函数返回0;
// select的返回值::
// 成功返回socket发生事件的个数;
// 超时返回0;
// 失败返回-1;

// select水平触发
// select采用“水平触发”的方式,如果报告了fd后事件没有被处理或数据没有被全部读取,那下次select时会再次报告该fd事件
  • select缺点
    1. select支持的文件描述符数量太小,默认是1024,虽然可以调整,但是,描述符数量越大,效率将更低,调整的意义不大。
    2. 每次调用select,都需要把fdset从用户态拷贝到内核。
    3. 同时在线的大量客户端有事件发生的可能很少,但还是需要遍历fdset,因此随着监视的描述符数量的增长,其效率也会线性下降。

poll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// poll函数详解    头文件#include <poll.h>
int poll(struct pollfd fd[], nfds_t nfds, int timeout)
// 第一个参数:一个结构体数组
// 第二个参数nfds:要监视的描述符的数目。
// 第三个参数timeout:是一个用微秒表示的时间,超时机制,如果他的值为-1,poll就永远不会超时
// poll函数返回的是有事件的描述符的个数

// struct pollfd结构如下:
struct pollfd
{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回该socket有无发生事件
};
// int fd:: socket的存储,类似于select的maxfd , fd初始化为-1
// short events:: 主要关注两个指标(POLLIN,POLLOUT) 把它的值设为POLLIN表示有数据可读事件
// shour revents:: 返回值为0表示无事件,大于零表示有事件(一般为1);

epoll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// epoll函数详解    头文件 #include《sys/epoll.h》
// 共三个函数 epoll_create, epoll_ctl, epoll_wait3个系统调用函数;

int epoll_create(int size);
// int size::大于0即可;
// 创建一个epoll的描述符。需要注意的是epoll描述符会占用一个fd值,在使用完后务必使用close()将其关闭,否则可能导致fd被耗尽;
// 该函数返回一个fd;

// epoll的事件注册函数。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event*event);
// int epfd::是epoll_create()的返回值;
// int op::表示动作,用三个宏来表示::
// EPOLL_CTL_ADD::注册新的fd到epfd中;
// EPOLL_CTL_MOD::修改一个事件的监听方式
// EPOLL_CTL_DEL::从epfd中删除一个fd;
// int fd::是需要添加的socket;
// struct epoll_event* event::是告诉内核需要监听什么事,struct epoll_event结构体详情如下::
struct epoll_event (绿色的是重点,其他的意义不大)
{
_uint32_t events;
epoll_data_t data;
// events 的赋值主要是EPOLLIN,EPOLLOUT;
// EPOLLIN::表示对应的文件描述符可以读;
// EPOLLOUT::表示对应的文件符可以写;
// EPOLLET:表示开启边缘触发模式in;
}
typedef union epoll_data //epoll_event内定义的结构体(epoll_data_t data
{
void *ptr;
int fd; //文件描述符
_uint32_t u32;
_uint64_t u64;
}

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
// int epfd::由epoll_create生成的epoll专用的文件描述符;
// struct epoll_event::用于回传带处理事件的数组;
// maxevents::每次能处理的事件数;
// timeout::超时机制;
// 返回值::
// 成功:返回发生的事件数
// 失败:-1;

// epoll的水平触发和边缘触发
// 水平触发::如果报告了fd事件没有被处理或数据没有被全部读取,那么epoll会立刻在报告该fd。
// 边缘触发::如果报告了fd事件没有被处理或数据没有被全部读取,那么epoll会在下次再报告该fd。

int setnonblocking(int fd);
// fd: 要设置为非阻塞模式的文件描述符, 监听的socket
// 返回值::
// 成功:0
// 失败:-1,并设置 errno 以指示错误;