IO复用之epoll系统调用

IO复用的第三个系统调用就是epoll。
epoll并不是由一个函数来实现,而是一组函数。
一.epoll()函数
**I.**epoll函数的创建:

#include<sys/epoll.h>
int epoll_create(int size);

唯一参数size只是给内核一个提示,告诉它需要多大的事件表;

**II.**epoll内核事件表的操作:

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);

第一个参数fd为要操作的文件描述符;
第二个参数op指定操作类型(往事件表中注册,修改,删除fd上的注册事件);
操作类型有:

EPOLL_CTL_ADD//往事件表中注册fd上的事件
EPOLL_CTL_MOD//修改fd上的注册事件
EPOLL_CTL_DEL//删除fd上的注册事件

第三个参数event 指定事件,它是epoll_event结构指针类型。

struct epoll_event
{
    _uint32_t events;//epoll事件
    epoll_data_t data;//用户数据
}

其中events成员描述事件类型,epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对用的宏前面加上“E”。
data成员用于存储用户数据,epoll_data_t是一个联合体:

typedef_union epoll_data
{
    void* ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
};

epoll_data_t是一个联合体,其四个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可以用来指定与fd相关的用户数据。

**III.**epoll系列系统调用的主要接口:

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

第一个参数fd为要操作的文件描述符;
此函数如果检测到事件,就将所有就绪事件从内核事件表中(由epfd中的参数指定)复制到它的第二个参数events指定的数组中,这个数组只输出epoll_wait检测出的就绪事件。
第三个参数maxevents指定最多监听对少个事件。
第四个参数是epoll的超时时间,单位为毫秒。

二.epoll的两种模式
epoll对文件描述符的操作有两种模式:LT(level trigger电平触发)和ET(edge trigger边沿触发)。LT模式是默认模式,LT模式与ET模式的区别如下:

 LT模式:在数据到达后,无论程序是没有接收,还是接收了,但没有接收完,下一轮epoll_wait仍然会提醒应用程序该描述符上有数据,直到数据被接收完;
LT模式两种方法把描述符放入就绪队列
1.设备驱动,调回调方法放入就绪队列
2.把就绪的描述返还给就绪队列

ET模式:在数据到达后,无论程序是没有接收,还是接收了,但没有接收完,都只提醒一次,下一轮不在提醒应用程序该描述符上有数据。所以,要求程序在收到提醒时必须将数据接收完,否则将会出现丢掉数据的可能。
ET模式一种方法把描述符放入就绪队列
1.设备驱动,调回调方法放入就绪队列

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

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

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

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6666);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

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

    while(1)
    {
        char buff[128] = {0};
        printf("input:\n");
        fgets(buff,128,stdin);

        if(strncmp(buff,"end",3) == 0)
        {
            break;
        }
        send(sockfd,buff,strlen(buff),0);

        memset(buff,0,128);
        recv(sockfd,buff,127,0);
        printf("buff = %s\n",buff);
    }
    close(sockfd);
}

服务器端:

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

#define MAXFD 10
int create_socket();
void epoll_add(int epfd,int fd);
void epoll_del(int epfd,int fd);

int main()
{
    int sockfd = create_socket();
    assert(sockfd != -1);

    int epfd = epoll_create(MAXFD);
    assert(epfd != -1);

    epoll_add(epfd,sockfd);//向内核事件表添加文件描述符

    struct epoll_event events[MAXFD];//events数组存放就绪事件

    while(1)
    {
        int n = epoll_wait(epfd,events,MAXFD,5000);//n就是有多少个文件描述符就绪

        if(n == -1)
        {
            perror("epoll wait error\n");   
        }
        else if(n == 0)
        {
            printf("timeout!\n");
            continue;
        }
        else
        {
            int i = 0;
            for(;i < n;i++)
            {
                int fd = events[i].data.fd;
                if(fd == -1)
                {
                    continue;
                }
                if(events[i].events & EPOLLIN)
                {
                    if(fd == sockfd)//监听套接字
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);

                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);

                        if(c < 0)
                        {
                            continue;
                        }

                        printf("accept :%d\n",c);
                        epoll_add(epfd,c);

                    }
                    else
                    {
                        char buff[128] = {0};
                        if((recv(fd,buff,127,0))<=0)
                        {
//                          close(fd);
                            epoll_del(epfd,fd);
                            close(fd);
                            printf("one client over\n");
                            continue;
                        }

                        printf("buff %d = %s\n",fd,buff);
                        send(fd,"OK",2,0);
                    }
                }


            }
        }
    }


}

int create_socket()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));

    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6666);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(res == -1)
    {
        return -1;
    }

    listen(sockfd,5);

    return sockfd;
}
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;

    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
    {
        perror("epoll_ctl_add error\n");
    }
}

void epoll_del(int epfd,int fd)
{

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;

    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) == -1)
    {
        perror("epoll_del_add error\n");
    }
}

非阻塞的服务器端:

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

#define MAXFD 10
int create_socket();
void epoll_add(int epfd,int fd);
void epoll_del(int epfd,int fd);
void setnonblock(int fd);

int main()
{
    int sockfd = create_socket();
    assert(sockfd != -1);

    int epfd = epoll_create(MAXFD);
    assert(epfd != -1);

    epoll_add(epfd,sockfd);//向内核事件表添加文件描述符

    struct epoll_event events[MAXFD];//events数组存放就绪事件

    while(1)
    {
        int n = epoll_wait(epfd,events,MAXFD,5000);//n就是有多少个文件描述符就绪

        if(n == -1)
        {
            perror("epoll wait error\n");   
        }
        else if(n == 0)
        {
            printf("timeout!\n");
            continue;
        }
        else
        {
            int i = 0;
            for(;i < n;i++)
            {
                int fd = events[i].data.fd;
                if(fd == -1)
                {
                    continue;
                }
                if(events[i].events & EPOLLIN)
                {
                    if(fd == sockfd)//监听套接字
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);

                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);

                        if(c < 0)
                        {
                            continue;
                        }

                        printf("accept :%d\n",c);
                        epoll_add(epfd,c);

                    }
                    else
                    {
                        while(1)
                        {
                            char buff[128] = {0};
                            int num = recv(fd,buff,1,0);
                            if(num == -1)
                            {
                                send(fd,"ok",2,0);
                                break;
                            }
                            else if(num == 0)
                            {
                                epoll_del(epfd,fd);
                                close(fd);
                                printf("one client over\n");
                            }
                            else
                            {
                                printf("buff = %s\n",buff);
                            }
                        }       
                    }
                }
            }
        }
    }


}

void setnonblock(int fd)
{
    int oldfl = fcntl(fd,F_GETFL);
    int newfl = oldfl | O_NONBLOCK;

    if(fcntl(fd,F_SETFL,newfl) == -1)
    {
        perror("fcntl error\n");
    }
}
int create_socket()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));

    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6666);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(res == -1)
    {
        return -1;
    }

    listen(sockfd,5);

    return sockfd;
}
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = fd;

    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
    {
        perror("epoll_ctl_add error\n");
    }
    setnonblock(fd);

}

void epoll_del(int epfd,int fd)
{

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;

    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) == -1)
    {
        perror("epoll_del_add error\n");
    }
}

四.结果展示
这是epoll下LT模式的代码运行结果:
这里写图片描述
ET模式其实只需要在LT代码中改一点:
这里写图片描述
这里我们recv每次接收127个字节,我们试试把它改为1个字节:
这里写图片描述
结果我们可以发现是这样的:
这里写图片描述
这就是我们ET模式下,当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件,但是流式传输,我们没有接受完上一个的数据,接下来还是会输出的上个数据。
这里有个问题:
就是一个客户端结束并没有像之前那样告诉服务器一个客户端退出,而是发生异常终止的现象?
经过调试我们发现是在一个客户端退出时我们的服务器收到这样一个信号,SIGPIPE
这里写图片描述
而这个信号在书上是这样解释的:
这里写图片描述
将一个socket 设置成阻塞模式和非阻塞模式,使用fcntl方法,即:
设置成非阻塞模式:

先用fcntl的F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK;

即:

 flags = fcntl(sockfd, F_GETFL, 0);  //获取文件的flags值。

fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式;

同时在接收和发送数据时,需要使用MSG_DONTWAIT标志

即:

recv,recvfrom和send,sendto数据时,将flag设置为MSG_DONTWAIT。

设置成阻塞模式:

先用fcntl的F_GETFL获取flags,用F_SETFL设置flags&~O_NONBLOCK;

即:

flags  = fcntl(sockfd,F_GETFL,0);  //获取文件的flags值。

fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK);    //设置成阻塞模式; 

大家可以参考这个博客

采用非阻塞的方式循环读取数据。
当没有数据是返回-1,只要没有返回-1就说明有数据,我们就不断循环的读数据。
结果为:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_39110766/article/details/80041735