Linux企業級開發技術(4)——epoll企業級開發之epoll例程

尹成發表於2014-09-23

 

為了使大家更加深入瞭解epoll模型在企業應用中的使用,下面給出一段基於epoll的伺服器程式碼,並在程式碼中新增了詳細註釋:

 

 

#include <deque>
#include <map>
#include <vector>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <sys/time.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
 
#include <string>
#include <cstdio>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
 
#include <cstdlib>
#include <cctype>
#include <sstream>
#include <utility>
#include <stdexcept>
 
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <signal.h>
 
using namespace std;
 
#define MAXLINE 5
#define LISTENQ 5
#define SERV_PORT 5000
 
bool bWrite = false;
 
void setnonblocking(int sock)
{    
         intopts;    
         opts=fcntl(sock,F_GETFL);    
         if(opts<0)    
         {        
                   perror("fcntl(sock,GETFL)");        
                   exit(1);    
         }    
         opts= opts|O_NONBLOCK;    
         if(fcntl(sock,F_SETFL,opts)<0)    
         {        
                   perror("fcntl(sock,SETFL,opts)");        
                   exit(1);    
         } 
}
 
static void sig_pro(int signum)
{
         cout<< "recv signal:" << signum << endl;
}
 
int main(int argc, char* argv[])
{
         inti, n, listenfd, connfd, nfds;
         charline[MAXLINE + 1];    
         socklen_tclilen;                                        //宣告epoll_event結構體的變數,ev用於註冊事件,陣列用於回傳要處理的事件    
         structepoll_event ev,events[20];     //生成用於處理accept的epoll專用的檔案描述符    
         intepfd=epoll_create(256);    
         structsockaddr_in clientaddr;    
         structsockaddr_in serveraddr;    
        
         //為讓應用程式不必對慢速系統呼叫的errno做EINTR檢查,可以採取兩種方式:1.遮蔽中斷訊號,2.處理中斷訊號
         //1.由signal()函式安裝的訊號處理程式,系統預設會自動重啟動被中斷的系統呼叫,而不是讓它出錯返回,
         //  所以應用程式不必對慢速系統呼叫的errno做EINTR檢查,這就是自動重啟動機制.
         //2.對sigaction()的預設動作是不自動重啟動被中斷的系統呼叫,
         //  因此如果我們在使用sigaction()時需要自動重啟動被中斷的系統呼叫,就需要使用sigaction的SA_RESTART選項
 
         //忽略訊號     
         //sigset_tnewmask;
         //sigemptyset(&newmask);
         //sigaddset(&newmask,SIGINT);
         //sigaddset(&newmask,SIGUSR1);
         //sigaddset(&newmask,SIGUSR2);
         //sigaddset(&newmask,SIGQUIT);
         //pthread_sigmask(SIG_BLOCK,&newmask, NULL);
        
         //處理訊號
         //預設自動重啟動被中斷的系統呼叫,而不是讓它出錯返回,應用程式不必對慢速系統呼叫的errno做EINTR檢查
         //signal(SIGINT,sig_pro);
         //signal(SIGUSR1,sig_pro);
         //signal(SIGUSR2,sig_pro);
         //signal(SIGQUIT,sig_pro);
 
         structsigaction sa;
   sa.sa_flags = SA_RESTART;      //SA_RESART:自動重啟動被中斷的系統呼叫,0:預設不自動重啟動被中斷的系統呼叫
   sa.sa_handler = sig_pro;
   sigaction(SIGINT, &sa, NULL);
   sigaction(SIGUSR1, &sa, NULL);
   sigaction(SIGUSR2, &sa, NULL);
   sigaction(SIGQUIT, &sa, NULL);
   
   /*//系統呼叫被中斷訊號中斷的測試驗證
         charbuf[1024];
   int nn;
 
         while(1){
       if((nn = read(STDIN_FILENO, buf, 1024)) == -1) {
           if(errno == EINTR)
                printf("read isinterrupted\n");
       }
       else {
           write(STDOUT_FILENO, buf, nn);
     }
    }
   
   return 0;*/
   
         listenfd= socket(AF_INET, SOCK_STREAM, 0);    
         //把socket設定為非阻塞方式    
         setnonblocking(listenfd);    
         //設定與要處理的事件相關的檔案描述符    
         ev.data.fd=listenfd;    
         //設定要處理的事件型別    
         ev.events=EPOLLIN|EPOLLET;       
         //註冊epoll事件    
         epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);    
         bzero(&serveraddr,sizeof(serveraddr));    
         serveraddr.sin_family= AF_INET;    
         serveraddr.sin_addr.s_addr= htonl(INADDR_ANY);  
         serveraddr.sin_port=htons(SERV_PORT);    
         bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));    
         listen(listenfd,LISTENQ);
 
         for( ; ; )
         {
                   cout<< "active" << endl;
                  
                   //等待epoll事件的發生        
                   nfds=epoll_wait(epfd,events,20,500);        
                   //處理所發生的所有事件            
                   for(i = 0; i < nfds; ++i)        
                   {
                            if(events[i].data.fd < 0)
                            {
                                     continue;
                            }
     
                            if(events[i].data.fd == listenfd)                 //監聽上的事件   
                            {
                                     cout<< "[conn] events=" << events[i].events << endl;
                                    
                                     if(events[i].events&EPOLLIN)          //有連線到來
                                     {
                                               do
                                               {
                                                        clilen= sizeof(struct sockaddr);              
                                                        connfd= accept(listenfd,(sockaddr *)&clientaddr, &clilen);                
                                                        if(connfd > 0)
                                                        {
                                                                 cout<< "[conn] peer=" << inet_ntoa(clientaddr.sin_addr)<< ":" << ntohs(clientaddr.sin_port) << endl;
                                                                
                                                                 //把socket設定為非阻塞方式
                                                                 setnonblocking(connfd);                           
                                                                 //設定用於讀操作的檔案描述符               
                                                                 ev.data.fd=connfd;                
                                                                 //設定用於注測的讀操作事件                
                                                                 ev.events=EPOLLIN|EPOLLET;              
                                                                 //註冊ev                
                                                                 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
                                                        }
                                                        else
                                                        {
                                                                 cout<< "[conn] errno=" << errno << endl;
                                                                
                                                                 if(errno == EAGAIN)         //沒有連線需要接收了
                                                                 {
                                                                           break;
                                                                 }
                                                                 elseif (errno == EINTR)   //可能被中斷訊號打斷,,經過驗證對非阻塞socket並未收到此錯誤,應該可以省掉該步判斷
                                                                 {
                                                                           ;
                                                                 }
                                                                 else  //其它情況可以認為該描述字出現錯誤,應該關閉後重新監聽
                                                 {
                                                          cout<< "[conn] close listen because accept fail and errno not equaleagain or eintr" << endl;
                                                         
                                                          //此時說明該描述字已經出錯了,需要重新建立和監聽
                                                          close(events[i].data.fd);                    
                                                                           epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
                                                                          
                                                                           //重新監聽
                                                                           listenfd= socket(AF_INET, SOCK_STREAM, 0);       
                                                                           setnonblocking(listenfd);        
                                                                           ev.data.fd=listenfd;        
                                                                           ev.events=EPOLLIN|EPOLLET;           
                                                                           epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);      
                                                                           bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));    
                                                                           listen(listenfd,LISTENQ);
                                                                           break;
                                                 }
                                                        }
                                               }while (1);
                                     }
                                     elseif (events[i].events&EPOLLERR || events[i].events&EPOLLHUP)   //有異常發生
                                     {
                                               cout<< "[conn] close listen because epollerr or epollhup" <<errno << endl;
                                              
                                               close(events[i].data.fd);                     
                                               epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
 
                                               //重新監聽
                                               listenfd= socket(AF_INET, SOCK_STREAM, 0);       
                                               setnonblocking(listenfd);        
                                               ev.data.fd=listenfd;        
                                               ev.events=EPOLLIN|EPOLLET;           
                                               epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);       
                                               bind(listenfd,(sockaddr*)&serveraddr, sizeof(serveraddr));    
                                               listen(listenfd,LISTENQ);
                                     }
                            }
                            else  //連線上的事件
                            {
                                     cout<< "[data] events=" << events[i].events << endl;
                                    
                                     if(events[i].events&EPOLLIN)   //有資料可讀         
                                     {                
                                               do
                                               {
                                                        n= read(events[i].data.fd, line, MAXLINE);
                                                        if(n > 0)    //讀到資料
                                                        {
                                                                 line[n]= '\0';
 
                                                                 //綜合下面兩種情況,在讀到位元組數大於0時必須繼續讀,不管讀到位元組數是否等於接收緩衝區大小,
                                                                 //也不管錯誤程式碼是否為EAGAIN,否則要麼導致關閉事件丟失,要麼導致後續資料的丟失
                                                                 if(n < MAXLINE)
                                                                 {
                                                                           //經過驗證,如果對方傳送完資料後就斷開,即使判斷是否錯誤程式碼為EAGAIN,也會導致close事件丟失,
                                                                           //必須繼續讀,以保證斷開事件的正常接收
                                                                           cout<< "[data] n > 0, read less recv buffer size, errno="<< errno << ",len=" << n << ",data=" << line << endl;
                                                                 }
                                                                 else
                                                                 {
                                                                           //經過驗證,傳送位元組數大於等於接收緩衝區時,讀到位元組數為接收緩衝區大小,錯誤程式碼為EAGAIN,
                                                                           //必須繼續讀,以保證正常接收後續資料
                                                                           cout<< "[data] n > 0, read equal recv buffer size, errno="<< errno << ",len=" << n << ",data=" << line << endl;
                                                                 }
                                                        }
                                                        elseif (n < 0) //讀取失敗
                                                        {
                                                       if (errno == EAGAIN)         //沒有資料了
                                                       {
                                                                cout<< "[data] n < 0, no data, errno=" << errno <<endl;
                                                               
                                                                break;
                                                       }
                                                       else if(errno == EINTR)              //可能被內部中斷訊號打斷,經過驗證對非阻塞socket並未收到此錯誤,應該可以省掉該步判斷
                                                       {
                                                                cout<< "[data] n < 0, interrupt, errno=" << errno <<endl;
                                                       }
                                                       else  //客戶端主動關閉
                                                       {
                                                                cout<< "[data] n < 0, peer close, errno=" << errno<< endl;
                                                               
                                                                close(events[i].data.fd);                    
                                                                           epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
                                                                           break;
                                                       }
                                                        }
                                                        elseif (n == 0) //客戶端主動關閉
                                                        {
                                                                 cout<< "[data] n = 0, peer close, errno=" << errno <<endl;
                                                                
                                                                 //同一連線可能會出現兩個客戶端主動關閉的事件,一個errno是EAGAIN(11),一個errno是EBADF(9),
                                                                 //對錯誤的檔案描述符EBADF(9)進行關閉操作不會有什麼影響,故可以忽略,以減少errno判斷的開銷
                                                                   
                                                                 close(events[i].data.fd);                    
                                                                 epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
                                                                 break;     
                                                        }
                                               }while (1);
                                     }
                                     elseif (events[i].events&EPOLLOUT)                //可以寫資料
                                     {
                                               cout<< "[data] epollout" << endl;
                                              
                                               if(events[i].data.u64 >> 32 == 0x01)       //假定0x01代表關閉連線
                                               {
                                                        //在需要主動斷開連線時僅註冊此事件不含可讀事件,用來處理服務端主動關閉
                                                        close(events[i].data.fd);                    
                                                        epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
                                               }
                                               else  //其它情況可以去設定該連線的可寫標誌
                                               {
                                                        bWrite= true;
                                               }
                                     }
                                     elseif (events[i].events&EPOLLERR || events[i].events&EPOLLHUP)   //有異常發生
                                     {
                                               cout<< "[data] close peer because epollerr or epollhup" <<endl;
                                              
                                               close(events[i].data.fd);                     
                                               epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,&events[i]);
                                     }
                            }
                   }   
         }
   return 0;
}
 
ssize_t mysend(int socket, const void*buffer, size_t length, int flags)
{
         ssize_ttmp;
         size_tleft = length;
         constchar *p = (const char *)buffer;
 
         while(left > 0)
         {
                   if(bWrite)                  //判斷該連線的可寫標誌
                   {
                            tmp= send(socket, p, left, flags);
                            if(tmp < 0)
                            {
                                     //當socket是非阻塞時,如返回此錯誤,表示寫緩衝佇列已滿,
                                     if(errno == EAGAIN)
                                     {
                                               //設定該連線的不可寫標誌
                                               bWrite= false;
                                              
                                               usleep(20000);
                                               continue;
                                     }
                                     elseif (errno == EINTR)
                                     {
                                               //被中斷訊號打斷的情況可以忽略,經過驗證對非阻塞socket並未收到此錯誤,應該可以省掉該步判斷
                                     }
                                     else
                                     {
                                               //其它情況下一般都是連線出現錯誤了,外部採取關閉措施
                                               break;
                                     }
                            }
                            elseif ((size_t)tmp == left)
                            {
                                     break;
                            }
                            else
                            {
                                     left-= tmp;
                                     p+= tmp;
                            }
                   }
                   else
                   {
                            usleep(20000);
                   }
         }
 
         return(ssize_t)(length - left);
}


 

 

 

相關文章