Linux企業級開發技術(4)——epoll企業級開發之epoll例程
為了使大家更加深入瞭解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);
}
相關文章
- Linux企業級開發技術(2)——epoll企業級開發之epoll介面Linux
- Linux企業級開發技術(3)——epoll企業級開發之epoll模型Linux模型
- Linux企業級開發技術(1)——epoll企業級開發之簡介Linux
- Linux企業級開發技術(6)——libevent企業級開發之記憶體管理Linux記憶體
- Linux企業級開發技術(7)——libevent企業級開發之鎖和執行緒Linux執行緒
- Linux企業級專案實踐之網路爬蟲(19)——epoll介面Linux爬蟲
- 企業級開發框架----------Django框架Django
- 企業級 Web 開發的挑戰Web
- 23. 企業級開發基礎4:物件導向物件
- 關於PHP在企業級開發領域的訪談——企業級開發,PHP準備好了嗎?PHP
- 我學Ajax企業級開發 之 Ajax構建塊
- 32.企業級開發進階4:正規表示式
- 微服務PaaS框架,RestCloud企業級開發框架微服務框架RESTCloud
- 如何快速開發Java RCP企業級應用?Java
- 低程式碼開發平臺,快速開發企業級系統
- 企業級軟體開發新模式:低程式碼模式
- 區塊鏈技術開發主鏈 區塊鏈的企業級應用剖析區塊鏈
- 30.4. 企業級開發進階2.4:服務端快捷開發服務端
- 安全可控的企業級低程式碼開發平臺
- 快速構建企業級應用的開發平臺
- 20. 企業級開發基礎1:自定義模組
- 22. 企業級開發基礎3:類和物件物件
- 百億級企業級 RPC 框架開源了!RPC框架
- 區塊鏈技術應用物流實體落地開發,企業級區塊鏈服務區塊鏈
- IOS企業版開發iOS
- 企業級軟體系統開發為何這麼貴?
- 33.企業級開發進階6:資料庫操作資料庫
- 28. 企業級開發基礎9:異常處理
- 30.2. 企業級開發進階2.2:TCP實戰TCP
- 關於企業級應用和web開發的區別Web
- 用Servlet開發企業級三層Web應用(一) (轉)ServletWeb
- KubeSphere 宣佈開源 Thanos 的企業級發行版 Whizard
- 《企業級ios應用開發實戰》一第1章企業應用的話題iOS
- 大咖助陣全球企業級OpenSCA技術開源釋出會
- 從開發平臺到智慧供應鏈,AI技術如何推動企業智慧化升級?AI
- ASP.NET Core 6 從入門到企業級實戰開發應用技術彙總ASP.NET
- 企業級JAVAJava
- Java EE 6之企業應用開發Java