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字段是表示感兴趣的
事件和被触发的事件可能的取值为:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
所用到的函数:
1、int epoll_create(int size)
该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大
范围
2、
用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
如果调用成功返回0,不成功返回-1
int epoll_ctl(
int epfd,//由 epoll_create 生成的epoll专用的文件描述符
int op,//要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、
EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL 删除
int fd,//关联的文件描述符
struct epoll_event *event//指向epoll_event的指针
)
3、
用于轮询I/O事件的发生
返回发生事件数
int epoll_wait(
int epfd,//由epoll_create 生成的epoll专用的文件描述符
struct epoll_event * events,//用于回传代处理事件的数组
int maxevents,//每次能处理的事件数
int timeout//等待I/O事件发生的超时值
//为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件
//为任意正整数的时候表示等这么长的时间,如果一直没有事件
//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以
保证一些效率
//如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率
。
)
- #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
阅读(784) | 评论(0) | 转发(0) |