epoll多路复用压力测试

epoll多路复用压力测试

1、epoll见解

  epoll相对于select、poll性能优越相当之多,可以说是二者结合加强版本,我们可以设想一下,假如有100w个tcp连接,那么每次有数据过来了,select、poll都需要去从第一个到最后一个进行依次遍历,而epoll会讲队列排序讲发生事件放在前面,后面的就不用遍历了,所以准确的说处理几千的连接可以用select、poll但是太多了就不行,而且select、poll都是从用户态拷贝到内核态,需要花费大量的时间,这样比较起来epoll性能就相当的高了。

2、epoll原理

  当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关,如下所示:

struct eventpoll {
  ...
  /*红黑树的根节点,这棵树中存储着所有添加到epoll中的事件,
  也就是这个epoll监控的事件*/
  struct rb_root rbr;
  /*双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件*/
  struct list_head rdllist;
  ...
};

  我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
  如果rdllist链表不为空,则这里的事件复制到用户态内存(使用共享内存提高效率)中,同时将事件数量返回给用户。因此epoll_waitx效率非常高。epoll_ctl在向epoll对象中添加、修改、删除事件时,从rbr红黑树中查找事件也非常快,也就是说epoll是非常高效的,它可以轻易地处理百万级别的并发连接。

  • epoll_ctl()
#include <sys/epoll.h> 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev); 

  系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。若成功返回0,若出错返回-1。
  第一个参数epfd是epoll_create()的返回值; 第二个参数op用来指定需要执行的操作,它可以是如下几种值:
    EPOLL_CTL_ADD:将描述符fd添加到epoll实例中的兴趣列表中去。对于fd上我们感兴趣的事件,都指定在ev所指向的 结构体中。如果我们试图向兴趣列表中添加一个已存在的文件描述符,epoll_ctl()将出现EEXIST错误;
    EPOLL_CTL_MOD:修改描述符上设定的事件,需要用到由ev所指向的结构体中的信息。如果我们试图修改不在兴趣列表 中的文件描述符,epoll_ctl()将出现ENOENT错误;
    POLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中移除,该操作忽略参数ev。如果我们试图移除一个不在epfd的兴 趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误。关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表 移除;
  第三个参数fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队 列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符。但是,这里fd不能作为普通文件或目录的文件描述符;
  第四个参数ev是指向结构体epoll_event的指针,结构体的定义如下:

typedef union epoll_data {    
   		 void        *ptr;    /* Pointer to user-defind data */    
   		 int        fd;    /* File descriptor */    
   		 uint32_t    u32;    /* 32-bit integer */    
   		 uint64_t    u64;    /* 64-bit integer */ } epoll_data_t;
struct epoll_event {    
   		uint32_t events; /* epoll events(bit mask) */    
   		epoll_data_t data; /* User data */ 
};

3、测试代码

#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void handler_events(int epfd,struct epoll_event revs[],int num,int listen_sock)
{
    struct epoll_event ev;
    int i = 0;
    for( ; i < num; i++ )
    {
    int fd = revs[i].data.fd;

    // 如果是监听文件描述符,则调用accept接受新连接

    if( fd == listen_sock && (revs[i].events & EPOLLIN) )
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(fd,(struct sockaddr *)&client,&len);

        if( new_sock < 0 )
        {
        perror("accept fail ... \n");
        continue;
        }

       printf("get a new link![%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

       //因为只是一个http协议:连接成功后,下面就是要 请求和响应 
       // 而服务器端响应之前:要先去读客户端要请求的内容

       ev.events = EPOLLIN;
       ev.data.fd = new_sock;
       epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);

       continue;
    }

    // 如果是普通文件描述符,则调用read提供读取数据的服务

    if(revs[i].events & EPOLLIN)
    {
        char buf[10240];
        ssize_t s = read(fd,buf,sizeof(buf)-1);
        if( s > 0 )// 读成功了
        {
        buf[s] = 0;
        printf(" %s ",buf);

        // 读成功后,就是要给服务端响应了
        // 而这里的事件是只读事件,所以要进行修改

        ev.events = EPOLLOUT;// 只写事件
        ev.data.fd = fd;
        epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);// 其中EPOLL_CTL_MOD 表示修改

        }

        else if( s == 0 )
        {
        printf(" client quit...\n ");
        close(fd);// 这里的fd 就是 revs[i].fd
        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);// 连接关闭,那么就要把描述该连接的描述符关闭
        }
        else// s = -1 失败了
        {   
        printf("read fai ...\n");
        close(fd);// 这里的fd 就是 revs[i].fd
        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);// 连接关闭,那么就要把描述该连接的描述符关闭
        }
        continue;
    }

    // 服务器端给客户端响应: 写

    if( revs[i].events & EPOLLOUT )
    {
        const char* echo = "HTTP/1.1 200 ok \r\n\r\n<html>hello epoll server!!!</html>\r\n";
        write(fd,echo,strlen(echo));
        close(fd);
        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
    }
    }
}

int startup( int port )
{
    // 1. 创建套接字
    int sock = socket(AF_INET,SOCK_STREAM,0);//这里第二个参数表示TCP
    if( sock < 0 )
    {
    perror("socket fail...\n");
    exit(2);
    }

    // 2. 解决TIME_WAIT时,服务器不能重启问题;使服务器可以立即重启
    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);// 地址为任意类型
    local.sin_port = htons(port);// 这里的端口号也可以直接指定8080
    // 3. 绑定端口号

    if( bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0 )
    {
    perror("bind fail...\n");
    exit(3);
    }

    // 4. 获得监听套接字
    if( listen(sock,5) < 0 )
    {
    perror("listen fail...\n");
    exit(4);
    }
    return sock;
}

int main(int argc,char* argv[] )
{

    // 1. 创建一个epoll模型: 返回值一个文件描述符

    int epfd = epoll_create(256);
    if( epfd < 0 )
    {
    perror("epoll_create fail...\n");
    return 2;
    }

    // 2. 获得监听套接字

   int listen_sock = startup(atoi("8899"));//端口号传入的时候是以字符串的形式传入的,需要将其转为整型


    // 3. 初始化结构体----监听的结构列表

    struct epoll_event  ev;
    ev.events = EPOLLIN;//关心读事件
    ev.data.fd = listen_sock;// 关心的描述文件描述符

    // 4. epoll的事件注册函数---添加要关心的文件描述符的只读事件

    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);

    struct epoll_event revs[128];
    int n = sizeof(revs)/sizeof(revs[0]);

    int timeout = 3000;
    int num = 0;


    while(1)
    {

       // 5 . 开始调用epoll等待所关心的文件描述符集就绪

       switch( num = epoll_wait(epfd,revs,n,timeout) )
       {
       case 0:// 表示词状态改变前已经超过了timeout的时间
           printf("timeout...\n");
           continue;
       case -1:// 失败了
           printf("epoll_wait fail...\n");
           continue;
       default: // 成功了

           handler_events(epfd,revs,num,listen_sock);
           break;
       }
    }
    close(epfd);
    close(listen_sock);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值