经销商自己做网站,做h5哪个网站好用,免费软件在线下载,织梦怎么做中英文网站From: http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll
epoll是什么#xff1f;按照man手册的说法#xff1a;是为处理大批量句柄而作了改进的poll。当然#xff0c;这不是2.6内核才有的#xff0c;它是在2.5.44内核中被引进的(epoll(4) is a …From: http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll
epoll是什么按照man手册的说法是为处理大批量句柄而作了改进的poll。当然这不是2.6内核才有的它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)它几乎具备了之前所说的一切优点被公认为Linux2.6下性能最好的多路I/O就绪通知方法。 epoll的相关系统调用
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。 1. int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后size参数是被忽略的。需要注意的是当创建好epoll句柄后它就是会占用一个fd值在linux下如果查看/proc/进程id/fd/是能够看到这个fd的所以在使用完epoll后必须调用close()关闭否则可能导致fd被耗尽。 2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数它不同于select()是在监听事件时告诉内核要监听什么类型的事件而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作用三个宏来表示
EPOLL_CTL_ADD注册新的fd到epfd中
EPOLL_CTL_MOD修改已经注册的fd的监听事件
EPOLL_CTL_DEL从epfd中删除一个fd 第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事struct epoll_event结构如下 [cpp] view plaincopyprint? //保存触发事件的某个文件描述符相关的数据与具体使用方式有关 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 */ }; //保存触发事件的某个文件描述符相关的数据与具体使用方式有关
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 表示对应的文件描述符可以读包括对端SOCKET正常关闭
EPOLLOUT表示对应的文件描述符可以写
EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来
EPOLLERR表示对应的文件描述符发生错误
EPOLLHUP表示对应的文件描述符被挂断
EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里 3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组epoll将会把发生的事件赋值到events数组中events不可以是空指针内核只负责把数据复制到这个events数组中不会去帮助我们在用户态中分配内存。maxevents告之内核这个events有多大这个 maxevents的值不能大于创建epoll_create()时的size参数timeout是超时时间毫秒0会立即返回-1将不确定也有说法说是永久阻塞。如果函数调用成功返回对应I/O上已准备好的文件描述符数目如返回0表示已超时。 epoll工作原理
epoll同样只告知那些就绪的文件描述符而且当我们调用epoll_wait()获得就绪文件描述符时返回的不是实际的描述符而是一个代表就绪描述符数量的值你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可这里也使用了内存映射mmap技术这样便彻底省掉了这些文件描述符在系统调用时复制的开销。 另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中进程只有在调用一定的方法后内核才对所有监视的文件描述符进行扫描而epoll事先通过epoll_ctl()来注册一个文件描述符一旦基于某个文件描述符就绪时内核会采用类似callback的回调机制迅速激活这个文件描述符当进程调用epoll_wait()时便得到通知。 Epoll的2种工作方式-水平触发LT和边缘触发ET 假如有这样一个例子
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2)并且它会返回RFD说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)...... Edge Triggered 工作模式
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志那么在第5步调用epoll_wait(2)之后将有可能会挂起因为剩余的数据还存在于文件的输入缓冲区内而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中会有一个事件产生在RFD句柄上因为在第2步执行了一个写操作然后事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据因此我们在第5步调用 epoll_wait(2)完成后是否挂起是不确定的。epoll工作在ET模式的时候必须使用非阻塞套接口以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口在后面会介绍避免可能的缺陷。 i 基于非阻塞文件句柄 ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起等待。但这并不是说每次read()时都需要循环读直到读到产生一个EAGAIN才认为此次事件处理完成当read()返回的读到的数据长度小于请求的数据长度时就可以确定此时缓冲中已没有数据了也就可以认为此事读事件已处理完成。 Level Triggered 工作模式
相反的以LT方式调用epoll接口的时候它就相当于一个速度比较快的poll(2)并且无论后面的数据是否被使用因此他们具有同样的职能。因为即使使用ET模式的epoll在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。 LT(level triggered)是epoll缺省的工作方式并且同时支持block和no-block socket.在这种做法中内核告诉你一个文件描述符是否就绪了然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作内核还是会继续通知你 的所以这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表 ET (edge-triggered)是高速工作方式只支持no-block socket它效率要比LT更高。ET与LT的区别在于当一个新的事件到来时ET模式下当然可以从epoll_wait调用中获取到这个事件可是如果这次没有把这个事件对应的套接字缓冲区处理完在这个套接字中没有新的事件再次到来时在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反只要一个事件对应的套接字缓冲区还有数据就总能从epoll_wait中获取这个事件。
因此LT模式下开发基于epoll的应用要简单些不太容易出错。而在ET模式下事件发生时如果没有彻底地将缓冲区数据处理完则会导致缓冲区中的用户请求得不到响应。
图示说明 Nginx默认采用ET模式来使用epoll。 epoll的优点
1.支持一个进程打开大数目的socket描述符(FD) select 最不能忍受的是一个进程所打开的FD是有一定限制的由FD_SETSIZE设置默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核不过资料也同时指出这样会带来网络效率的下降二是可以选择多进程的解决方案(传统的 Apache方案)不过虽然linux上面创建进程的代价比较小但仍旧是不可忽视的加上进程间数据同步远比不上线程间同步的高效所以也不是一种完美的方案。不过 epoll则没有这个限制它所支持的FD上限是最大可以打开文件的数目这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 2.IO效率不随FD数目增加而线性下降 传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合不过由于网络延时任一时间只有部分的socket是活跃的但是select/poll每次调用都会线性扫描全部的集合导致效率呈现线性下降。但是epoll不存在这个问题它只会对活跃的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么只有活跃的socket才会主动的去调用 callback函数其他idle状态socket则不会在这点上epoll实现了一个伪AIO因为这时候推动力在os内核。在一些 benchmark中如果所有的socket基本上都是活跃的---比如一个高速LAN环境epoll并不比select/poll有什么效率相反如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。 3.使用mmap加速内核与用户空间的消息传递 这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间如何避免不必要的内存拷贝就很重要在这点上epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话一定不会忘记手工 mmap这一步的。 4.内核微调
这一点其实不算epoll的优点了而是整个linux平台的优点。也许你可以怀疑linux平台但是你无法回避linux平台赋予你微调内核的能力。比如内核TCP/IP协议栈使用内存池管理sk_buff结构那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度)也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。 linux下epoll如何实现高效处理百万句柄的
开发高性能网络程序时windows开发者们言必称iocplinux开发者们则言必称epoll。大家都明白epoll是一种IO多路复用技术可以非常高效的处理数以百万计的socket句柄比起以前的select和poll效率高大发了。我们用起epoll来都感觉挺爽确实快那么它到底为什么可以高速处理这么多并发连接呢 使用起来很清晰首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数多于这个最大数时内核可不保证效果。 epoll_ctl可以操作上面建立的epoll例如将刚建立的socket加入到epoll中让其监控或者把 epoll正在监控的某个socket句柄移出epoll不再监控它等等。 epoll_wait在调用时在给定的timeout时间内当在监控的所有句柄中有事件发生时就返回用户态的进程。 从上面的调用方式就可以看到epoll比select/poll的优越之处因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用这意味着需要将用户态的socket列表copy到内核态如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态非常低效。而我们调用epoll_wait时就相当于以往调用select/poll但是这时却不用传递socket句柄给内核因为内核已经在epoll_ctl中拿到了要监控的句柄列表。 所以实际上在你调用epoll_create后内核就已经在内核态开始准备帮你存储要监控的句柄了每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。 当一个进程调用epoll_creaqte方法时Linux内核会创建一个eventpoll结构体这个结构体中有两个成员与epoll的使用方式密切相关 [cpp] view plaincopyprint? /* 171 * This structure is stored inside the private_data member of the file 172 * structure and represents the main data structure for the eventpoll 173 * interface. 174 */ 175struct eventpoll { 176 /* Protect the access to this structure */ 177 spinlock_t lock; 178 179 /* 180 * This mutex is used to ensure that files are not removed 181 * while epoll is using them. This is held during the event 182 * collection loop, the file cleanup path, the epoll file exit 183 * code and the ctl operations. 184 */ 185 struct mutex mtx; 186 187 /* Wait queue used by sys_epoll_wait() */ 188 wait_queue_head_t wq; 189 190 /* Wait queue used by file-poll() */ 191 wait_queue_head_t poll_wait; 192 193 /* List of ready file descriptors */ 194 struct list_head rdllist; 195 196 /* RB tree root used to store monitored fd structs */ 197 struct rb_root rbr;//红黑树根节点这棵树存储着所有添加到epoll中的事件也就是这个epoll监控的事件 198 199 /* 200 * This is a single linked list that chains all the struct epitem that 201 * happened while transferring ready events to userspace w/out 202 * holding -lock. 203 */ 204 struct epitem *ovflist; 205 206 /* wakeup_source used when ep_scan_ready_list is running */ 207 struct wakeup_source *ws; 208 209 /* The user that created the eventpoll descriptor */ 210 struct user_struct *user; 211 212 struct file *file; 213 214 /* used to optimize loop detection check */ 215 int visited; 216 struct list_head visited_list_link;//双向链表中保存着将要通过epoll_wait返回给用户的、满足条件的事件 217}; /*
171 * This structure is stored inside the private_data member of the file
172 * structure and represents the main data structure for the eventpoll
173 * interface.
174 */
175struct eventpoll {
176 /* Protect the access to this structure */
177 spinlock_t lock;
178
179 /*
180 * This mutex is used to ensure that files are not removed
181 * while epoll is using them. This is held during the event
182 * collection loop, the file cleanup path, the epoll file exit
183 * code and the ctl operations.
184 */
185 struct mutex mtx;
186
187 /* Wait queue used by sys_epoll_wait() */
188 wait_queue_head_t wq;
189
190 /* Wait queue used by file-poll() */
191 wait_queue_head_t poll_wait;
192
193 /* List of ready file descriptors */
194 struct list_head rdllist;
195
196 /* RB tree root used to store monitored fd structs */
197 struct rb_root rbr;//红黑树根节点这棵树存储着所有添加到epoll中的事件也就是这个epoll监控的事件
198
199 /*
200 * This is a single linked list that chains all the struct epitem that
201 * happened while transferring ready events to userspace w/out
202 * holding -lock.
203 */
204 struct epitem *ovflist;
205
206 /* wakeup_source used when ep_scan_ready_list is running */
207 struct wakeup_source *ws;
208
209 /* The user that created the eventpoll descriptor */
210 struct user_struct *user;
211
212 struct file *file;
213
214 /* used to optimize loop detection check */
215 int visited;
216 struct list_head visited_list_link;//双向链表中保存着将要通过epoll_wait返回给用户的、满足条件的事件
217};每一个epoll对象都有一个独立的eventpoll结构体这个结构体会在内核空间中创造独立的内存用于存储使用epoll_ctl方法向epoll对象中添加进来的事件。这样重复的事件就可以通过红黑树而高效的识别出来。 在epoll中对于每一个事件都会建立一个epitem结构体 [cpp] view plaincopyprint? /* 130 * Each file descriptor added to the eventpoll interface will 131 * have an entry of this type linked to the rbr RB tree. 132 * Avoid increasing the size of this struct, there can be many thousands 133 * of these on a server and we do not want this to take another cache line. 134 */ 135struct epitem { 136 /* RB tree node used to link this structure to the eventpoll RB tree */ 137 struct rb_node rbn; 138 139 /* List header used to link this structure to the eventpoll ready list */ 140 struct list_head rdllink; 141 142 /* 143 * Works together struct eventpoll-ovflist in keeping the 144 * single linked chain of items. 145 */ 146 struct epitem *next; 147 148 /* The file descriptor information this item refers to */ 149 struct epoll_filefd ffd; 150 151 /* Number of active wait queue attached to poll operations */ 152 int nwait; 153 154 /* List containing poll wait queues */ 155 struct list_head pwqlist; 156 157 /* The container of this item */ 158 struct eventpoll *ep; 159 160 /* List header used to link this item to the struct file items list */ 161 struct list_head fllink; 162 163 /* wakeup_source used when EPOLLWAKEUP is set */ 164 struct wakeup_source __rcu *ws; 165 166 /* The structure that describe the interested events and the source fd */ 167 struct epoll_event event; 168}; /*
130 * Each file descriptor added to the eventpoll interface will
131 * have an entry of this type linked to the rbr RB tree.
132 * Avoid increasing the size of this struct, there can be many thousands
133 * of these on a server and we do not want this to take another cache line.
134 */
135struct epitem {
136 /* RB tree node used to link this structure to the eventpoll RB tree */
137 struct rb_node rbn;
138
139 /* List header used to link this structure to the eventpoll ready list */
140 struct list_head rdllink;
141
142 /*
143 * Works together struct eventpoll-ovflist in keeping the
144 * single linked chain of items.
145 */
146 struct epitem *next;
147
148 /* The file descriptor information this item refers to */
149 struct epoll_filefd ffd;
150
151 /* Number of active wait queue attached to poll operations */
152 int nwait;
153
154 /* List containing poll wait queues */
155 struct list_head pwqlist;
156
157 /* The container of this item */
158 struct eventpoll *ep;
159
160 /* List header used to link this item to the struct file items list */
161 struct list_head fllink;
162
163 /* wakeup_source used when EPOLLWAKEUP is set */
164 struct wakeup_source __rcu *ws;
165
166 /* The structure that describe the interested events and the source fd */
167 struct epoll_event event;
168};此外epoll还维护了一个双链表用户存储发生的事件。当epoll_wait调用时仅仅观察这个list链表里有没有数据即eptime项即可。有数据就返回没有数据就sleep等到timeout时间到后即使链表没数据也返回。所以epoll_wait非常高效。 而且通常情况下即使我们要监控百万计的句柄大多一次也只返回很少量的准备就绪句柄而已所以epoll_wait仅需要从内核态copy少量的句柄到用户态而已如何能不高效 那么这个准备就绪list链表是怎么维护的呢当我们执行epoll_ctl时除了把socket放到epoll文件系统里file对象对应的红黑树上之外还会给内核中断处理程序注册一个回调函数告诉内核如果这个句柄的中断到了就把它放到准备就绪list链表里。所以当一个socket上有数据到了内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。 如此一颗红黑树一张准备就绪句柄链表少量的内核cache就帮我们解决了大并发下的socket处理问题。执行epoll_create时创建了红黑树和就绪链表执行epoll_ctl时如果增加socket句柄则检查在红黑树中是否存在存在立即返回不存在则添加到树干上然后向内核注册回调函数用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。 epoll的使用方法
那么究竟如何来使用epoll呢其实非常简单。 通过在包含一个头文件#include sys/epoll.h 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。 首先通过create_epoll(int maxfds)来创建一个epoll的句柄。这个函数会返回一个新的epoll句柄之后的所有操作将通过这个句柄来进行操作。在用完之后记得用close()来关闭这个创建出来的epoll句柄。 之后在你的网络主循环里面每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口看哪一个可以读哪一个可以写了。基本的语法为
nfds epoll_wait(kdpfd, events, maxevents, -1); 其中kdpfd为用epoll_create创建之后的句柄events是一个epoll_event*的指针当epoll_wait这个函数操作成功之后epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时为0的时候表示马上返回为-1的时候表示一直等下去直到有事件返回为任意正整数的时候表示等这么长的时间如果一直没有事件则返回。一般如果网络主循环是单独的线程的话可以用-1来等这样可以保证一些效率如果是和主逻辑在同一个线程的话则可以用0来保证主循环的效率。 epoll_wait返回之后应该是一个循环遍历所有的事件。 几乎所有的epoll程序都使用下面的框架 [cpp] view plaincopyprint? for( ; ; ) { nfds epoll_wait(epfd,events,20,500); for(i0;infds;i) { if(events[i].data.fdlistenfd) //有新的连接 { connfd accept(listenfd,(sockaddr *)clientaddr, clilen); //accept这个连接 ev.data.fdconnfd; ev.eventsEPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,ev); //将新的fd添加到epoll的监听队列中 } else if( events[i].eventsEPOLLIN ) //接收到数据读socket { n read(sockfd, line, MAXLINE)) 0 //读 ev.data.ptr md; //md为自定义类型添加数据 ev.eventsEPOLLOUT|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev);//修改标识符等待下一个循环时发送数据异步处理的精髓 } else if(events[i].eventsEPOLLOUT) //有数据待发送写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.fdsockfd; ev.eventsEPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev); //修改标识符等待下一个循环时接收数据 } else { //其他的处理 } } } for( ; ; )
{
nfds epoll_wait(epfd,events,20,500);
for(i0;infds;i)
{
if(events[i].data.fdlistenfd) //有新的连接
{
connfd accept(listenfd,(sockaddr *)clientaddr, clilen); //accept这个连接
ev.data.fdconnfd;
ev.eventsEPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,ev); //将新的fd添加到epoll的监听队列中
}
else if( events[i].eventsEPOLLIN ) //接收到数据读socket
{
n read(sockfd, line, MAXLINE)) 0 //读
ev.data.ptr md; //md为自定义类型添加数据
ev.eventsEPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev);//修改标识符等待下一个循环时发送数据异步处理的精髓
}
else if(events[i].eventsEPOLLOUT) //有数据待发送写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.fdsockfd;
ev.eventsEPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,ev); //修改标识符等待下一个循环时接收数据
}
else
{
//其他的处理
}
}
}epoll的程序实例 [cpp] view plaincopyprint? #include stdio.h #include stdlib.h #include unistd.h #include errno.h #include sys/socket.h #include netdb.h #include fcntl.h #include sys/epoll.h #include string.h #define MAXEVENTS 64 //函数: //功能:创建和绑定一个TCP socket //参数:端口 //返回值:创建的socket static int create_and_bind (char *port) { struct addrinfo hints; struct addrinfo *result, *rp; int s, sfd; memset (hints, 0, sizeof (struct addrinfo)); hints.ai_family AF_UNSPEC; /* Return IPv4 and IPv6 choices */ hints.ai_socktype SOCK_STREAM; /* We want a TCP socket */ hints.ai_flags AI_PASSIVE; /* All interfaces */ s getaddrinfo (NULL, port, hints, result); if (s ! 0) { fprintf (stderr, getaddrinfo: %s\n, gai_strerror (s)); return -1; } for (rp result; rp ! NULL; rp rp-ai_next) { sfd socket (rp-ai_family, rp-ai_socktype, rp-ai_protocol); if (sfd -1) continue; s bind (sfd, rp-ai_addr, rp-ai_addrlen); if (s 0) { /* We managed to bind successfully! */ break; } close (sfd); } if (rp NULL) { fprintf (stderr, Could not bind\n); return -1; } freeaddrinfo (result); return sfd; } //函数 //功能:设置socket为非阻塞的 static int make_socket_non_blocking (int sfd) { int flags, s; //得到文件状态标志 flags fcntl (sfd, F_GETFL, 0); if (flags -1) { perror (fcntl); return -1; } //设置文件状态标志 flags | O_NONBLOCK; s fcntl (sfd, F_SETFL, flags); if (s -1) { perror (fcntl); return -1; } return 0; } //端口由参数argv[1]指定 int main (int argc, char *argv[]) { int sfd, s; int efd; struct epoll_event event; struct epoll_event *events; if (argc ! 2) { fprintf (stderr, Usage: %s [port]\n, argv[0]); exit (EXIT_FAILURE); } sfd create_and_bind (argv[1]); if (sfd -1) abort (); s make_socket_non_blocking (sfd); if (s -1) abort (); s listen (sfd, SOMAXCONN); if (s -1) { perror (listen); abort (); } //除了参数size被忽略外,此函数和epoll_create完全相同 efd epoll_create1 (0); if (efd -1) { perror (epoll_create); abort (); } event.data.fd sfd; event.events EPOLLIN | EPOLLET;//读入,边缘触发方式 s epoll_ctl (efd, EPOLL_CTL_ADD, sfd, event); if (s -1) { perror (epoll_ctl); abort (); } /* Buffer where events are returned */ events calloc (MAXEVENTS, sizeof event); /* The event loop */ while (1) { int n, i; n epoll_wait (efd, events, MAXEVENTS, -1); for (i 0; i n; i) { if ((events[i].events EPOLLERR) || (events[i].events EPOLLHUP) || (!(events[i].events EPOLLIN))) { /* An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */ fprintf (stderr, epoll error\n); close (events[i].data.fd); continue; } else if (sfd events[i].data.fd) { /* We have a notification on the listening socket, which means one or more incoming connections. */ while (1) { struct sockaddr in_addr; socklen_t in_len; int infd; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; in_len sizeof in_addr; infd accept (sfd, in_addr, in_len); if (infd -1) { if ((errno EAGAIN) || (errno EWOULDBLOCK)) { /* We have processed all incoming connections. */ break; } else { perror (accept); break; } } //将地址转化为主机名或者服务名 s getnameinfo (in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);//flag参数:以数字名返回 //主机地址和服务地址 if (s 0) { printf(Accepted connection on descriptor %d (host%s, port%s)\n, infd, hbuf, sbuf); } /* Make the incoming socket non-blocking and add it to the list of fds to monitor. */ s make_socket_non_blocking (infd); if (s -1) abort (); event.data.fd infd; event.events EPOLLIN | EPOLLET; s epoll_ctl (efd, EPOLL_CTL_ADD, infd, event); if (s -1) { perror (epoll_ctl); abort (); } } continue; } else { /* We have data on the fd waiting to be read. Read and display it. We must read whatever data is available completely, as we are running in edge-triggered mode and wont get a notification again for the same data. */ int done 0; while (1) { ssize_t count; char buf[512]; count read (events[i].data.fd, buf, sizeof(buf)); if (count -1) { /* If errno EAGAIN, that means we have read all data. So go back to the main loop. */ if (errno ! EAGAIN) { perror (read); done 1; } break; } else if (count 0) { /* End of file. The remote has closed the connection. */ done 1; break; } /* Write the buffer to standard output */ s write (1, buf, count); if (s -1) { perror (write); abort (); } } if (done) { printf (Closed connection on descriptor %d\n, events[i].data.fd); /* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */ close (events[i].data.fd); } } } } free (events); close (sfd); return EXIT_SUCCESS; } #include stdio.h
#include stdlib.h
#include unistd.h
#include errno.h
#include sys/socket.h
#include netdb.h
#include fcntl.h
#include sys/epoll.h
#include string.h
#define MAXEVENTS 64
//函数:
//功能:创建和绑定一个TCP socket
//参数:端口
//返回值:创建的socket
static int
create_and_bind (char *port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int s, sfd;
memset (hints, 0, sizeof (struct addrinfo));
hints.ai_family AF_UNSPEC; /* Return IPv4 and IPv6 choices */
hints.ai_socktype SOCK_STREAM; /* We want a TCP socket */
hints.ai_flags AI_PASSIVE; /* All interfaces */
s getaddrinfo (NULL, port, hints, result);
if (s ! 0)
{
fprintf (stderr, getaddrinfo: %s\n, gai_strerror (s));
return -1;
}
for (rp result; rp ! NULL; rp rp-ai_next)
{
sfd socket (rp-ai_family, rp-ai_socktype, rp-ai_protocol);
if (sfd -1)
continue;
s bind (sfd, rp-ai_addr, rp-ai_addrlen);
if (s 0)
{
/* We managed to bind successfully! */
break;
}
close (sfd);
}
if (rp NULL)
{
fprintf (stderr, Could not bind\n);
return -1;
}
freeaddrinfo (result);
return sfd;
}
//函数
//功能:设置socket为非阻塞的
static int
make_socket_non_blocking (int sfd)
{
int flags, s;
//得到文件状态标志
flags fcntl (sfd, F_GETFL, 0);
if (flags -1)
{
perror (fcntl);
return -1;
}
//设置文件状态标志
flags | O_NONBLOCK;
s fcntl (sfd, F_SETFL, flags);
if (s -1)
{
perror (fcntl);
return -1;
}
return 0;
}
//端口由参数argv[1]指定
int
main (int argc, char *argv[])
{
int sfd, s;
int efd;
struct epoll_event event;
struct epoll_event *events;
if (argc ! 2)
{
fprintf (stderr, Usage: %s [port]\n, argv[0]);
exit (EXIT_FAILURE);
}
sfd create_and_bind (argv[1]);
if (sfd -1)
abort ();
s make_socket_non_blocking (sfd);
if (s -1)
abort ();
s listen (sfd, SOMAXCONN);
if (s -1)
{
perror (listen);
abort ();
}
//除了参数size被忽略外,此函数和epoll_create完全相同
efd epoll_create1 (0);
if (efd -1)
{
perror (epoll_create);
abort ();
}
event.data.fd sfd;
event.events EPOLLIN | EPOLLET;//读入,边缘触发方式
s epoll_ctl (efd, EPOLL_CTL_ADD, sfd, event);
if (s -1)
{
perror (epoll_ctl);
abort ();
}
/* Buffer where events are returned */
events calloc (MAXEVENTS, sizeof event);
/* The event loop */
while (1)
{
int n, i;
n epoll_wait (efd, events, MAXEVENTS, -1);
for (i 0; i n; i)
{
if ((events[i].events EPOLLERR) ||
(events[i].events EPOLLHUP) ||
(!(events[i].events EPOLLIN)))
{
/* An error has occured on this fd, or the socket is not
ready for reading (why were we notified then?) */
fprintf (stderr, epoll error\n);
close (events[i].data.fd);
continue;
}
else if (sfd events[i].data.fd)
{
/* We have a notification on the listening socket, which
means one or more incoming connections. */
while (1)
{
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len sizeof in_addr;
infd accept (sfd, in_addr, in_len);
if (infd -1)
{
if ((errno EAGAIN) ||
(errno EWOULDBLOCK))
{
/* We have processed all incoming
connections. */
break;
}
else
{
perror (accept);
break;
}
}
//将地址转化为主机名或者服务名
s getnameinfo (in_addr, in_len,
hbuf, sizeof hbuf,
sbuf, sizeof sbuf,
NI_NUMERICHOST | NI_NUMERICSERV);//flag参数:以数字名返回
//主机地址和服务地址
if (s 0)
{
printf(Accepted connection on descriptor %d
(host%s, port%s)\n, infd, hbuf, sbuf);
}
/* Make the incoming socket non-blocking and add it to the
list of fds to monitor. */
s make_socket_non_blocking (infd);
if (s -1)
abort ();
event.data.fd infd;
event.events EPOLLIN | EPOLLET;
s epoll_ctl (efd, EPOLL_CTL_ADD, infd, event);
if (s -1)
{
perror (epoll_ctl);
abort ();
}
}
continue;
}
else
{
/* We have data on the fd waiting to be read. Read and
display it. We must read whatever data is available
completely, as we are running in edge-triggered mode
and wont get a notification again for the same
data. */
int done 0;
while (1)
{
ssize_t count;
char buf[512];
count read (events[i].data.fd, buf, sizeof(buf));
if (count -1)
{
/* If errno EAGAIN, that means we have read all
data. So go back to the main loop. */
if (errno ! EAGAIN)
{
perror (read);
done 1;
}
break;
}
else if (count 0)
{
/* End of file. The remote has closed the
connection. */
done 1;
break;
}
/* Write the buffer to standard output */
s write (1, buf, count);
if (s -1)
{
perror (write);
abort ();
}
}
if (done)
{
printf (Closed connection on descriptor %d\n,
events[i].data.fd);
/* Closing the descriptor will make epoll remove it
from the set of descriptors which are monitored. */
close (events[i].data.fd);
}
}
}
}
free (events);
close (sfd);
return EXIT_SUCCESS;
}运行方式
在一个终端运行此程序epoll.out PORT
另一个终端telnet 127.0.0.1 PORT
截图 参考资料 http://man7.org/linux/man-pages/man2/epoll_create.2.html
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
http://blog.csdn.net/sparkliang/article/details/4770655 《深入理解Nginx模块开发与架构解析》9.6小节
http://blog.csdn.net/eroswang/article/details/4481521
http://www.ccvita.com/515.html
http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html