Linux 2.6内核完全支持epoll.
epoll的IO效率不随FD数目增加而线性下降
传统的select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。
内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主
动的去调用 callback函数,其他idle状态socket则不会。
如果所有的socket基本上都是活跃的---比如一个高速LAN环境,过多使用epoll,效率
相比还有稍微的下降。
但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
poll的执行分三部分:
1.将用户传入的pollfd数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间
上这是一个O(n)操作
2.
查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等
待队列中加入一项并继续查询下一设备的状态。
查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直
到设备就绪或者超时。
设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。
这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时
间。。
3.
将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向
用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n)。
epoll用到的所有函数都是在头文件sys/epoll.h中声明的,下面简要说明所用到的数
据结构和函数:
所用到的数据结构
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 */
};
结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件.
其中epoll_data 联合体用来保存触发事件的某个文件描述符相关的数据.
例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对
应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段以便后面的读
写操作在这个文件描述符上进行。epoll_event 结构体的events字段是表示感兴趣的
出处张沈鹏的javaeye博客
http://zsp.iteye.com/blog/146850
——————注:上面的内容皆是摘录————
之前的select模式的代码
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include "wrap.h"
#include "httpprocesser.h"
#define MAXLINE 800
#define SERV_PORT 7080
#define BACKLOG 200
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
listenfd = my_socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
my_bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
my_listen(listenfd, BACKLOG);
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for ( ; ; ) {
rset = allset; /* structure assignment */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
cliaddr_len = sizeof(cliaddr);
connfd = my_accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE) {
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready == 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = my_read(sockfd, buf, MAXLINE)) > 0) {
struct http_msg msg;
parse_msg(buf, &msg);
printf("%s\n", msg.response);
my_write(sockfd, buf, n);
}
/* connection closed by client */
my_close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
if (--nready == 0)
break; /* no more readable descriptors */
}
}
}
}
现在使用的epoll模式的代码
这部分的代码由http://blog.csdn.net/mote_li/archive/2004/12/08/209450.aspx 修改而来
另外还增加了基于pthread库实现的工作队列方式的线程池。
#ifndef __myhttpd_h
#define __myhttpd_h
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <strings.h>
#include <pthread.h>
#include "wrap.h"
#include "task.h"
#define OPEN_MAX 100
#define LISTENQ 20
#define INFTIM 1000
#define LOCAL_IP "127.0.0.1" /* 修改为自己本地机器就可以测试了 */
#define SERV_PORT 5555
extern pthread_mutex_t mutex; /* 线程安全使用 */
extern pthread_cond_t cond1; /* 线程条件等待使用 */
extern struct task *readhead ,
*readtail ,
*writehead ;
extern struct epoll_event ev, events[20];
extern int epfd;
void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1); /* 其实这样做不怎么好, 最好自己做好出错处理的工作, 不光是进程退出就可以了 */
}
if(fcntl(sock, F_SETFL, opts | O_NONBLOCK)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd, nfds;
socklen_t clilen;
pthread_t tid1,tid2;
struct task *new_task=NULL;
struct user_data *rdata=NULL;
readhead = readtail = writehead = NULL;
/* initialize the thread pool */
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond1, NULL);
/* 创建线程, 最好做好错误处理工作, 自己也比较懒. 真正作东西千万别这样噢! */
pthread_create(&tid1, NULL, readtask, NULL);
pthread_create(&tid2, NULL, readtask, NULL);
/* 生成用于处理accept的epoll专用的文件描述符
* 以前从没用过
*
Create a new epoll file descriptor by requesting the kernel allocate an event backing store dimensioned
[n. 尺寸, 尺度, 维(数), 度(数), 元] for size descriptors.
The size is not the maximum size of the backing store but just a hint to the kernel about
how to dimension internal structures.
The returned file descriptor will be used for all the subsequent calls to the epoll interface.
The file descriptor returned by epoll_create must be closed by using POSIX::close.
When successful, epoll_create returns a positive integer identifying the descriptor. When an error occurs,
epoll_create returns -1 and errno is set appropriately.
*
*/
epfd = epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = my_socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd = listenfd;
//设置要处理的事件类型
ev.events = EPOLLIN | EPOLLET;
/*注册epoll事件
Control an epoll descriptor, $epfd, by requesting the operation op be performed on the target file descriptor, fd.
$epfd is an epoll descriptor returned from epoll_create.
$op is one of EPOLL_CTL_ADD, EPOLL_CTL_MOD or EPOLL_CTL_DEL.
$fd is the file desciptor to be watched.
$eventmask is a bitmask of events defined by EPOLLIN, EPOLLOUT, etc.
When successful, epoll_ctl returns 0. When an error occurs, epoll_ctl returns -1 and errno is set appropriately.
*/
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = LOCAL_IP;
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT);
my_bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
my_listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; )
{
/*等待epoll事件的发生
Wait for events on the epoll file descriptor $epfd.
$epfd is an epoll descriptor returned from epoll_create.
$maxevents is an integer specifying the maximum number of events to be returned.
$timeout is a timeout, in milliseconds
When successful, epoll_wait returns a reference to an array of events. Each event is a two element array,
the first element being the file descriptor which triggered the event,
and the second is the mask of event types triggered.
For example, if epoll_wait returned the following data structure:
*/
nfds = epoll_wait(epfd, events, 20, 500);
//处理所发生的所有事件
for(i = 0; i < nfds; ++i)
{
if(events[i].data.fd == listenfd)
{
connfd = my_accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
if(connfd < 0)
{
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
printf("connect_from >> %s \n", str);
ev.data.fd = connfd; //设置用于读操作的文件描述符
ev.events = EPOLLIN | EPOLLET; //设置用于注测的读操作事件
//注册ev
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
printf("reading!\n");
if ((sockfd = events[i].data.fd) < 0)
continue;
new_task = (struct task *)malloc(sizeof(struct task));
new_task->fd = sockfd;
new_task->next = NULL;
pthread_mutex_lock(&mutex); //添加新的读任务
if(readhead == NULL)
{
readhead = new_task;
readtail = new_task;
}
else
{
readtail->next = new_task;
readtail = new_task;
}
//唤醒所有等待cond1条件的线程
pthread_cond_broadcast(&cond1);
pthread_mutex_unlock(&mutex);
}
else if (events[i].events & EPOLLOUT)
{
rdata = (struct user_data *)events[i].data.ptr;
sockfd = rdata->fd;
printf("thread.%u Write data fd.%d len.%d data.%s \n"
, (uint32_t)pthread_self(), sockfd, rdata->n_size, rdata->line);
my_write(sockfd, rdata->line, rdata->n_size);
my_close(sockfd);
free(rdata);
ev.data.fd = sockfd; //设置用于读操作的文件描述符
ev.events = EPOLLIN | EPOLLET; //设置用于注测的读操作事件
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
}
#endif
分享到:
相关推荐
在Linux系统中,处理高并发I/O事件时,select和epoll是两种常见的技术。本文将详细介绍这两种技术,以及它们在处理大量并发连接时的特点和优势。 首先,我们来看看`select`函数。`select`是一种古老的I/O多路复用...
服务器与客户端建立连接需要使用到一些接口,包括但不限于socket、bind、listen、accept.高并发编程会有一些服务器模型,例如reactor或proactor。这两类都要使用到IO多路复用,O多路复用是指单个进程/线程就可以同时...
在IT领域,多路复用技术是用于高并发网络编程的一种高效策略,它允许一个单一的线程同时处理多个连接。本项目通过`select`和`epoll`两种方法实现了这一功能,并使用`jmeter`进行了性能测试。下面将详细讨论这两个多...
select,poll,epoll都是多路复用IO的函数,简单说就是在一个线程里,可以同时处理多个文件描述符的读写。 select/poll的实现很类似,epoll是从select/poll扩展而来,主要是为了解决select/poll天生的缺陷。 epoll在...
EPOLL 是 Linux 内核提供的 I/O 多路复用技术,它是 Select 和 Poll 的更高效版本,特别是在处理大量并发连接时。EPOLL 提供了一种基于事件的异步 I/O 模型,能够有效地管理和监控大量文件描述符(FDs),显著提高了...
在Linux系统中,当面临需要同时管理大量网络连接或文件描述符时,`select`、`poll`和`epoll`是三种常见的I/O多路复用技术,它们允许程序在一个单独的线程中等待多个文件描述符的事件,提高了程序的效率和并发能力。...
2. 创建监听套接字:socket()、bind()、listen(),然后将监听套接字添加到epoll实例,设置为边缘触发模式。 3. 循环处理事件:epoll_wait()获取就绪的套接字描述符,根据描述符类型执行相应操作。 - 对于监听套接字...
epoll是Linux内核提供的更为高效的一种I/O多路复用技术,相比select和poll,它能处理更多的并发连接,并且有更好的性能。epoll采用“事件驱动”模型,通过`epoll_create()`、`epoll_ctl()`和`epoll_wait()`三个系统...
传统的IO复用方法包括select和poll,但它们在处理大量并发连接时性能表现欠佳,尤其是在现代以Web服务器为主的环境中。为了克服这些限制,Linux引入了epoll,BSD系统有kqueue,Solaris提供了/dev/poll,而Windows则...
在Linux系统中,`epoll`是用于处理大量并发连接的一种高效I/O多路复用技术,特别适合构建海量级连接服务器。相比传统的`select`和`poll`,`epoll`具有显著的优势,比如更高的可扩展性和更好的性能。本文将深入探讨`...
- **边缘触发(ET)**:更高效的模式,只在FD状态由非就绪变为就绪时返回一次,即使数据未被完全处理也不会再次报告。 这三种机制在不同场景下各有优劣。对于小规模的并发连接,`select`可能已经足够;中等规模的...
linux中 epoll poll 和select的区别
Linux系统编程——I/O多路复用select、poll、epoll的区别使用,相关教程如下: http://blog.csdn.net/tennysonsky/article/details/45745887
epoll(边缘触发)是对传统select和poll(水平触发)的改进,克服了它们在处理大量文件描述符时的性能瓶颈。epoll通过内核与用户空间的数据共享来提高效率,当文件描述符上有事件发生时,内核会将相关信息保存在用户...
### EPOLL模型:关于并发连接的处理 #### 一、Select 的局限性 在 Linux 内核中,`select` 是一种常用的 I/O 多路复用机制,它允许一个进程监控多个文件描述符(FD),并在这些 FD 就绪(例如可读或可写)时通知...
最后,`epoll`(Event Poll)是Linux内核为解决`select`和`poll`的性能问题而引入的一种更高效的方法。`epoll`采用“边缘触发”(Edge Triggered, ET)和“水平触发”(Level Triggered, LT)两种模式。在ET模式下,...
`TCP`、`Epoll`和`Select`都是与网络I/O相关的概念,它们在处理高并发连接时起着关键作用。本篇文章将深入探讨这三个核心概念以及它们在实际应用中的优缺点。 **TCP(Transmission Control Protocol)** TCP是一种...
select poll epoll 代码实例
在Linux系统中,epoll是I/O多路复用技术的一种高效实现,它极大地改进了传统的轮询方式,尤其在处理大量并发连接时表现出色。本文将深入探讨如何在多线程环境中使用epoll进行程序设计,以提高系统的并行处理能力。 ...