epoll——高并发的功臣

一句话总结:减少遍历fd、加速fd从用户态到内核态的拷贝,一切皆为提升性能。与IOCP有异曲同工之妙。


说说select和poll,没有具体研究过,基本过程是每次调用select/poll都会将fd集合从用户态拷贝到内核态,在内核中需要全遍历一遍,都是开销啊,而且对fd的数目是有限制的(默认是1024)。

说说epoll,相对于前面两者,通过内存共享mmap加速了fd集合的拷贝且仅拷贝一次,只遍历就绪的fd,大大减少了开销,而且对fd的数目支持更大,与系统资源有关。


举例说下吧:(epoll)入学一批新同学,学校给他们每人发一张饭卡,很大数量的同学去食堂吃饭,吃完饭将碗筷放到餐具收纳处,工作人员会每隔一段时间来去收拾碗筷。

是不是没什么感觉, 那说下poll的情况:每到吃饭时间,学校会先给没人发张饭卡,只能一定数量的同学可以去吃饭,学生在座位上吃饭,工作人员一个个询问,吃完了吗碗筷可以收了吗。

比较一下,哪种效率更高,肯定第一种嘛。


怎么编程呢,只能说高手们屏蔽了太多复杂的实现,只留了三个接口使用,简直太方便了。

1、int epoll_create(int size)

创建一个epoll句柄,参数size用来告诉内核监听的数目,高级版本参数size已经失效。

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

epoll事件注册函数,

  参数epfd为epoll的句柄;

  参数op表示动作,用3个宏来表示:EPOLL_CTL_ADD(注册新的fd到epfd),EPOLL_CTL_MOD(修改已经注册的fd的监听事件),EPOLL_CTL_DEL(从epfd删除一个fd);

  参数fd为需要监听的标示符;

  参数event告诉内核需要监听的事件,event的结构如下:

struct epoll_event {

  __uint32_t events;  /* Epoll events */

  epoll_data_t data;  /* User data variable */

};

  其中events可以用以下几个宏的集合:

  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)

  EPOLLOUT:表示对应的文件描述符可以写

  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

  EPOLLERR:表示对应的文件描述符发生错误

  EPOLLHUP:表示对应的文件描述符被挂断;

  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3、 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表示已超时。


select/poll只有水平触发,poll多个了边缘触发,编程时默认还是水平触发。

        LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

  ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。仅支持non-block socket。

  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。


差不多了,该实例上了:

环境是虚拟机CentOS,内存1G



epoll支持的的最大fd个数是



上代码:

//
//  epoll_server.cpp
//  NetworkProgramServer
//
//  Created by zhaojunyan on 17/6/19.
//  Copyright © 2017年 zhaojunyan. All rights reserved.
//

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include            /* socket类定义需要*/
#include             /* epoll头文件 */
#include                 /* nonblocking需要 */
#include 
#include             //inet_addr()头文件

#define MAXEPOLL    10000   /* 对于服务器来说,这个值可以很大的! cat /proc/sys/fs/file-max */
#define BUFLEN      1024
#define PORT        1235
#define MAXBACK     1000

/*setnonblocking - 设置句柄为非阻塞方式*/
int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
    {
        return -1;
    }
    return 0;
}

int setblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, 0) == -1) {
        return -1;
    }
    return 0;
}

int main( int argc, char ** argv )
{
    int   listen_fd;
    int   conn_fd;
    int   epoll_fd;
    int   read_len;
    int   cur_fds;
    int   wait_fds;
    struct sockaddr_in servaddr;
    struct sockaddr_in cliaddr;
    struct  epoll_event ev;
    struct  epoll_event evs[MAXEPOLL];
    char    buf[BUFLEN];
    socklen_t sock_len = sizeof( struct sockaddr_in );
    printf("CentOS server starting...\n");
    
    bzero( &servaddr, sizeof( servaddr ) );
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("10.211.55.4");
    servaddr.sin_port = htons( PORT );
    
    //创建监听套接字
    if( ( listen_fd = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 )
    {
        printf("Socket Error...\n");
        exit( EXIT_FAILURE );
    }
    //设置非阻塞
    if( setnonblocking( listen_fd ) == -1 )
    {
        printf("Setnonblocking Error...\n");
        exit( EXIT_FAILURE );
    }
    
    //绑定监听端口
    if( bind( listen_fd, (struct sockaddr *)&servaddr, sock_len) == -1 )
    {
        printf("Bind Error...\n");
        exit( EXIT_FAILURE );
    }
    
    //开始监听端口
    if( listen( listen_fd, MAXBACK ) == -1 )
    {
        printf("Listen Error...\n");
        exit( EXIT_FAILURE );
    }
    
    //创建epoll文件描述符
    epoll_fd = epoll_create( MAXEPOLL );
    
    //设置处理的“文件描述符”和“事件类型”
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listen_fd;
    
    //注册epoll事件
    if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev ) < 0 )
    {
        printf("Epoll Error...\n");
        exit( EXIT_FAILURE );
    }
    cur_fds = 1;
    
    while( 1 )
    {
        //printf( "Epoll Wait...\n" );
        //等待epoll事件发生,返回值为需处理发生事件的总个数
        if( ( wait_fds = epoll_wait( epoll_fd, evs, cur_fds, -1 ) ) == -1 )
        {
            printf( "Epoll Wait Error...\n" );
            exit( EXIT_FAILURE );
        }
        
        for( int i = 0; i < wait_fds; i++ )
        {
            //printf("Epoll cur_fds: %d, wait_fds: %d\n", cur_fds, wait_fds);
            if( evs[i].data.fd == listen_fd && cur_fds < MAXEPOLL )//是被监听的文件描述符
            {
                if( ( conn_fd = accept( listen_fd, (struct sockaddr *)&cliaddr, &sock_len ) ) == -1 ) //等待连接
                {
                    printf("Accept Error...\n");
                    exit( EXIT_FAILURE );
                }
                char IPdotdec[20];
                if (inet_ntop(AF_INET, &cliaddr.sin_addr, IPdotdec, sizeof(IPdotdec))==NULL)
                    return NULL;
                
                printf( "client connected server! %s:%d\n", IPdotdec, ntohs(cliaddr.sin_port));
                
                //设置epoll事件属性
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = conn_fd;
                if( epoll_ctl( epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev ) < 0 )//注册epoll事件
                {
                    printf("Epoll Error : %d\n", errno);
                    exit( EXIT_FAILURE );
                }
                ++cur_fds;
                continue;
            }
            
            read_len = read( evs[i].data.fd, buf, sizeof( buf ) );
            if( read_len <= 0 )
            {
                close( evs[i].data.fd );
                epoll_ctl( epoll_fd, EPOLL_CTL_DEL, evs[i].data.fd, &ev );
                --cur_fds;
                continue;
            }
            if (0 == strncmp(buf, "bye", read_len)) {
                printf("close socket %d\n", evs[i].data.fd);
            }
            else
            {
                printf("recv msg: %s\n", buf);
            }
            write( evs[i].data.fd, "I am centos!", 20 );
            //printf("send msg...\n");
        }
    }
    
    close( listen_fd );
    return 0;
}

//
//  main.cpp
//  NetworkProgramClient
//
//  Created by zhaojunyan on 17/6/19.
//  Copyright © 2017年 zhaojunyan. All rights reserved.
//

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define BUFLEN 1024
#define PORT   1235
#define ADDR   "10.211.55.4"
#define STAT_RUNNING 1
#define STAT_CLOSE 2

unsigned char g_stat = STAT_RUNNING;

void* recvMsg(void* arg)
{
    int sockfd = (int)(*(int*)arg);
    ssize_t recvMsgLen = 0;
    char buf[1024] = {0};
    while (true) {
        recvMsgLen = recv(sockfd, buf, BUFLEN, 0);
        if (-1 == recvMsgLen) {
            perror("recv error");
            close(sockfd);
            return NULL;
        }
        printf("recvMsg: %s\n", buf);
    }
    
    
    return NULL;
}

void* sendMsg(void* arg)
{
    int sockfd = (int)(*(int*)arg);
    ssize_t res = 0;
    string strMsg;
    while (true) {
        //printf("input send message:\n");
        getline(cin, strMsg);
        res = send(sockfd, strMsg.c_str(), BUFLEN, 0);
        if (-1 == res) {
            perror("send error");
            close(sockfd);
        }
        if (strMsg.compare("bye") == 0) {
            g_stat = STAT_CLOSE;
            return NULL;
        }
    }
    
    return NULL;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    int cnntfd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in clientAddr;
    clientAddr.sin_family = AF_INET;
    clientAddr.sin_port=htons( PORT );
    clientAddr.sin_addr.s_addr=inet_addr( ADDR );
    if (connect(cnntfd, (struct sockaddr *)&clientAddr, sizeof(sockaddr_in)) == -1) {
        perror("connect error");
        return -1;
    }
    
    pthread_t pid;
    if (pthread_create(&pid, NULL, recvMsg, &cnntfd) != 0) {
        perror("create recvMsg thread error");
    }
    
    if (pthread_create(&pid, NULL, sendMsg, &cnntfd) != 0) {
        perror("create sendMsg thread error");
    }
    printf("Clinet is running, you can input send message:\n");
    while (STAT_RUNNING == g_stat) {
        sleep(1);
    }
    close(cnntfd);
    return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值