Linux网络编程:epoll的简介及实例

0x00前言

文章中的文字可能存在语法错误以及标点错误,请谅解;

如果在文章中发现代码错误或其它问题请告知,感谢!

0x01 epoll简介

epoll是Linux内核中可扩展IO事件的处理机制,它的出现,可以用于替代曾长时间使用POSIX select 和 poll模型处理的事件的系统调用。

epoll工作机制:
epoll在被内核初始化时,会开辟一个高速缓存区,并利用epoll_ create()创建一个file节点(服务epoll)、红黑树和就绪句柄链表,利用这四个对象,就可以帮助开发者解决高并发下的socket处理问题。具体实现机制为:在epoll初始化时,内核会给epoll分配一个高速缓存区,以便暂存我们想要监听的socket(通过epoll_ ctl()传递),为了方便查找、插入和删除,这些socket以红黑树的形式保存在内核缓存中。另外,epoll会建立一个链表,当epoll_ ctl()传递socket给高速缓存区的同时会给内核中断处理程序注册一个回调函数,使得socket有数据进来后就将该socket插入到链表中。
然后,当使用epoll_ wait()时,该函数会查看链表中是否有数据(socket),有数据就返回,没有就按照该函数中的参数timeout时间等待,timeout时间到就返回。因为在select 和poll模式下,需要遍历整个fd事件集合,而epoll只需要判断链表是否为空就可以,所以大大节省了CPU的时间,这就是epoll能够在大并发处理下能够应对多个socket请求的原因。

epoll支持两种工作模式:
水平触发LT(level trigger):当epoll_ wait()检测到某描述符有事件发生后,会通知应用程序,应用程序可以选择立即处理或部暂不处理事件,若不处理,epoll_ wait()再次检测到该事件后,仍然会再次响应并通知应用程序,这种模式称为水平触发LT(level trigger)。另外要注意,该模式为默认模式;
Edge Triggered (ET):当epoll_ wait()检测到某描述符有事件发生后通知应用程序,应用程序若不处理,则下次再调用epoll_ wait()时,将不会将该事件告知应用程序。
理论上 ,因为ET模式减少了事件被重复出发的次数,所以该模式效率要比LT模式高,但是,使用ET模式只会对发生的事件通知一次,可能会造成程序员的“漏掉”要处理的数据,增加了出错机会。
无论是LT和ET模式,都是用以上所述的epoll工作机制。

epoll的更多的介绍可以参见本文末尾参考文档5。

0x02 epoll优点

1.不必每次等待事件之前都要遍历socket描述符集合,只需要遍历被唤醒加入到链表中的socket描述符即可;
2.处理效率不随socket描述符数目增加而下降;
3.支持进程打开最大数目socket描述符;
4.因为内核已经在epoll_ctl()中拿到了要监控的socket描述符集合,所以不需要将socket描述符集合从用户态拷贝到内核态。

0x03 epoll接口介绍

创建:

int epoll_create(int size);

该函数用来创建一个epoll句柄,参数size告诉内核需要监听的socket数目一共是多少。当创建好句柄后,会占用一个fd值,所以在使用完毕之后,需要用close()关闭,否则占用资源。
注册:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该函数用来事件注册,第一个参数是epoll_create()返回值,第二个参数表示动作,第三个参数是需要监听的socket,第四个参数告诉内核需要监听什么事件。
第二个参数表动作,用下面三个宏表示:

EPOLL_CTL_ADD    //注册新的fd到epfd中;
EPOLL_CTL_MOD    //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL    //从epfd中删除一个fd;

第四个参数告知内核需要监听什么事件event,该event为epoll_event 结构体,该结构体内容如下:

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

其中 ,epoll_data_t 结构体如下:

typedef union epoll_data
{
  void        *ptr;
  int          fd;
  __uint32_t   u32;
  __uint64_t   u64;
} epoll_data_t;

另外,events为以下宏集合:

EPOLLIN     //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT    //表示对应的文件描述符可以写;
EPOLLPRI    //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR    //表示对应的文件描述符发生错误;
EPOLLHUP    //表示对应的文件描述符被挂断;
EPOLLET     //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

从内核中得到事件的集合:

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

该函数类似select(),作用为等待事件的产生。参数events用来从内核得到事件的集合,maxevents 告之内核这个events有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的size,参数 timeout 是超时时间(毫秒,0会立即返回,-1将不确定)。该函数返回需要处理的事件数目,如返回0表示已超时。
 epoll_wait范围之后应该是一个循环,遍历所有的事件。

0x04 epoll模型编程逻辑

一般使用epoll模型编程的逻辑如下:

新建的epoll描述符=epoll_create()
epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
while(1)
{
	 返回的活跃连接 ==epoll_wait( epoll描述符 )
	 处理活跃的连接
}

0x04 代码实例

TCP服务器代码如下:

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <pthread.h>

#define MAXLINE  1024
#define OPEN_MAX  16 //一些系统会定义这些宏
#define SERV_PORT  10001

void *EpollListen(void *arg)
{
    int i , maxi ,listenfd , connfd , sockfd ,epfd, nfds;
    int n;
    char buf[MAXLINE];

    struct epoll_event ev, events[20];
    socklen_t clilen;
    struct pollfd client[OPEN_MAX];
    struct sockaddr_in cliaddr , servaddr;

    listenfd = socket(AF_INET , SOCK_STREAM , 0);
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(listenfd , (struct sockaddr *) & servaddr, sizeof(servaddr));
    listen(listenfd,10);

    epfd = epoll_create(256);
    ev.data.fd=listenfd;
    ev.events=EPOLLIN|EPOLLET;
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

    for(;;)
    {
        nfds=epoll_wait(epfd,events,20,500);
        for(i=0; i<nfds; i++)
        {
            if (listenfd == events[i].data.fd)
            {
				printf("listenfd %d\n",listenfd);

                clilen = sizeof(cliaddr);
                connfd = accept(listenfd , (struct sockaddr *)&cliaddr, &clilen);
                if(connfd < 0)
                {
                    perror("connfd < 0");
                    exit(1);
                }
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if (events[i].events & EPOLLIN)		//EPOLLIN只有当对端有数据写入时才会触发
            {
                if ( (sockfd = events[i].data.fd) < 0)
                    continue;
                n = recv(sockfd,buf,MAXLINE,0);
                if (n <= 0)
                {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                else
                {
					printf("EPOLLIN %d\n",events[i].data.fd);

                    buf[n]='\0';
                    printf("Socket %d said : %s\n",sockfd,buf);
                    ev.data.fd=sockfd;
                    ev.events=EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,connfd,&ev);
                }
            }

            else if(events[i].events&EPOLLOUT)		//EPOLLOUT只有在连接时触发一次
            {
				printf("EPOLLOUT %d\n",events[i].data.fd);

                sockfd = events[i].data.fd;

                send(sockfd, "Hello!", 7, 0);

                ev.data.fd=sockfd;
                ev.events=EPOLLIN|EPOLLET;			//改回EPOLLIN
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
            else
            {
                printf("This is not avaible!");
            }
        }
    }
    close(epfd);

	return;
}

int main(int argc, char *argv[])
{
    int ret = 0;

	pthread_t threadID = 0;
	pthread_attr_t attr;

	pthread_attr_init(&attr);//属性设置
	pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

    ret = pthread_create(&threadID, &attr, EpollListen, NULL);
    if (0 != ret)
    {
        printf("main: Init pthread_create EpollListen Failed!\n");
        return -1;
    }

	while (1)
	{
        sleep(100);
    }

	pthread_attr_destroy(&attr);//线程属性销毁

    return 0;
}


0x05 运行结果

在这里插入图片描述
在这里插入图片描述
以上。

参考文档:
1.https://blog.csdn.net/lixungogogo/article/details/52226479
2.http://www.cnblogs.com/bugutian/p/4816764.html
3.https://www.cnblogs.com/springmvc-hibernate/archive/2010/10/28/2484209.html
4.http://www.cnblogs.com/haippy/archive/2012/01/09/2317269.html
5.https://www.cnblogs.com/chenny7/p/5069627.html

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值