【网络】(十一)更高效的epoll

来源:互联网 发布:yum 安装snmp 编辑:程序博客网 时间:2024/05/16 09:47

第十篇文章中将select函数换做了poll,解决了客户端最大并发数量限制的问题,但通过压力测试会发现连接建立的速度是比较慢的,不管时select还是poll,都会存在这个问题。

select会受fd_set集合大小的限制,该大小不易更改,需要重新编译内核;同时也受系统所能打开的最大文件描述符的限制,该限制易更改。
poll只受系统所能打开的最大文件描述符的限制,前面也专门提到了该如何更改这个限制;该值的极限值受物理内存大小的限制,查看/proc/sys/fs/file-max文件可查看当前系统所能打开的最大文件描述符,4G内存的电脑上这一数值差不多有378675。该两者在使用时,都需要内核遍历所有的文件描述符,直到找到所有发生事件的文件描述符,并通知应用程序,所以当文件描述符非常多时,该两者的效率就会明显下降,这在上一篇中用压力测试客户端可看出来。

1、select、poll和epoll的区别

相比于select与poll,epoll最大的好处在于它不会随着监听文件描述符数目的增长而降低效率。
内核中的select与poll的实现是采用轮询来处理的,轮询的fd数目越多,自然耗时越多
epoll的实现是基于回调的,如果fd有期望的事件发生,就通过回调函数将其加入epoll就绪队列中,也就是说它只关心“活跃”的fd,与fd数目无关;
内核/用户空间内存拷贝问题,如何让内核把fd消息通知给用户空间呢?在这个问题上,select/poll采用内存拷贝方法,而epoll采用共享内存的方式;
epoll不仅会告诉应用程序有I/O事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息,应用程序就能直接定位到事件,而不必遍历整个fd集合

2、改进poll服务端
将上一篇文章中的服务端程序用epoll重新实现,提高效率!

handle.h/handle.c 与上一篇文章相同
客户端程序暂时不做更改,client.c/connclient.c 与上一篇文章相同

服务端源码

因为要使用容器,所以采用C++改写,编译命令

g++ -Wall -g server.cpp handle.c -o server

server.cpp

#include <string.h>#include <signal.h>#include <sys/epoll.h>#include "handle.h"#include <vector>#include <iostream>#include <algorithm>typedef std::vector<struct epoll_event> Eventlist;#define FD_MAXSIZE 2048     //最大客户端数量//为了防止SIGPIPE信号产生而终止了进程,所以捕获此信号void handle_sigpipe(void){    struct sigaction act;    act.sa_handler = SIG_IGN;       //忽略SIGPIPE信号    act.sa_flags = 0;    sigemptyset(&act.sa_mask);    if(sigaction(SIGPIPE, &act, NULL) == -1)        handle_error("sigaction");}/** activate_noblock -- 设置IO为非阻塞模式* 参数    fd:套接字*/void activate_nonblock(int fd){    int iret = 0;    int flags = fcntl(fd,F_GETFL);    if(flags == -1)        handle_error("fcntl");    flags |= O_NONBLOCK;    iret = fcntl(fd,F_SETFL,flags);    if(iret == -1)        handle_error("fcntl");}int main(void){    init(FD_MAXSIZE);    handle_sigpipe();       //捕获SIGPIPE信号    int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);    if(sk_fd < 0)        handle_error("socket");    //使用REUSEADDR,不必等待TIME_WAIT 状态消失,就可以重新使用端口    int on = 1;    if(setsockopt(sk_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)    {        close(sk_fd);        handle_error("setsockopt");    }    struct sockaddr_in sr_addr;    memset(&sr_addr,0,sizeof(sr_addr));    sr_addr.sin_family = AF_INET;    sr_addr.sin_port = htons(5188);    sr_addr.sin_addr.s_addr = htonl(INADDR_ANY);    if(bind(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0)    {        close(sk_fd);        handle_error("bind");    }    //被动套接字    if(listen(sk_fd, SOMAXCONN) < 0)     //内核为此套接字排队的最大连接数由SOMAXCONN宏指定    {        close(sk_fd);        handle_error("listen");    }    std::vector<int> clients;   //客户端连接套接字集合    int epfd;           //epoll实例描述符    //创建一个EPOLL实例,EPOLL_CLOEXEC指定当进程被替换时,自动关闭epfd    epfd = epoll_create1(EPOLL_CLOEXEC);    struct epoll_event event;    event.data.fd = sk_fd;      //监听套接字    event.events = EPOLLIN | EPOLLET;   //监听的事件    epoll_ctl(epfd, EPOLL_CTL_ADD, sk_fd, &event);  //监听套接字添加事件管理    Eventlist events(16);   //记录哪些IO产生了事件    struct sockaddr_in peeraddr;    socklen_t peerlen;    int conn_sk;    int nready;    int count = 0;      //客户端数目    while(true)    {        //检测哪些IO发生了事件        nready = epoll_wait(epfd, &*events.begin(), static_cast<int>(events.size()), -1);        if(nready == 0)        {            printf("epoll_wait time out!\n");            continue;        }        else if(nready == -1)        {            if(errno == EINTR)                continue;            handle_error("epoll_wait");        }        if(nready == static_cast<int>(events.size()))   //events集合空间不够用了            events.resize(events.size()*2);        //遍历事件        for(int i = 0; i < nready; i++)        {            if(events[i].data.fd == sk_fd)  //监听套接字发生了事件            {                peerlen = sizeof(struct sockaddr_in);                conn_sk = accept(sk_fd, (struct sockaddr*)&peeraddr, &peerlen);                if(conn_sk < 0)                {                    if(errno == EINTR)                        continue;                    handle_error("accept");                }                clients.push_back(conn_sk);     //已连接套接字添加到客户端套接字集合中                activate_nonblock(conn_sk);     //设置套接字为非阻塞模式                event.data.fd = conn_sk;                event.events = EPOLLIN | EPOLLET;   //监听的事件                epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sk, &event);  //添加事件管理                printf("Count = %d: Connect ip = %s\tport = %d\n",count++,inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));            }            else if(events[i].events & EPOLLIN)            {                conn_sk = events[i].data.fd;                if(conn_sk < 0)                    continue;                char recvbuf[1024] = {0};                int iret = recvline(conn_sk,recvbuf,sizeof(recvbuf));                if(iret == -1)                    handle_error("recvline");                if(iret == 0)                {                    printf("Client was closed!\n");                    event = events[i];                    //从监听集合中删除它                    epoll_ctl(epfd, EPOLL_CTL_DEL, conn_sk,&event);                    //删除该客户端的已连接套接字                    clients.erase(std::remove(clients.begin(), clients.end(),conn_sk), clients.end());                    close(conn_sk);                }                fputs(recvbuf, stdout);                writen(conn_sk, recvbuf, strlen(recvbuf));            }        }    }    for(unsigned int i = 0; i < clients.size(); i++)    {        close(clients[i]);    }    close(sk_fd);    return 0;}

3、压力测试

编译上一篇中的压力测试客户端:

gcc -Wall -g -std=gnu99 connclient.c -o client

通过连接服务端,可明显发现速度快多了!

0 0
原创粉丝点击