1、I/O复用的含义:
一个单进程、单线程的服务器程序同时监听多个文件描述符上是否有关注的事件发生,如果某些文件描述符上有事件发生,则程序接着处理有事件发生的文件描述符,没有事件发生的文件描述符则不予理会;这样就可以极大的提高程序的性能。
I/O模型:
同步I/O:I/O数据读写 由应用程序自己完成,内核向应用程序通知的是就绪事件;
异步I/O:I/O数据读写 由内核完成,内核向应用程序通知的是完成事件;
2、I/O复用的方式:
select poll epoll
3、select函数用法:
int select(int maxFd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
返回值:-1 失败 0 超时 >0 就绪的文件描述符个数
maxFd : 最大的文件描述符的值+1
readfds、writefds、exceptfds : 分别指向可读、可写、异常事件的文件描述符集合,并在 select 调用时,将用户关注的文件描述符传递给系统内核;select 返回时,内核也是通过在线修改这三个变量,来通知应用程序那些文件描述符就绪;
timeout: 指定超时时间(NULL表示永久阻塞,直到有文件描述符上事件发生)
操作 fd_set 结构的四个宏函数:
FD_ZERO(fd_set *fds); 初始化 清空
FD_SET(int fd, fd_set *fds); 将fd设置到 fds 上
FD_CLR(int fd, fd_set *fds); 清除 fds 上的 fd
FD_ISSET(int fd, fd_set *fds); 判断 fds 上的 fd 文件描述符是否有事件发生
struct fd_set{long fdsets[32]}; //按位标识关注的文件描述符
4、poll函数的用法:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds: 指向一个结构体数组
nfds: 结构体数组的长度
timeout: 超时时间 -1 位永久阻塞
返回值:-1 失败 0 超时 >0 就绪的文件描述符个数
struct pollfd
{
int fd; //关注的文件描述符
short events; //关注的事件类型
short revents; //由内核填充,poll 返回时,其返回就绪的事件类型
};
5、epoll函数的用法: 一组函数
int epoll_create(int size); // 创建一个内核事件表,保存用户关注的文件描述符及事件类型
(select: fd_set read; //用户空间)
(poll: struct pollfd fds[n]; // 用户空间)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作内核事件表
op: EPOLL_CTL_ADD EPOLL_CTL_DEL EPOLL_CTL_MOD
struct epoll_event
{
uint32_t events; //用户关注的事件
epoll_data_t data;
};
union epoll_data_t
{
int fd; // 用户关注的文件描述符
void *ptr;
uint32_t u32;
uint64_t u64;
};
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
返回值: 0 超时 -1 失败 >0 就绪文件描述符的个数
events: 指向一个用户传递的数组,用于保存内核返回的就绪事件(文件描述符+事件类型)
maxevents:数组的长度
timeout: 超时时间 -1 位永久阻塞
6、I/O复用函数的比较:
1.select 只能关注三种事件类型,poll 和 epoll 关注的事件类型更多;
2.select 通过参数传递用户的事件,也通过其参数返回内核检测到的就绪事件,
每次调用 select 之前都必须重新设置参数;
poll 将用户关注的事件类型和内核返回的事件类型由两个变量表示;
epoll 直接将用户关注的事件保存到内核事件表上,每次 epoll_wait 返回,
将就绪的事件拷贝到用户传递的数组 上;
3.select 和 poll 每次调用都需要两次数据的全拷贝
(调用时,从用户态拷贝到内核态,返回时,从内核态拷贝回用户态);
epoll_wait 函数不需要将用户数据从用户态拷贝到内核态,仅返回时,将内核就绪的事件拷贝回用户态;
4.select 和 poll 返回所有的文件描述符,而 epoll 仅返回就绪的事件,
所以用户程序检测就绪事件文件描述符的事件复杂度分别为 O(n), O(1);
5.select 最多关注 1024 个文件描述符, poll 和 epoll 是由系统资源限制;
6.内核实现上, select 和 poll 采用轮询的方式检测就绪事件,epoll 采用回调的方式;
7.select 和 poll 只能工作在 LT 模式下, epoll 支持高效的 ET 模式;
7、epoll 的 LT 和 ET 的区别:
LT 模式:(电平触发) 事件就绪后,通知应用程序,可以不立即处理或者不处理完这个事件, epoll_wait 下次调用时,还会通知该事件;
ET模式:(边沿触发) 事件就绪后,通知应用程序,要求必须立即处理并且一次处理完该事件,否则,下次调用 epoll_wait 不会再通知该事件;
1.同一个事件,ET 比 LT 模式会少触发 epoll_wait 的返回;
2.内核事件表:
rbd(root) --> 保存用户关注的文件描述符及相应的事件类型;
rdlist --> 内核检测到事件就绪后,通过调用 回调函数,将就绪事件添加到 rdlist 上
1)将 rdlist 中数据移动到 txlist 中,rdlist 就为空;
2)通过 txlist 将就绪的事件拷贝给用户数组;
3)将 txlist 中移动到 rdlist 中 --> 文件描述符没有关注 EPOLLET 事件;
8、业务并发:
1.多进程、多线程
2.进程池、线程池
9、服务器框架
1.服务器模型:
C/S :(C:客户端 S:服务器)数据垄断 ,适用于文件传输,百度网盘;
P2P:网络中所有的主机都是对等的 适应于用户之间数据传递;
2.两种高效事件处理模式:
Reactor: 主线程仅仅监听文件描述符上的事件,当事件发生,直接将事件交付工作线程,接受链接、数据接受、数据处理、应答数据 都是由工作线程完成;
Proctor: 主线程监听文件描述符上的事件,当事件发生时,主线程处理接受 I/O 事件(接收新的连接、接收数据、发送数据),数据接收后,将数据传递给工作线程,工作线程负责对接收数据的业务逻辑处理;
3.两种并高效发模式:
半同步/半异步:进程、线程的同步异步 (异步进程线程的内核通知机制:信号 中断);
领导者/追随者:领导者线程、追随者线程,同一时刻只会有一个领导者,但是有 n 个追随者;
领导者负责监听所有的文件描述符上是否有事件发生,如果有:
1.在所有的追随者中推荐一个新的领导者;
2.原来的领导者开始处理事件;
3.事件处理完成后,随即变为追随者阻塞在线程池中;
10、libevent : I/O框架库的组件:
1.句柄: I/O事件的文件描述符 信号值 NULL(定时事件)
2.事件处理器和具体的事件处理器 :句柄、事件类型、回调函数
3.事件多路分发器 :句柄+事件类型 I/O复用
4.Reactor: 函数接口 适配器
register_handle()
remove_headle()
headle_event()
memcached: salb :申请的内存划分成一些桶,桶中按内存划分大小相等的块;