深入linux網路編程(三):非同步阻塞IO —— epoll__區塊鏈

來源:互聯網
上載者:User

作者:yurunsun@gmail.com 新浪微博@孫雨潤 新浪部落格 CSDN部落格日期:2012年11月17日 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 yurunsun@gmail.com */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 64EpollServer::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 = appTARGET = DEPENDPATH += .INCLUDEPATH += .CONFIG -= qtPRECOMPILED_HEADER += stdafx.h# InputSOURCES += main.cpp epollserver.cppHEADERS += epollserver.h

編譯方法:

qmake epoll.promake
如果這篇文章對您有協助,請到CSDN部落格留言; 轉載請註明:來自雨潤的技術部落格 http://blog.csdn.net/sunyurun

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.