引言
"在計(jì)算機(jī)網(wǎng)絡(luò)編程中,多路IO技術(shù)是非常常見的一種技術(shù)。其中,Poll函數(shù)和Epoll函數(shù)是最為常用的兩種多路IO技術(shù)。這兩種技術(shù)可以幫助服務(wù)器端處理多個(gè)客戶端的并發(fā)請(qǐng)求,提高了服務(wù)器的性能。本文將介紹Poll和Epoll函數(shù)的使用方法,并探討了在服務(wù)器開發(fā)中使用這兩種技術(shù)的流程和注意事項(xiàng)。"
?
poll函數(shù)介紹
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(man poll 調(diào)用)
函數(shù)說明: 跟select類似, 委托內(nèi)核監(jiān)控可讀, 可寫, 異常事件
函數(shù)參數(shù):
fds: 一個(gè)struct pollfd結(jié)構(gòu)體數(shù)組的首地址
????
struct pollfd {
? ? ??
int fd;//要監(jiān)控的文件描述符,如果fd為-1, 表示內(nèi)核不再監(jiān)控
?????
short events; //輸入?yún)?shù), 表示告訴內(nèi)核要監(jiān)控的事件, 讀事件, 寫事件, 異常事件?
?????
short revents;//輸出參數(shù), 表示內(nèi)核告訴應(yīng)用程序有哪些文件描述符有事件發(fā)生???
????
};
events/revents:
?
POLLIN:可讀事件,讓內(nèi)核監(jiān)控讀事件就要寫這個(gè)
?
POLLOUT: 可寫事件,緩沖區(qū)未滿就可寫
?
POLLERR: 異常事件
nfds: 告訴內(nèi)核監(jiān)控的范圍, 具體是: 數(shù)組下標(biāo)的最大值+1
timeout:
=0: 不阻塞, 立刻返回
-1: 表示一直阻塞, 直到有事件發(fā)生
>0: 表示阻塞時(shí)長(zhǎng), 在時(shí)長(zhǎng)范圍內(nèi)若有事件發(fā)生會(huì)立刻返回;
??
如果超過了時(shí)長(zhǎng)也會(huì)立刻返回
函數(shù)返回值:
>0: 發(fā)生變化的文件描述符的個(gè)數(shù)
=0: 沒有文件描述符發(fā)生變化
-1: 表示異常
poll函數(shù)開發(fā)流程
1 創(chuàng)建socket ,得到監(jiān)聽文件描述符,lfd ----- socket();
2 設(shè)置端口復(fù)用----------setsockopt()
3 綁定 ------ bind()
4
struct pollfd client[1024];?
client[0].fd = lfd;??????// 放在哪都行,放在最倆頭方便使用
client[0].events = POLLIN;??//監(jiān)控讀事件,如果也讓其監(jiān)控可寫事件,用或
// 設(shè)置為fd 為-1 ,表示內(nèi)核不在監(jiān)控,這是一個(gè)初始化
int maxi = 0;???//??定義最大數(shù)組下標(biāo)
for(int i = 0;i < 1024;i??++)
{
????????client[i].fd = -1;
}
//委托內(nèi)核持續(xù)監(jiān)控
k= 0;
while(1)
{
??????nready = poll(client,maxi + 1,-1);
?????//異常情況
?????if(nready < 0 )
?????{
????????????if(error == EINTR)
????????????{
???????????????????continue;
????????????}
????????????break;
?????}
?????if(client[0].revents = POLLIN)
????{
??????????//接受新的客戶端連接
?????????k ++;
??????????cfd??= Accept(lfd,NULL,NULL);
??????????/*繼續(xù)委托內(nèi)核監(jiān)聽事件
?????????尋找在client 數(shù)組中可用位置*/
??????????for(i??= 0;i < 1024;i ++ )
?????????{
?????????????????if(client[i ].fd ==-1 )
????????????????{
????????????????????????client.fd[i] =??cfd;
????????????????????????client.fd[i] = POLLIN;
?????????????????????????break;
????????????????}
??????????}
?????????//客戶端連接數(shù)達(dá)到最大值
??????????if(i == 1024)
??????????{
?????????????????close(cfd);
??????????????????continue;???//退出,可能會(huì)有客戶端連接退出,方便繼續(xù)尋找
???????????}
??????????//修改client 數(shù)組下標(biāo)最大值?
???????????if(maxi < i )
????????????????maxi = i;
???????????if(--nready == 0 )
???????????????continue;
????}
????//下面是有客戶端發(fā)送數(shù)據(jù)的情況
?????for(i = 1;i <=??maxi;i ++)
????{
?????????//如果client數(shù)組中fd 為-1,表示已經(jīng)不再讓內(nèi)核監(jiān)控了
??????????if(client[i].fd == -1)
???????????????continue;
??????????if(client[i].revents == POLLIN)
??????????{
? ? ? ? ? ? ? ? ?sockfd =? client[i].fd;
? ? ? ? ? ? ? ? ?memset(buf,0x00,sizeof(buf));
??????????????????//read 數(shù)據(jù)
??????????????????n??=? Read(sockfd, buf,sizeof(buf));
??????????????????if(n <= 0)
??????????????????{
? ? ? ? ? ? ? ? ? ? ? ? ?printf("read error or client closed,n =[%d]\n",n);
??????????????????????????close(sockfd);
??????????????????????????client[i].fd = -1;????//告訴內(nèi)核不再監(jiān)控
?????????????
??????????????????}
??????????????????else?
??????????????????{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?printf("read error,n == [%d],buf==[%s]\n,"n,buf);
????????????????????????????//發(fā)送數(shù)據(jù)給客戶端
????????????????????????????Write(sockfd,buf,n);
???????????????????}
? ? ? ? ? ? ? ? ? ? if(--nready == 0 )
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ? ? ? ?}
???????????}
?????}
?????close(lfd);
}
多路IO-epoll? ???(重點(diǎn))
將檢測(cè)文件描述符的變化委托給內(nèi)核去處理, 然后內(nèi)核將發(fā)生變化的文件描述符對(duì)應(yīng)的
事件返回給應(yīng)用程序.
頭文件
#include <sys/epoll.h>
函數(shù)
int epoll_create(int size)?
函數(shù)說明:創(chuàng)建一棵poll樹,返回一個(gè)數(shù)根節(jié)點(diǎn)
函數(shù)參數(shù):size:必須傳一個(gè)大于0的數(shù)
返回值:返回個(gè)文件描述符,這個(gè)文件描述符就表示epoll樹的樹根節(jié)點(diǎn)
int? epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
函數(shù)說明:將fd上的epoll樹,從樹上刪除和修改
函數(shù)參數(shù):?
? ? ? ? ? ? ? ?epfd:epoll樹的樹根節(jié)點(diǎn)
op:
? ? ? ? ? ? ? ?EPOLL_CTL_ADD: 添加事件節(jié)點(diǎn)到樹 上
? ? ? ? ? ? ? ?EPOLL_CTL_DEL: 從樹上刪除事件節(jié)點(diǎn)
? ? ? ? ? ? ? ?EPOLL_CTL_MOD: 修改樹上對(duì)應(yīng)的事件節(jié)點(diǎn)fd:要操做的文件描述符
event :
? ? ? ??event.events 常用的有:
? ? ? ? ? ? ? EPOLLIN: 讀事件
? ? ? ? ? ? ? EPOLLOUT: 寫事件? ?
? ? ? ? ? ? ? EPOLLERR: 錯(cuò)誤事件
? ? ? ? ? ? ??EPOLLET: 邊緣觸發(fā)模式
event.fd: 要監(jiān)控的事件對(duì)應(yīng)的文件描述符typedef union epoll_data{
? ? ? ? ?void? *ptr;
? ? ? ? ? int? ? ?fd;
? ? ? ? ? uint32_t? u32;
? ? ? ? ? uint64_t? u64;
?}epoll_data_t;
struct epoll_event{
? ? ? ?uint32? events;? ? / *? Epoll events */
? ? ? ? epoll_data data;? ? ? /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
函數(shù)說明:等待內(nèi)核返回事件發(fā)生
參數(shù)說明:
? ? ? epfd: epoll樹根
? ? ? events: 傳出參數(shù), 其實(shí)是一個(gè)事件結(jié)構(gòu)體數(shù)組
? ? ? maxevents: 數(shù)組大小
timeout:
? ? ? -1: 表示永久阻塞
? ? ? 0: 立即返回
? ? ? >0: 表示超時(shí)等待事件
返回值:
成功: 返回發(fā)生事件的個(gè)數(shù)
失敗: 若timeout=0, 沒有事件發(fā)生則返回; 返回-1, 設(shè)置errno值,
使用epoll 模型開發(fā)服務(wù)器流程
? ? ? ?1:創(chuàng)建socket,得到監(jiān)聽文件描述符lfd ---- socket()
? ? ? ?2:? 設(shè)置端口復(fù)用 -----? setsockopt()
? ? ? ?3:? 綁定 ------ bind()
? ? ? ?4:? 監(jiān)聽 -------- listen()?
? ? ? ?5.? 創(chuàng)建一棵epoll樹? ? ??
開發(fā)完整的代碼
//EPOLL 模型測(cè)試
#include "wrap.h"
#include <sys/epoll.h>
#include <ctype.h>
int main()
{
int ret;
int n;
int nready;
int lfd;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
int k;
int i;
//創(chuàng)建socket
lfd = Socket(AF_INET,SOCK_STREAM,0);
//設(shè)置文件描述符為端口復(fù)用
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//綁定
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(8888);
Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));
//Listen
Listen(lfd,128);
//創(chuàng)建一棵epoll樹
int epfd = epoll_create(1024);
if(epfd < 0 )
{
perror("create epoll error");
return -1;
}
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev); //lfd 對(duì)應(yīng)的事件節(jié)點(diǎn)上樹
while(1)
{
nready = epoll_wait(epfd,events,1024,-1); //等待內(nèi)核返回事件
if(nready < 0)
{
perror("epoll_wait error");
if(nready == EINTR) //判斷是否收到了中斷信號(hào)
{
continue;
}
break;
}
for(i = 0;i < nready;i ++) //小于發(fā)生事件的個(gè)數(shù)
{
//有客戶端連接發(fā)來請(qǐng)求
sockfd = events[i].data.fd;
if(sockfd == lfd)
{
cfd = Accept(lfd,NULL,NULL);
ev.data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
}
//有客戶端發(fā)送數(shù)據(jù)過來
else {
memset(buf,0x00,sizeof(buf));
n = Read(sockfd,buf,sizeof(buf));
if(n <= 0)
{
close(sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd從epfd樹上刪除
}
else
{
for(k = 0;k < n;k ++)
{
buf[k] = toupper(buf[k]); //返回大寫
}
Write(sockfd,buf,n);
}
}
}
}
close(epfd);
close(lfd);
return 0;
}
epoll 的兩種模式 ET 和 LT 模式 ?
epoll 的LT模式:
? ? ?epoll 默認(rèn)情況是LT模式,在這種情況下,如果讀數(shù)據(jù)一次性沒有讀完,
? ? ?緩沖區(qū)還有可讀數(shù)據(jù),則epoll_wait還會(huì)再次通知。
epoll 的ET模式:
? ? 如果將epoll設(shè)置為ET模式,若讀數(shù)據(jù)的時(shí)候一次性沒有讀完,則epoll_wait不再通知
? ? 直到下次有新的數(shù)據(jù)
用ET模式下,為了防止第二個(gè)客戶端可以正常連接,并且發(fā)送數(shù)據(jù),需要將socket設(shè)置為非阻塞模式
ET設(shè)置了非阻塞模式是因?yàn)槭褂昧诉吘売|發(fā)模式(EPOLLET)。在邊緣觸發(fā)模式下,當(dāng)有數(shù)據(jù)可讀時(shí),只會(huì)觸發(fā)一次EPOLLIN事件,如果該次讀取沒有將緩沖區(qū)中的數(shù)據(jù)全部讀取完畢,下次還是會(huì)觸發(fā)EPOLLIN事件。因此,為了保證每次讀取完整的數(shù)據(jù),需要將socket設(shè)置為非阻塞模式,避免在緩沖區(qū)沒有全部讀取完畢時(shí)進(jìn)行阻塞。
代碼:文章來源:http://www.zghlxwxcb.cn/news/detail-736023.html
//EPOLL 模型測(cè)試 ET
#include "wrap.h"
#include <sys/epoll.h>
#include <ctype.h>
#include <fcntl.h>
int main()
{
int ret;
int n;
int nready;
int lfd;
int cfd;
int sockfd;
char buf[1024];
socklen_t socklen;
struct sockaddr_in svraddr;
struct epoll_event ev;
struct epoll_event events[1024];
int k;
int i;
//創(chuàng)建socket
lfd = Socket(AF_INET,SOCK_STREAM,0);
//設(shè)置文件描述符為端口復(fù)用
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));
//綁定
svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(8888);
Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));
//Listen
Listen(lfd,128);
//創(chuàng)建一棵epoll樹
int epfd = epoll_create(1024);
if(epfd < 0 )
{
perror("create epoll error");
return -1;
}
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev); //lfd 對(duì)應(yīng)的事件節(jié)點(diǎn)上樹
while(1)
{
nready = epoll_wait(epfd,events,1024,-1); //等待內(nèi)核返回事件
if(nready < 0)
{
perror("epoll_wait error");
if(nready == EINTR) //判斷是否收到了中斷信號(hào)
{
continue;
}
break;
}
for(i = 0;i < nready;i ++) //小于發(fā)生事件的個(gè)數(shù)
{
//有客戶端連接發(fā)來請(qǐng)求
sockfd = events[i].data.fd;
if(sockfd == lfd)
{
cfd = Accept(lfd,NULL,NULL);
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET; //
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
//將cfd設(shè)置為非阻塞模式
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK; //O_NONBLOCK(非阻塞)標(biāo)志位置為1。
fcntl(cfd, F_SETFL, flag);
}
//有客戶端發(fā)送數(shù)據(jù)過來
else {
memset(buf,0x00,sizeof(buf));
while(1)
{
n = Read(sockfd,buf,sizeof(buf));
printf("n == [%d]\n",n);
if(n == -1)
{
printf("read over,n == [%d]\n",n);
break;
}
if(n < 0 || (n <0 && n!=-1)) //對(duì)方關(guān)閉連接,或者異常的情況
{
printf("n == [%d],buf == [%s]\n",n,buf);
close(sockfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd從epfd樹上刪除
break;
}
else
{
printf("n == [%d],buf == [%s]\n",n,buf);
for(k = 0;k < n;k ++)
{
buf[k] = toupper(buf[k]); //返回大寫
}
Write(sockfd,buf,n);
}
}
}
}
}
close(epfd);
close(lfd);
return 0;
}
圖解epoll反應(yīng)堆流程
文章來源地址http://www.zghlxwxcb.cn/news/detail-736023.html
到了這里,關(guān)于多路IO—POll函數(shù),epoll服務(wù)器開發(fā)流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!