I/O多路复用之epoll服务器

本文详细介绍了Linux特有的epoll I/O多路复用函数,包括epoll_create、epoll_ctl和epoll_wait的使用,以及ET和LT模式的区别。epoll在效率和性能上优于select和poll,它通过红黑树管理和就绪队列优化了事件处理,并使用内存映射减少数据拷贝。此外,文章还提供了epoll服务器的代码实现和运行结果。
摘要由CSDN通过智能技术生成

1、epoll服务器的函数
epoll是linux特有的I/O复用函数,它的实现和select和poll有很大的差异。epoll是使用一组函数来完成任务,而不是单个的函数。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,而无需向select和poll那样每次调用都要重复传入文件描述符集或事件集,但是epoll却需要一个额外的文件描述符,来唯一标识内核中的这个事件表。

1、epoll_create函数:
这里写图片描述
这个函数是用来创建epoll事件的模型,它只有一个参数来表示创建的这个事件表是需要多大的。函数返回的文件描述符作为epoll其他函数的第一个参数,用来指定要访问的内核时间表。

2、epoll_ctl函数:
这里写图片描述
这个函数有四个参数,epfd是epoll_create这个函数的返回值,fd是要操作的文件描述符,op参数是指定的操作类型,而操作类型有以下三种:
这里写图片描述
EPOLL_CTL_ADD:往事件表上注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件

event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:
这里写图片描述
events成员描述事件类型,而epoll_event中的data成员用于存储用户数据,而其中epoll_data_t定义如上图所示。
epoll_data_t是一个联合体,其中用的最多的是fd,它用来指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但是epoll_data_t是一个联合体我们不能同时使用ptr成员和fd成员。
epoll_ctl成功的时候返回的是0,失败就会返回-1,并设置errno。

3、epoll_wait函数:
这里写图片描述
第一个参数epfd还是epoll_create函数的返回值,maxevents表示的是最多监听的 事件,它是必须大于0的。
timeout是要等待的时间,它的含义与poll函数中的timeout的含义是相同的。
epoll_wait这个函数如果检测到事件,就将所有继续的事件从内核事件表复制到它的第二个参数events的数组中去。

2、ET模式和LT模式
epoll对文件描述符的操作有两种模式:LT模式(水平触发)和ET模式(边沿触发),LT是默认的工作模式,在这种模式下epoll相当于一个效率较高的epoll,当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作方式。
ET模式在很大程度上降低了同一个epoll事件的被重复触发的次数,因此它的效率要比LT模式要高。

3、三种服务器的比较

这里写图片描述

4、epoll服务器的优点

1、底层采用回调机制来激活节点,将已经就绪的文件描述符加到就绪队列中去。
2、当有了新的事件就绪的时候这个事件就会被加到epoll事件的红黑树中去,红黑树的增删查改的时间复杂度(O (N*lgN))是要比数组高很多
3、epoll_wait获得就绪的文件描述符是从就绪队列中获取的,它的时间复杂度为O (1)这是epoll的时间复杂度也是epoll高效的原因。
4、epoll关心的文件描述符是没有上限的
5、就绪队列中的节点会映射到epoll_wait中的events结构体中,节省了一次内核态到用户态的数据拷贝,这是用的mmap技术(内存映射)。
6、就绪事件的陈列的方式是不同的,epoll访问的是就绪队列避免了访问没价值的数据,而select是一个数组保存的。

5、代码实现
epoll.c:

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

#define MAX_EVENTS 10
#define SIZE 64

typedef struct epbuff{
    int fd;
    char buf[2048];
    int index;
}epbuff_t,*epbuff_p,**epbuff_pp;


static epbuff_p alloc_buff(int fd) //此处开空间之后后面就要是ptr指向的一段空间
{
    epbuff_p ptr = (epbuff_p)malloc(sizeof(epbuff_t));
    if(NULL == ptr)
    {
        perror("malloc");
        exit(9);
    }
    ptr->fd = fd;
    return ptr;
}

static void dealloc(epbuff_p ptr)//既然要malloc就得去free
{
    if(NULL != ptr)
    {
        free(ptr);
        ptr = NULL;
    }
}

static usage(const char* proc)
{
    printf("Usage: [local_ip],[local_port]%s\n",proc);
}

int setnonblocking(int sock) //将文件描述符设置为非阻塞的
{
    int old_option = fcntl(sock,F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(sock,F_SETFL,new_option);
    return old_option;
}


int startup(const char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }

    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);


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

    if(listen(sock,10) < 0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

//实现的是ET版本的epoll服务器  要实现非阻塞版本
int myread(int sock,char* buf)
{
    int len = 0;
    int total = 0;
    while((len = read(sock,buf+total,1024) > 0) && (len = 1024 ))
    {
        total += len;
    }
    if(len > 0 && len< 1024)
    {
        total += len;
    }
    buf[total] = '\0';
    return total;
}



int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        usage(argv[0]);
        return 1;
    }

    int listen_sock = startup(argv[1],atoi(argv[2]));

    int epfd = epoll_create(SIZE);//先创建一个红黑树和队列
    if(epfd < 0)
    {
        perror("epoll_create");
        exit(5);
    }

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.ptr = alloc_buff(listen_sock);

    setnonblocking(listen_sock);//由于是ET工作模式下  将listen_sock设置为非阻塞的状态

    if(epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev) < 0)//往事件表上注册fd事件(把listen_sock)加到红黑树上去
    {
        perror("epoll_ctl");
        exit(6);
    }

    int timeout = 5000;
    int nums = -1;

    struct epoll_event revs[MAX_EVENTS];
    while(1)
    {
        nums = epoll_wait(epfd,revs,MAX_EVENTS,timeout);
        switch(nums)
        {
            case 0:
                printf("time out!!!...\n");
                break;
            case -1:
                perror("epoll_wait");
                break;
            default:
                {
                    int i = 0;
                    for(;i < nums ;i++)
                    {
                        int sock = ((epbuff_p)(revs[i].data.ptr))->fd;
                        if(sock == listen_sock && revs[i].events == EPOLLIN)
                        {
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
                            if(new_sock < 0)
                            {
                                perror("myaccept");
                                continue;
                            }
                            printf("get a new client! ip: %s port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                            ev.events = EPOLLIN | EPOLLOUT;
                            setnonblocking(new_sock);
                            ev.data.ptr = alloc_buff(new_sock);
                            int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);
                            if(ret < 0)
                            {
                                perror("epoll_ctl");
                                exit(7);
                            }
                        }
                        else if(sock != listen_sock && (revs[i].events & EPOLLIN))
                        {
                            //char buf[1024];
                            char* buf = ((epbuff_p)(revs[i].data.ptr))->buf;
                            ssize_t s = myread(sock,buf);
                            if( s > 0)
                            {
                                buf[s] = '\0';
                                printf("client#:%s\n",buf);  //如果读成功之后就会直接关心其写操作
                                ev.events=EPOLLOUT;
                                epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev);
                            }
                            else if(s == 0) // 直接关闭连接
                            {
                                printf("client is close!\n");
                                dealloc(revs[i].data.ptr);
                                epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
                                close(sock);
                            }
                            else
                            {
                                perror("read");
                                dealloc(revs[i].data.ptr);
                                int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
                                if(ret < 0)
                                {
                                    perror("epoll_ctl");
                                    exit(8);
                                }
                                close(sock);
                            }
                        }
                        else if(sock != listen_sock && (revs[i].events &EPOLLOUT))
                        {
                            const char* msg = "HTTP/1.0 200 OK\r\n\r\n Hello epoll!  ";
                            //const char* msg = "Hello epoll!\n";
                            ssize_t s = write(sock,msg,strlen(msg));
                            if(s < 0)
                            {
                                perror("write");
                                exit(10);
                            }
                            dealloc(revs[i].data.ptr);
                            epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
                            close(sock);
                        }
                    }
                    break;
                }
        }
    }

    return 0;
}

6、运行结果

服务器端:
这里写图片描述
客户端:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值