天天看點

Linux epoll的用法Linux epoll的用法

Linux

epoll

的用法

epollfd_create

函數

#include <sys/epoll.h>
	int epoll_create (int __size)
           
參數 含義
__size 此參數從Linux 2.6.8後就不再使用了,但必須設定成大于零的值
傳回值 含義
>0 可用的epollfd
-1 調用失敗

epollfd_ctl

函數

有了

epollfd

,我們需要将要檢測事件的fd綁定到這個

epollfd

上,或者修改或者移除,使用

epollfd_ctl

完成

int epoll_ctl (int __epfd, int __op, int __fd,
		      struct epoll_event *__event) 
           
參數 含義
__epfd 上文中的epollfd
__op 操作類型1.(EPOLLFD_CTL_ADD)添加2.(EPOLLFD_CTL_MOD)修改3.(EPOLLFD_CTL_DEL)移除
__fd 需要被操作的描述符fd
epoll_event *__event 這是一個epollfd_event結構體位址,下文解釋
struct epoll_event
{
  uint32_t events;	/* 需要檢測fd事件标志 */
  epoll_data_t data;	/* 使用者自定義的資料 */
}
           

其中的

epoll_data_t

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
           
傳回值 含義
成功
-1 失敗

epollfd_wait

函數

int epoll_wait (int __epfd, struct epoll_event *__events,
		       int __maxevents, int __timeout);
           
參數 含義
epoll_event *__events 輸出參數,在函數調用成功後,

events

中存放的是與就緒事件相關的

epoll_event

結構體數組
__maxevents 上述數組中元素的個數
傳回值 含義
>0 有事件的fd的數量
逾時
-1 失敗

示例:

int main()
{
    epoll_event epoll_events[1024];
    int n = epoll_wait(epollfd,epoll_events,1024,1000);
    if(n<0) 
    {
        //被信号中斷
        if(errno == EINTR)
        {
            //...
        }
    }
    else if(n ==0)
    {
        //...
    }
    for (size_t i = 0; i < n; ++i) 
    {
        if(epoll_events[i].events & EPOLLIN)
        {
            //處理可讀事件
        } else if (epoll_events[i].events & EPOLLOUT)
        {
            //處理可寫事件
        } else if (epoll_events[i].events & EPOLLERR)
        {
            //處理出錯事件
        }
    }
}
           

poll

epoll_wait

函數的差別

Linux epoll的用法Linux epoll的用法

邊緣觸發模式(ET) 和 水準觸發模式 (LT)

水準觸發模式:一個事件隻要有,就會一直觸發

邊緣觸發模式:一個事件從無到有才會觸發

想不出好例子,摘抄一個吧

水準觸發

兒子:媽媽,我收到了500元的壓歲錢。

媽媽:嗯,省着點花。

兒子:媽媽,我今天花了200元買了個變形金剛。

媽媽:以後不要亂花錢。

兒子:媽媽,我今天買了好多好吃的,還剩下100元。

媽媽:用完了這些錢,我可不會再給你錢了。

兒子:媽媽,那100元我沒花,我攢起來了

媽媽:這才是明智的做法!

兒子:媽媽,那100元我還沒花,我還有錢的。

媽媽:嗯,繼續保持。

兒子:媽媽,我還有100元錢。

媽媽:…

接下來的情形就是沒完沒了了:隻要兒子一直有錢,他就一直會向他的媽媽彙報。LT模式下,隻要核心緩沖區中還有未讀資料,就會一直傳回描述符的就緒狀态,即不斷地喚醒應用程序。在上面的例子中,兒子是緩沖區,錢是資料,媽媽則是應用程序了解兒子的壓歲錢狀況(讀操作)。

邊緣觸發

兒子:媽媽,我收到了500元的壓歲錢。

媽媽:嗯,省着點花。

(兒子使用壓歲錢購買了變形金剛和零食。)

兒子:

媽媽:兒子你倒是說話啊?壓歲錢呢?

這個就是ET模式,兒子隻在第一次收到壓歲錢時通知媽媽,接下來兒子怎麼把壓歲錢花掉并沒有通知媽媽。即兒子從沒錢變成有錢,需要通知媽媽,接下來錢變少了,則不會再通知媽媽了。在ET模式下, 緩沖區從不可讀變成可讀,會喚醒應用程序,緩沖區資料變少的情況,則不會再喚醒應用程序。

/** 
 * 驗證epoll的LT與ET模式的差別,epoll_server.cpp
 * zhangyl 2019.04.01
 */
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<poll.h>
#include<iostream>
#include<string.h>
#include<vector>
#include<errno.h>
#include<iostream>

int main()
{
    //建立一個監聽socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        std::cout << "create listen socket error" << std::endl;
        return -1;
    }

    //設定重用ip位址和端口号
    int on = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));


    //将監聽socker設定為非阻塞的
    int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
    int newSocketFlag = oldSocketFlag | O_NONBLOCK;
    if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
    {
        close(listenfd);
        std::cout << "set listenfd to nonblock error" << std::endl;
        return -1;
    }

    //初始化伺服器位址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);

    if (bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1)
    {
        std::cout << "bind listen socker error." << std::endl;
        close(listenfd);
        return -1;
    }

    //啟動監聽
    if (listen(listenfd, SOMAXCONN) == -1)
    {
        std::cout << "listen error." << std::endl;
        close(listenfd);
        return -1;
    }


    //建立epollfd
    int epollfd = epoll_create(1);
    if (epollfd == -1)
    {
        std::cout << "create epollfd error." << std::endl;
        close(listenfd);
        return -1;
    }

    epoll_event listen_fd_event;
    listen_fd_event.data.fd = listenfd;
    listen_fd_event.events = EPOLLIN;
    //取消注釋掉這一行,則使用ET模式
    //listen_fd_event.events |= EPOLLET;

    //将監聽sokcet綁定到epollfd上去
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
    {
        std::cout << "epoll_ctl error" << std::endl;
        close(listenfd);
        return -1;
    }

    int n;
    while (true)
    {
        epoll_event epoll_events[1024];
        n = epoll_wait(epollfd, epoll_events, 1024, 1000);
        if (n < 0)
        {
            //被信号中斷
            if (errno == EINTR) 
                continue;

            //出錯,退出
            break;
        }
        else if (n == 0)
        {
            //逾時,繼續
            continue;
        }
		
        for (size_t i = 0; i < n; ++i)
        {
            //事件可讀
            if (epoll_events[i].events & EPOLLIN)
            {
                if (epoll_events[i].data.fd == listenfd)
                {
                    //偵聽socket,接受新連接配接
                    struct sockaddr_in clientaddr;
                    socklen_t clientaddrlen = sizeof(clientaddr);
                    int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
                    if (clientfd != -1)
                    {
                        int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
                        int newSocketFlag = oldSocketFlag | O_NONBLOCK;
                        if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1)
                        {
                            close(clientfd);
                            std::cout << "set clientfd to nonblocking error." << std::endl;
                        }
                        else
                        {
                            epoll_event client_fd_event;
                            client_fd_event.data.fd = clientfd;
                            client_fd_event.events = EPOLLIN;
                            //取消注釋這一行,則使用ET模式
                            //client_fd_event.events |= EPOLLET; 
                            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
                            {
                                std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
                            }
                            else
                            {
                                std::cout << "add client fd to epollfd error" << std::endl;
                                close(clientfd);
                            }
                        }
                    }
                }
                else
                {
                    std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
                    //普通clientfd
                    char ch;
                    //每次隻收一個位元組
                    int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
                    if (m == 0)
                    {
                        //對端關閉了連接配接,從epollfd上移除clientfd
                        if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
                        {
                            std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
                        }
                        close(epoll_events[i].data.fd);
                    }
                    else if (m < 0)
                    {
                        //出錯
                        if (errno != EWOULDBLOCK && errno != EINTR)
                        {
                            if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
                            {
                                std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
                            }
                            close(epoll_events[i].data.fd);
                        }
                    }
                    else
                    {
                        //正常收到資料
                        std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;
                    }
                }
            }
            else if (epoll_events[i].events & POLLERR)
            {
                // TODO 暫不處理
            }
        }
    }

    close(listenfd);
    return 0;
}

           
Linux epoll的用法Linux epoll的用法

現在采用的是一個水準模式,隻要有資料可讀,就會觸發事件

現在采用邊緣觸發模式

Linux epoll的用法Linux epoll的用法

采用邊緣觸發模式,隻有有新資料到來才會觸發,是以就有了上面的現象

繼續閱讀