epoll編程

包含頭文件:html

#include <sys/epoll.h>編程

 

epoll的接口很是簡單,一共就三個函數:
1. int epoll_create(int size);
建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。數組

2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);併發

epoll的事件註冊函數,它不一樣與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型.異步

第一個參數是epoll_create()的返回值,socket

第二個參數表示動做,用三個宏來表示:
EPOLL_CTL_ADD;註冊新的fd到epfd中;
EPOLL_CTL_MOD;修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL;從epfd中刪除一個fd;
第三個參數是須要監聽的fd;函數

第四個參數是告訴內核須要監聽什麼事,struct epoll_event結構以下;高併發

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};oop

events能夠是如下幾個宏的集合;
EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符能夠寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏ui

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,相似於select()調用。

第一個參數:epfd,epoll_create返回的句柄

第二個參數:events用來從內核獲得事件的集合

第三個參數:maxevents告以內核這個events有多大,這個 maxevents的值不能大於建立epoll_create()時的size,

第四個參數timeout是超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。

該函數返回須要處理的事件數目,如返回0表示已超時。

4.close(epollfd)

退出時記得釋放建立的epoll句柄;

 

五、關於ET、LT兩種工做模式:
能夠得出這樣的結論:
ET模式僅當狀態發生變化的時候纔得到通知,這裏所謂的狀態的變化並不包括緩衝區中還有未處理的數據,也就是說,若是要採用ET模式,須要一直read/write直到出錯爲止,不少人反映爲何採用ET模式只接收了一部分數據就再也得不到通知了,大多由於這樣;

而LT模式是隻要有數據沒有處理就會一直通知下去的.

兩者的差別在於:

level-trigger(LT)模式下只要某個socket處於readable/writable狀態,不管何時進行epoll_wait都會返回該socket;

edge-trigger(ET)模式下只有某個socket從unreadable變爲readable或從unwritable變爲writable時,epoll_wait纔會返回該socket。

因此,在epoll的ET模式下,正確的讀寫方式爲:
讀:只要可讀,就一直讀,直到返回0,或者 errno = EAGAIN
寫:只要可寫,就一直寫,直到數據發送完,或者 errno = EAGAIN

 

PS:

epoll工做在ET模式的時候,必須使用非阻塞套接口

//設置socket鏈接爲非阻塞模式
void setnonblocking(int sockfd) 
{
    int opts;
  // 獲得文件狀態標誌  
    opts = fcntl(sockfd, F_GETFL, 0);
    if(opts < 0)
    {
        perror("fcntl(F_GETFL)\n");
        exit(1);
    }
  // 設置文件狀態標誌 opts
= (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL)\n"); exit(1); } }

 

6.

參考網址:

epoll詳解

http://blog.chinaunix.net/uid-24517549-id-4051156.html

高併發的epoll+線程池,epoll在線程池內

http://blog.chinaunix.net/uid-311680-id-2439723.html

基於epoll實現socket編程完整實例

http://blog.chinaunix.net/uid-20771605-id-4596400.html

epoll使用詳解(精髓)

http://blog.csdn.net/ljx0305/article/details/4065058

 

1)

#define ERR_EXIT(m) do \
{ \
  perror(m); \
  exit(EXIT_FAILURE); \
} while(0)

 

//聲明epoll_event結構體的變量,event用於註冊事件,數組用於回傳要處理的事件
struct epoll_event event;
struct epoll_event events[20];
// 生成用於處理accept的epoll專用的文件描述符
int epfd = epoll_create(256);

// 註冊監聽的事件

memset(&event,0,sizeof(event));

event.data.fd = listenFd;
event.events = EPOLLIN | EPOLLET | EPOLLOUT;

// 註冊epoll事件
rlt = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);

if(rlt == -1)
{
  ERR_EXIT("註冊事件失敗!\n");
}


struct timeval outtime;
memset(&outtime, 0x00, sizeof(struct timeval));

const int MAX_EVENTS = 1024; // 最大事件數

void *epoll_loop(void* para)
{
    int nfds; // 臨時變量,存放返回值
    int epfd; // 監聽用的epoll句柄,epoll_create返回值
    //struct epoll_event events[MAX_EVENTS]; // 監聽事件數組
    //struct epoll_event event;
    // struct sockaddr_in client_addr;
    int i;
    int connfd; // 
    for(;;) 
    {
        // 等待epoll事件的發生
        nfds = epoll_wait(epfd,events,MAX_EVENTS,-1); // -1: timeout
        //printf("nfds = %d\n", nfds);

        // 處理所發生的全部事件
        if(nfds > 0)
        {
            for(i=0; i<nfds; ++i)
            {
                if(events[i].data.fd == listenfd) // 若是新監測到一個SOCKET用戶鏈接到了綁定的SOCKET端口,創建新的鏈接。
                {
                    //while(1)
                    //{
                        // socklen_t cliaddrlen;                         
                        connfd = accept(listenfd,(struct sockaddr *)&client_addr, &cliaddrlen);
                        
                        if(connfd > 0)
                        {
                            //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << ",connect:" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << endl;
                            event.data.fd=connfd;
                            event.events = EPOLLIN | EPOLLET; // ET模式
                            epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event); // //註冊ev.將新的fd添加到epoll的監聽隊列中

                            //fd_Setnonblocking(event.data.fd);
                            //event.events=EPOLLIN|EPOLLET;
                            //epoll_ctl(epfd,EPOLL_CTL_ADD,event.data.fd,&event);
                        }
                        else
                        {
                            //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << endl;
                            if (errno == EAGAIN) // 沒有鏈接須要接收了
                            {
                                break;
                            }
                            else if(errno == EINTR) // 可能被中斷信號打斷,,通過驗證對非阻塞socket並未收到此錯誤,應該能夠省掉該步判斷
                            {
                                ;
                            }
                            else // 其它狀況能夠認爲該描述字出現錯誤,應該關閉後從新監聽
                            {
                                //...
                            }
                        }
                    //}
                }
                else if(events[i].events & EPOLLIN ) // 接收到數據,讀socket
                {
                    n = read(sockfd, line, MAXLINE)) < 0; //
                    
                    ev.data.ptr = md;     //md爲自定義類型,添加數據
                    ev.events=EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓

                    // 或者以下處理:
                    char recvBuf[1024] = {0}; 
                    int ret = 999;
                    int rs = 1;

                    while(rs)
                    {
                        ret = recv(events[i].data.fd, recvBuf, 1024, 0);// 接受客戶端消息
                        if(ret < 0)
                        {
                            // 因爲是非阻塞的模式,因此當errno爲EAGAIN時,表示當前緩衝區已無數據可讀,在這裏就看成是該次事件已處理過。
                            if(errno == EAGAIN)
                            {
                                printf("EAGAIN\n");
                                break;
                            }
                            else
                            {
                                printf("recv error!\n");
                                epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); // 其中 struct epoll_event event;
                                                                                           // memset(&event,0,sizeof(event));
                                                                                           // event.data.fd=listenfd;
                                                                                           // event.events=EPOLLIN|EPOLLET;
                                close(events[i].data.fd);
                                break;
                            }
                        }
                        else if(ret == 0)
                        {
                            // 這裏表示對端的socket已正常關閉. 
                            rs = 0;
                        }
                        if(ret == sizeof(recvBuf))
                        {
                            rs = 1; // 須要再次讀取
                        }
                        else
                        {
                            rs = 0;
                        }
                    }

                }
                else if(events[i].events & EPOLLOUT) // 有數據待發送,寫socket
                {

            struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取數據
            sockfd = md->fd;
            send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //發送數據
            event.data.fd=sockfd;
            event.events=EPOLLIN|EPOLLET;
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據

//sprintf(buf,"HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n%s","Hello world!\n");
                    send(events[i].data.fd,buf,strlen(buf),0);

                    close(events[i].data.fd);
                }
                else
                {
                    close(events[i].data.fd);
                }
                //else if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) // 有異常發生
                //{
                //    //此時說明該描述字已經出錯了,須要從新建立和監聽
                //    //close(listenfd);   

                //    // epoll_ctl(epfd, EPOLL_CTL_DEL, listenfd, &event);
                //    // 建立監聽socket
                //    //listenfd = socket(AF_INET, SOCK_STREAM, 0);
                //    //...
                //}


                //epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
                //close(events[i].data.fd);

            }
        }    
    }
}
相關文章
相關標籤/搜索