常见IO模型和epoll O(1)实现原理
来源:互联网 发布:谭浩强c语言第四版答案 编辑:程序博客网 时间:2024/05/02 00:10
同步和阻塞
同步:发送方发送请求后,需要收到响应后才能接着发送下一步请求;
阻塞;通常针对网络套接字socket,调用结果返回前,当前线程一直挂起等待;
同步针对调用方,阻塞针对接受方;
同步非阻塞:发送方发出请求后一直等待,接受方处理请求时立即返回,不用等待执行结果;
异步阻塞:发送方发出请求后马上返回,接受方处理请求期间一直等待,直到返回执行结果;
nginx工作进程采用了异步非阻塞,即执行请求时,工作进程和客户端都无需等待响应;
那么问题来了,当IO请求完成时,如何通知工作进程?
有两种方式:
1 worker定期检查IO运行状态
2 IO在完成后主动通知worker,即采用事件驱动,允许一个进程同时处理多个请求;
事件驱动
包含事件收集器,事件发送器和事件处理器,重点是事件处理器,通常有3种模式;
基于进程的,基于线程的,基于非阻塞IO的(每接受一个请求,将其放入待处理事件的列表,使用非阻塞IO方式调用事件处理器来处理请求,形成事件驱动处理库)
基于非阻塞IO常见的有select()和epoll(),然而两者皆有不足,尤其在高并发连接时。
select():描述符总数有限制(1024个);读/写/异常各对应一个描述符;O(n)
poll():读/写/异常集合到一个描述符中;描述符总数没有限制;O(n)
而epoll()很大程度上解决了上述问题。
epoll优势
为了避免每次扫描所有fd,epoll引入两个数据结构,
红黑树--存储socket fd
ready list--当socket上有事件发生时,将其加入此列表
epoll每次只扫描ready list,直接跳过那些不符合条件socketfd,算法复杂度为O(1);
epoll在初始化时调用kmem_cache_create申请内核内存(采用slab机制),用于创建epitem和eppoll_entry;
epoll对外只提供epoll_create/ctl/wait 3个API,原型如下:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll_create(): 在内核cache里创建上述红黑树和ready list;
epoll_ctl(): 如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上;然后向内核注册回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里;
epoll_wait(): 与select()等效,即返回ready list中的数据,将fd从内核态复制到用户态;
客户端只需调用epoll_ctl(),负责把socket fd加入epoll监控;
sockfd-1
|
|
sockfd-2 e
| p -----------> wait_queue(process)
| -----------> o
sockfd-3 o
| -----------> l
| f -----------> ready_queue(ready events)
..... d
sockfd-N-2
|
|
sockfd-N-1
|
|
sockfd-N
伪代码
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}
再详细一点
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i)< span="" style="word-wrap: break-word;">
{
if(events[i].data.fd==listenfd) //有新的连接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
}
else if( events[i].events&EPOLLIN ) //接收到数据,读socket
{
n = read(sockfd, line, MAXLINE)) < 0 //读
ev.data.ptr = md; //md为自定义类型,添加数据
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
}
else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //发送数据
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
}
else
{
//其他的处理
}
}
}
epoll_event数据结构如下
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
注:events有多种类型,包括EPOLLIN/EPOLLOUT/EPOLLERR/EPOLLET
参考资料
https://gist.github.com/fisheuler/085890c9dff4547b0aa3
http://blog.csdn.net/russell_tao/article/details/7160071
同步:发送方发送请求后,需要收到响应后才能接着发送下一步请求;
阻塞;通常针对网络套接字socket,调用结果返回前,当前线程一直挂起等待;
同步针对调用方,阻塞针对接受方;
同步非阻塞:发送方发出请求后一直等待,接受方处理请求时立即返回,不用等待执行结果;
异步阻塞:发送方发出请求后马上返回,接受方处理请求期间一直等待,直到返回执行结果;
nginx工作进程采用了异步非阻塞,即执行请求时,工作进程和客户端都无需等待响应;
那么问题来了,当IO请求完成时,如何通知工作进程?
有两种方式:
1 worker定期检查IO运行状态
2 IO在完成后主动通知worker,即采用事件驱动,允许一个进程同时处理多个请求;
事件驱动
包含事件收集器,事件发送器和事件处理器,重点是事件处理器,通常有3种模式;
基于进程的,基于线程的,基于非阻塞IO的(每接受一个请求,将其放入待处理事件的列表,使用非阻塞IO方式调用事件处理器来处理请求,形成事件驱动处理库)
基于非阻塞IO常见的有select()和epoll(),然而两者皆有不足,尤其在高并发连接时。
select():描述符总数有限制(1024个);读/写/异常各对应一个描述符;O(n)
poll():读/写/异常集合到一个描述符中;描述符总数没有限制;O(n)
而epoll()很大程度上解决了上述问题。
epoll优势
为了避免每次扫描所有fd,epoll引入两个数据结构,
红黑树--存储socket fd
ready list--当socket上有事件发生时,将其加入此列表
epoll每次只扫描ready list,直接跳过那些不符合条件socketfd,算法复杂度为O(1);
epoll在初始化时调用kmem_cache_create申请内核内存(采用slab机制),用于创建epitem和eppoll_entry;
epoll对外只提供epoll_create/ctl/wait 3个API,原型如下:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll_create(): 在内核cache里创建上述红黑树和ready list;
epoll_ctl(): 如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上;然后向内核注册回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里;
epoll_wait(): 与select()等效,即返回ready list中的数据,将fd从内核态复制到用户态;
客户端只需调用epoll_ctl(),负责把socket fd加入epoll监控;
sockfd-1
|
|
sockfd-2 e
| p -----------> wait_queue(process)
| -----------> o
sockfd-3 o
| -----------> l
| f -----------> ready_queue(ready events)
..... d
sockfd-N-2
|
|
sockfd-N-1
|
|
sockfd-N
伪代码
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}
再详细一点
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i)< span="" style="word-wrap: break-word;">
{
if(events[i].data.fd==listenfd) //有新的连接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中
}
else if( events[i].events&EPOLLIN ) //接收到数据,读socket
{
n = read(sockfd, line, MAXLINE)) < 0 //读
ev.data.ptr = md; //md为自定义类型,添加数据
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
}
else if(events[i].events&EPOLLOUT) //有数据待发送,写socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //发送数据
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据
}
else
{
//其他的处理
}
}
}
epoll_event数据结构如下
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
注:events有多种类型,包括EPOLLIN/EPOLLOUT/EPOLLERR/EPOLLET
参考资料
https://gist.github.com/fisheuler/085890c9dff4547b0aa3
http://blog.csdn.net/russell_tao/article/details/7160071
0 0
- 常见IO模型和epoll O(1)实现原理
- 常见IO模型和epoll O(1)实现原理
- IO模型(select, poll, epoll的区别和原理)
- epoll模型的实现原理
- epoll和select I/O模型研究
- IO模型和Select/Poll/Epoll解析
- 网络io模型:epoll
- IO多路复用 epoll模型
- 从零开始写select和epoll I/O多路复用网络模型
- Linux下select, poll和epoll IO模型的详解
- IO模型及select、poll、epoll和kqueue的区别
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux下select, poll和epoll IO模型的详解
- Linux虚拟机中IntelliJ IDEA的激活方法
- 44. Wildcard Matching
- 打开联系人获取手机号,一个联系人对应多个手机号获取方式
- Android酷炫实用的开源框架
- 扫描文件夹
- 常见IO模型和epoll O(1)实现原理
- Android怎样避免内存泄漏的问题,不断补充中
- USB3.0规范中译本 第一章 引言
- oracle数据泵导入导出
- 435. Non-overlapping Intervals
- 标题 node第一个服务器程序
- Java开发中的23种设计模式详解
- VC6编译错误总结
- Android SD卡简单的文件读写操作