1. 程式人生 > >epoll例項2

epoll例項2

1. epoll的優越性
上一節介紹的select有幾個缺點:

存在最多監聽的描述符上限FD_SETSIZE
每次被喚醒時必須遍歷才能知道是哪個描述符上狀態ready,CPU隨描述符數量線性增長
描述符集需要從核心copy到使用者態
這幾個缺點反過來正是epoll的優點,或者說epoll就是為了解決這些問題誕生的:

沒有最多監聽的描述符上限FD_SETSIZE,只受最多檔案描述符的限制,在系統中可以使用ulimit -n設定,運維一般會將其設定成20萬以上
每次被喚醒時返回的是所有ready的描述符,同時還帶有ready的型別
核心態與使用者態共享記憶體,不需要copy
2. 簡述epoll的工作過程
2.1 建立
首先由epoll_create建立epoll的例項,返回一個用來標識此例項的檔案描述符。

2.2 控制
通過epoll_ctl註冊感興趣的檔案描述符,這些檔案描述符的集合也被稱為epoll set。

2.3 阻塞
最後呼叫epoll_wait阻塞等待核心通知。

3. 水平觸發(LB)和邊緣觸發(EB)
epoll的核心通知機制有水平觸發和邊緣觸發兩種表現形式,我們在下面例子中看一下兩者的區別。

有一個代表讀的檔案描述符(rfd)註冊在epoll上

在管道的寫端,寫者寫入了2KB資料

呼叫epoll_wait會返回rfd作為ready的檔案描述符

管道讀端從rfd讀取了1KB資料

再次呼叫epoll_wait

如果rfd檔案描述符以ET的方式加入epoll的描述符集,那麼上邊最後一步就會繼續阻塞,儘管rfd上還有剩餘的資料沒有讀完。相反LT模式下,檔案描述符上資料沒有讀完就會一直通知下去。

4. epoll的兩個資料結構
4.1 epoll_event
struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};
引數events:

此引數是一個位集合,可能有以下幾種中的組合:

EPOLLIN:適用read操作,包括對端正常關閉

EPOLLOUT:適用write操作

EPOLLRDHUP :TCP對端關閉了連線

EPOLLPRI:對於read操作有緊急的資料到來

EPOLLERR:檔案描述符上的錯誤,不需要設定在events上,因為epoll總是會等待錯誤

EPOLLHUP:與上邊EPOLLERR相同

EPOLLET:設定邊緣觸發方式

EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL佇列裡

4.2 epoll_data
typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;
這個結構體有些tricky,是四種不同資料型別的union,實際上設計者的意思是內容是什麼交給使用者決定,相當於一個上下文。一般使用int fd來區分是哪個socket發生的事件。

5. API詳解
5.1 epoll_create
int epoll_create(int size);
int epoll_create1(int flags);
epoll_create建立了一個epoll的例項,請求核心為size大小的檔案描述符分配一個事件通知物件。實際上size只是一個提示,並沒有什麼實際的作用。此函式返回用來標識epoll例項的檔案描述符,此後所有對epoll的請求都要通過這個檔案描述符。當不需要使用epoll時需要使用close關閉這個檔案描述符,告訴核心銷燬例項。

5.2 epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
在epoll例項epfd上的控制操作,op的取值有以下三種:

EPOLL_CTL_ADD: 將fd帶著事件引數event註冊到epfd上

EPOLL_CTL_MOD: 改變事件

EPOLL_CTL_DEL: 從epfd上刪除

返回的錯誤碼請參閱man手冊。

5.3 epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待註冊在epfd上的事件,事件再events引數中帶出。對於timeout引數:

-1:永遠等待
0:不等待直接返回
其他:在超時時間內沒有事件發生,返回0
6. 完整C++程式碼示例
與上節一樣,網上的epoll程式碼基本都是已C語言方式寫在同一個main函式中,本人實現了一個完全正確的、可讀性好的版本。除了將select替換成了epoll,其他細節還有些變化,例如將sockaddr_in替換成了現代氣息的addrinfo,支援ipv4/ipv6等等。

上一節中按照工程習慣,在SelectServer類中增加了虛擬函式回掉介面,供派生類實現。有讀者反應會沖淡主題,在這個EpollServer中沒再繼續設計虛擬函式介面。猛擊此處下載原始碼。

6.1 stdafx.h
#ifndef STDAFX_H
#define STDAFX_H
 
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
 
#endif // STDAFX_H
6.2 epollserver.h
#ifndef EPOLLSERVER_H
#define EPOLLSERVER_H
 
/**
 * @brief The EpollServer class
 * @author [email protected]
 */
 
class EpollServer
{
public:
    EpollServer();
    ~EpollServer();
    bool _listen(const std::string &port);
    bool pulse();
 
private:
    bool setUnblock(int socket);
    bool createEpoll();
    bool addEpoll(int socket, epoll_event &e);
    bool isEpollError(const epoll_event& e);
    bool isEpollNewConnection(const epoll_event& e);
    bool _error(const epoll_event& e);
    bool _accept(epoll_event &e);
    bool _receive(const epoll_event& e);
    bool _send(int clientFd, const std::string& data);
    bool removeClient(int clientFd);
 
    addrinfo m_serverAddr;              /** server address */
    int m_listenerSocket;               /** listening socket descriptor */
    int m_epollFd;                      /** epoll operation fd */
    epoll_event m_epollEvent;           /** epoll event*/
    epoll_event* m_pEpollEvents;        /** epoll events buffer to hold notification from kernal*/
    char m_readBuf[1024];               /** buffer for client data */
};
 
#endif // EPOLLSERVER_H
6.3 epollserver.cpp
#include "stdafx.h"
#include "epollserver.h"
 
using namespace std;
#define MAXEVENTS 64
 
EpollServer::EpollServer()
    : m_pEpollEvents(NULL)
{
}
 
EpollServer::~EpollServer()
{
    if (m_pEpollEvents != NULL) {
        delete [] m_pEpollEvents;
    }
}
 
bool EpollServer::_listen(const string& port)
{
    cout << "try to listen port " << port << endl;
    addrinfo *pResult = NULL;
    memset(&(m_serverAddr), '\0', sizeof(m_serverAddr));
    m_serverAddr.ai_family = AF_UNSPEC;         /** Return IPv4 and IPv6 choices */
    m_serverAddr.ai_socktype = SOCK_STREAM;     /** We want a TCP socket */
    m_serverAddr.ai_flags = AI_PASSIVE;         /** All interfaces */
 
    if (getaddrinfo(NULL, port.c_str(), &m_serverAddr, &pResult) != 0) {
        cerr << "fail to getaddrinfo!" << endl;
        return false;
    }
    if (pResult != NULL) {
        for (addrinfo *pRes = pResult; pRes != NULL; pRes = pRes->ai_next) {
            if ((m_listenerSocket = socket (pRes->ai_family, pRes->ai_socktype, pRes->ai_protocol)) == -1) {
                cerr << "fail to create socket for " << pRes->ai_family << " " << pRes->ai_socktype << " " << pRes->ai_protocol << endl;
                continue;
            }
            if (bind(m_listenerSocket, pRes->ai_addr, pRes->ai_addrlen) == -1) {
                cerr << "fail to bind " << m_listenerSocket << " " << pRes->ai_addr << " " << pRes->ai_addrlen << endl;
                close(m_listenerSocket);
                continue;
            }
            freeaddrinfo(pResult);
            setUnblock(m_listenerSocket);
            if (listen (m_listenerSocket, SOMAXCONN) == -1) {
                cerr << "fail to listen " << m_listenerSocket << endl;
            } else {
                cout << "listen port " << port << " ok! " << endl;
                return createEpoll();                    /** We managed to bind successfully! */
            }
        }
    }
    return false;
}
 
bool EpollServer::pulse()
{
    int n = epoll_wait(m_epollFd, m_pEpollEvents, MAXEVENTS, -1);
    for (int i = 0; i < n; ++i) {
        epoll_event& e = m_pEpollEvents[i];
        if (isEpollError(e)) {
            _error(e);
        } else if (isEpollNewConnection(e)) {
            _accept(e);
        } else {
            _receive(e);
        }
    }
    return true;
}
 
bool EpollServer::setUnblock(int socket)
{
    int flag = 0;
    if ((flag = fcntl(socket, F_GETFL, 0)) != -1) {
        flag |= O_NONBLOCK;
        if (fcntl (socket, F_SETFL, flag) != -1) {
            return true;
        }
    }
    cerr << "fail to call fcntl F_SETFL for m_listenerSocket" << endl;
    return false;
}
 
bool EpollServer::createEpoll()
{
    cout << "try to creat epoll" << endl;
    if ((m_epollFd = epoll_create1(0)) == -1) {
        cerr << "fail to call epoll_create" << endl;
        return false;
    }
    m_epollEvent.data.fd = m_listenerSocket;
    m_epollEvent.events = EPOLLIN | EPOLLET;
    if (addEpoll(m_listenerSocket, m_epollEvent)) {
        m_pEpollEvents = new epoll_event[MAXEVENTS];
        cout << "create epoll ok!" << endl;
        return true;
    }
    return false;
}
 
bool EpollServer::addEpoll(int socket, epoll_event& e)
{
    if ((epoll_ctl (m_epollFd, EPOLL_CTL_ADD, socket, &e)) == -1) {
        cerr << "fail to call epoll_ctl for " << socket << endl;
        return false;
    }
    return true;
}
 
bool EpollServer::isEpollError(const epoll_event &e)
{
    return ((e.events & EPOLLERR) || (e.events & EPOLLHUP) || (!(e.events & EPOLLIN)));
}
 
bool EpollServer::isEpollNewConnection(const epoll_event &e)
{
    return (m_listenerSocket == e.data.fd);
}
 
bool EpollServer::_error(const epoll_event &e)
{
    /** An error has occured on this fd, or the socket is not ready for reading */
    cerr << "epoll error for client " << e.data.fd << endl;
    removeClient(e.data.fd);
    return true;
}
 
bool EpollServer::_accept(epoll_event &e)
{
    cout << "a new client is coming - " << e.data.fd << endl;
    sockaddr clientAddr;
    int clientFd = 0;
    socklen_t clientAddrLen = sizeof (clientAddr);
    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
 
    if ((clientFd = accept (m_listenerSocket, &clientAddr, &clientAddrLen)) == -1) {
        if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
            cerr << "fail to accept new client " << endl;
            return false;
        }
    }
    if (getnameinfo (&clientAddr, clientAddrLen, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) == -1) {
        cerr << "fail to getnameinfo" << endl;
    }
    if (!setUnblock(clientFd)) {
        cerr << "fail to set unblock to client fd" << clientFd << endl;
        return false;
    }
    e.data.fd = clientFd;
    e.events = EPOLLIN | EPOLLET;
    return addEpoll(clientFd, e);
}
 
bool EpollServer::_receive(const epoll_event &e)
{
    int clientFd = e.data.fd;
    uint32_t nbytes = recv(clientFd, m_readBuf, sizeof(m_readBuf), 0);
    cout << "receive " << nbytes << " bytes data from client " << clientFd << endl;
    if (nbytes > 0) {   /** we got some data from a client*/
        string data(m_readBuf, nbytes);
        _send(1, data);
        _send(clientFd, data);
    } else {
        cout << "socket " << clientFd << " has sth wrong since nbytes == " << nbytes  << endl;
        removeClient(clientFd);
    }
    return true;
}
 
bool EpollServer::_send(int clientFd, const std::string &data)
{
    if (write(clientFd, data.c_str(), data.size()) == -1) {
        cerr << "fail to send data to " << clientFd << endl;
        return false;
    }
    return true;
}
 
bool EpollServer::removeClient(int clientFd)
{
    cout << "remove client " << clientFd << endl;
    close (clientFd);
    return true;
}
6.4 main.cpp
#include "stdafx.h"
#include "epollserver.h"
 
using namespace std;
 
int main(int argc, char* argv[])
{
    if (argc >= 2) {
        EpollServer server;
        if (server._listen(argv[1])) {
            while (server.pulse()) {
                usleep(1000);
            }
        }
    } else {
        cout << "Usage: [port]" << endl;
    }
    return 0;
}
6.5 qmake工程檔案epoll.pro
######################################################################
# Automatically generated by qmake (2.01a) Fri Nov 16 18:01:16 2012
######################################################################
 
TEMPLATE = app
TARGET = 
DEPENDPATH += .
INCLUDEPATH += .
CONFIG -= qt
PRECOMPILED_HEADER += stdafx.h
 
# Input
SOURCES += main.cpp epollserver.cpp
HEADERS += epollserver.h
編譯方法:

qmake epoll.pro
make

--------------------- 
作者:sunyurun 
來源:CSDN 
原文:https://blog.csdn.net/sunyurun/article/details/8194979 
版權宣告:本文為博主原創文章,轉載請附上博文連結!