搭建高并发服务器--高效I/O复用(epoll)

正如前文所说,epoll是目前linux上一种高效的I/O复用技术。

接下来就浅析一下epoll的事件处理。

epoll有两种工作模式:LT(Level Trigger,电平触发)模式和ET(Edge Trigger,边缘触发)模式。

epoll默认的是LT工作模式,在这种模式下epoll相当于一个高效的poll。在linux内核中维护着一个事件列表,只有被触发的有I/O读写的事件句柄才会被epoll返回。而不像select那样需要把有I/O读写的和没有I/O读写的句柄全部遍历一遍,很蛋疼的事情!

当往epoll内核事件表中注册一个文件描述符的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。即epoll进入高效的工作模式。

ET模式下的读写事件,内核只通知一次,如果没有被处理,内核也将不在通知。但在LT模式下工作,只要有没有处理的读写事件,内核都将不停的通知。

因此在epoll工作在ET模式下,一单epoll_wait检测到有时间发生并且将此事件通知给应用程序,那么应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。由此可见,ET模式在很大程度上降低了同一个epoll事件被触发的次数,因此效率要比LT模式高,但同时也增加了应用程序对ET模式下的读写事件的处理难度。

epoll_wait函数原型:

#include <sys/epoll.h>

int epoll_create(int size);

size参数其实并不起作用,它只是给内核一个提示,告诉它事件表需要多大,该函数的返回值标识着这个内核的epoll资源,所有对epoll的操作都是基于该返回值的。


epoll_ctl函数:

#include <sys/epoll.h>

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

该函数的作用就是把应用程序感兴趣的时间添加(修改或删除)到epoll事件列表中。

fd参数是需要操作的文件描述符,op指定操作类型,具体的操作类型有如下三种:

EPOLL_CTL_ADD:将感兴趣的fd上的事件注册到epoll时间表中

EPOLL_CTL_MOD:修改fd上的注册事件,如把写事件修改为读事件

EPOLL_CTL_DEL:删除fd上的注册事件

event参数指定事件,它是epoll_event结构体的指针类型,其定义如下:

struct epoll_event

{

    __uint32_t events;     //epoll事件

    epoll_data_t data;     //用户数据

}

typedef struct epoll_data

{

    void* ptr;

    int fd;

    uint32_t u32;

    uint64_t u64;

}


epoll_wait函数:

#include <sys/epoll.h>

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

该函数的返回值为就绪的文件描述符的个数,调用失败返回-1,并设置errno值

epfd参数为epoll系统资源的标识符,是epoll_create函数的返回值

events参数是返回的就绪的文件描述符的列表指针

timeout是超时事件,单位为毫秒。

maxevents参数指定最多监听多少个事件,该值必须大于0


如何用epoll实现一个简单的高并发服务器程序呢?

以获取服务器时间为例,请看下面的代码:

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




#define MAX_EVENTS  5000
#define PORT        8888



int set_non_block(int fd)
{
    int old_option = fcntl(fd, F_GETFL, 0);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    
    return old_option;
}


void event_add(int epfd, int fd, int events)
{
    struct epoll_event evn;
    evn.events = events;
    evn.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evn);
}


void event_mod(int epfd, int fd, int events)
{
    struct epoll_event evn;
    evn.events = events;
    evn.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &evn);
}


void event_del(int epfd, int fd)
{
    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, 0);
}




int main()
{
    int sfd;
    int epfd;
    int num;
    struct epoll_event events[MAX_EVENTS];


    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd <= 0)
    {
        printf("socket() error[%d]:%s", errno, strerror(errno));
        return -1;
    }


    set_non_block(sfd);


    struct sockaddr_in saddr;
    bzero(&saddr, sizeof(saddr));
    saddr.sin_port = htons(PORT);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;


    if(bind(sfd, (struct sockaddr*)&saddr, sizeof(saddr)) == -1)
    {
        printf("server bind port(%d) error[%d]:%s\n", PORT, errno, strerror(errno));
        return -1;
    }


    if(listen(sfd, 1024) == -1)
    {
        printf("server socket listen error[%d]:%s\n", errno, strerror(errno));
        return -1;
    }


    printf("server listen fd = %d, port: %d\n", sfd, PORT);


    epfd = epoll_create(MAX_EVENTS);


    event_add(epfd, sfd, EPOLLIN);


    int i;
    
    while(1)
    {
        num = epoll_wait(epfd, events, MAX_EVENTS, 500);
        if(num < 0)
        {
            printf("epoll wait error[%d]:%s\n", errno, strerror(errno));
            continue;
        }
        if (!num)
        {
            continue;
        }


        for(i = 0; i < num; i++)
        {
            int fd = events[i].data.fd;
            if (fd ==  sfd)
            {
                //printf("new connection comes!\n");
                struct sockaddr_in addr;
                socklen_t len = sizeof(struct sockaddr_in);
                int nfd;
                //accept;
                if((nfd = accept(sfd, (struct sockaddr*)&addr, &len)) == -1)
                {
                    if (errno == EAGAIN)
                    {
                        break;
                    }
                    else if( errno == EINTR)
                    {
                        continue;
                    }
                    break;
                }
                set_non_block(nfd);


                event_add(epfd, nfd, EPOLLIN|EPOLLET);
            }
            else
            {
                if(events[i].events & EPOLLIN)
                {
                    char buf[512] = {0};


                    //实际工作中,可能接收的数据由于某种原因不能一次接收完,需要做相应的处理
                    //由于工作在ET模式,内核不会再次通知,因此需要自己手动处理
                    int len = recv(fd, buf, sizeof(buf), 0);        
                    if(len < 0)
                    {
                        if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
                        {
                            printf("read later; %s\n", strerror(errno));
                            continue;
                        }
                        close(fd);
                        event_del(epfd, fd);
                    }
                    else if (len == 0)
                    {
                        close(fd);
                        event_del(epfd, fd);
                    }
                    else
                    {
                        //printf("recv data: %s\n", buf);
                        event_mod(epfd, fd, EPOLLOUT|EPOLLET);
                    }
                }
                else if(events[i].events & EPOLLOUT)   //write
                {
                    time_t           now;
                    struct tm       *p;
                    char             buf[128];
                    int              fd = events[i].data.fd;


                    now = time(NULL);
                    p = localtime(&now);
                    p->tm_year = p->tm_year + 1900;
                    p->tm_mon = p->tm_mon + 1;
                    sprintf(buf, "%04d-%02d-%02d %d:%d:%d", p->tm_year, p->tm_mon, p->tm_mday,  p->tm_hour, p->tm_min, p->tm_sec);


                    //实际工作中,可能发送的数据由于某种原因不能一次发送完,需要做相应的处理
                    //由于工作在ET模式,内核不会再次通知,因此需要自己手动处理
                    send(fd, buf, strlen(buf), 0);
                    //printf("send buf:%s\n", buf);


                    event_del(epfd, fd);
                    close(fd);
                }
                else if (events[i].events & EPOLLERR ||events[i].events & EPOLLHUP)
                {
                    event_del(epfd, fd);
                    close(fd);
                    continue;
                }
            }
            
        }
    }


    close(sfd);
    close(epfd);


    return 0;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值