欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux I/O复用--------epoll

程序员文章站 2022-06-13 22:30:49
...

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

int epoll_create(int size);用于创建一个文件描述符来唯一标识内核中的事件表。size不起作用,他只是告诉内核事件表需要多大。

对事件表的操作:

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

epfd:是int epoll_create();函数的返回值(标识内核事件表的文件描述符)。

Op:指定操作类型:

            1:EPOLL_CTL_ADD:往内核事件表上注册fd的事件

            2:EPOLL_CTL_MOD:修改内核时间表上的事件

            3:EPOLL_CTL_DEL:删除内核事件表上的事件

Struct epoll_event* event:此处的event只是一个结构体变量

Struct epoll_event

{

          _uint32_t event;  //epoll事件

         Epoll_data_t data: //用户数据

}

Typedef union epoll_data

{

        Void* ptr;

        Int fd;   //关注的事件类型

}epoll_data_t;

epoll_wait():在一段时间内等待一组文件描述符上的事件

Int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);  成功时返回就绪文件描述符的个数  失败返回-1

Epfd:是int epoll_create();函数的返回值(标识内核事件表的文件描述符)。

struct epoll_event* events:events是struct epoll_event类型的一个数组(存在于用户空间,用于输出epoll_wait()检测到的就绪事件)

maxevents:数组的大小(指定监听多少个事件)

timeout:超时时间,以毫秒记

epoll_wait函数如果检测到事件,就将所有有序的事件从内核事件表中复制到它的第二个参数events指向的数组中,这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样即用于传入用户注册的事件,又用于输出内核检测到的就绪事件,这要就极大地提高了索引就绪文件描述符的效率。

一幅图可能能让大家更好的理解epoll带给我们的方便。

linux I/O复用--------epoll

epoll的优点:

1:用户关注的文件描述符记录在内核事件表中,无需两次拷贝(实际有一次拷贝,就是epoll_wait()函数检测到事件,就将就绪事件从内核事件表中拷贝到用户空间events结构体数组中,但是这次拷贝很小几乎可以忽略不计)

2:关注的文件描述符的个数以及大小受系统限制(系统有规定一个进程所能打开的文件描述符的个数)

3:返回的就是就绪文件描述符,所以检索文件描述符的时间复杂度为O(1)

epoll的缺点:

它的缺点就是它关注的文件描述符很少,就绪的文件描述符很多时,虽然它不需要轮询检测但是它调用回调函数也需要消耗栈帧空间,

epoll的 客户端和服务器发送数据的代码:

/********************客户端************************/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>

void main()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);

    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(6000);
    ser.sin_addr.s_addr=inet_addr("127.0.0.1");

    
    int n=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
    assert(n!=-1);

    while(1)
    {
        char buff[128]={0};
        printf("input:\n");
        fgets(buff,128,stdin);
        
        if(strncmp(buff,"end",3)==0)
        {
            close(n);
            break;
        }
        send(sockfd,buff,strlen(buff)-1,0);
        memset(buff,0,128);
        recv(sockfd,buff,127,0);
        printf("%s\n",buff);
    }
    close(sockfd);
}
/*************服务器***************/
#define _GNU_SOURCE 1
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<sys/select.h>
#include<sys/epoll.h>
#define MAX 1024


void main()
{
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    assert(listenfd!=-1);
    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(6000);
    ser.sin_addr.s_addr=inet_addr("127.0.0.1");



    int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
    assert(res!=-1);

    listen(listenfd,5);

    struct epoll_event event;
    int epfd=epoll_create(5);

    event.data.fd=listenfd;
    event.events=EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);
    struct epoll_event events[MAX];

    while(1)
    {
         int n=epoll_wait(epfd,events,MAX,-1);
         printf("epoll_wait return\n");
         if(n==-1)
         {
             printf("epoll error\n");
             exit(0);
         }
         else
         {
             int i=0;
             for(;i<n;++i)
             {
                int fd=events[i].data.fd;
    
                if(fd==listenfd)
                {
                    int len=sizeof(cli);
                    int c=accept(listenfd,(struct sockaddr*)&cli,&len);
                    if(c<0)
                    {
                        printf("one client link error\n");
                        continue;
                    }
                    printf("one client link\n");
                    event.data.fd=c;
                    event.events=EPOLLIN|EPOLLRDHUP;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,c,&event);
                }
                    
                else
                {

                    if(events[i].events&EPOLLRDHUP)
                    {
                        close(fd);
                        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
                        printf("events error\n");
                        continue;
                    }
                    if(events[i].events&EPOLLIN)
                    {
                        while(1)
                        {

                            char buff[128]={0};
                            int n=recv(fd,buff,127,0);
                            if(n<=0)
                            {
                                printf("not get\n");
                                close(fd);
                                epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
                                continue;
                            }   
                            printf("%d  %s\n",fd,buff);
                            send(fd,"ok",2,0);
                        }
                    }
                }
             }
         }
    }
}
相关标签: linux I/O复用