傳統(tǒng)藝能??
小編是雙非本科大二菜鳥不贅述,歡迎米娜桑來指點江山哦
1319365055
????非科班轉碼社區(qū)誠邀您入駐????
小伙伴們,滿懷希望,所向披靡,打碼一路向北
一個人的單打獨斗不如一群人的砥礪前行
這是和夢想合伙人組建的社區(qū),誠邀各位有志之士的加入?。?br> 社區(qū)用戶好文均加精(“標兵”文章字數(shù)2000+加精,“達人”文章字數(shù)1500+加精)
直達: 社區(qū)鏈接點我
poll??
poll
也是系統(tǒng)提供的一個多路轉接接口。poll 系統(tǒng)調(diào)用也可以讓程序同時監(jiān)視多個文件描述符上的事件是否就緒,和 select 定位是一樣的,適用場景也是一樣的。
p o l l 函數(shù)的函數(shù)原型如下: \color{red} {poll 函數(shù)的函數(shù)原型如下:} poll函數(shù)的函數(shù)原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
為一個 poll 函數(shù)監(jiān)視的結構列表,每一個元素包含三部分內(nèi)容:文件描述符、監(jiān)視的事件集合、就緒的事件集合。nfds
為 fds 數(shù)組長度。timeout
為poll函數(shù)的超時時間,單位是毫秒(ms)。
timeout 可取值:
-1:poll 調(diào)用后阻塞等待,直到被監(jiān)視的某個文件描述符上的某個事件就緒。
0:poll 調(diào)用后非阻塞等待,無論被監(jiān)視的文件描述符上的事件是否就緒,poll 檢測后都會立即返回。
特定的時間值:poll 調(diào)用后在指定的時間內(nèi)進行阻塞等待,如果被監(jiān)視的文件描述符上一直沒有事件就緒,則在該時間后 poll 進行超時返回。
函數(shù)調(diào)用成功則返回有事件就緒的文件描述符個數(shù)。如果 timeout 時間耗盡,則返回 0。如果函數(shù)調(diào)用失敗,則返回 -1,同時錯誤碼會被設置。
在 poll 調(diào)用失敗后會設置錯誤碼,具體分為四種錯誤碼:
EFAULT:fds數(shù)組不包含在調(diào)用程序的地址空間中。
EINTR:此調(diào)用被信號所中斷。
EINVAL:nfds值超過RLIMIT_NOFILE值。
ENOMEM:核心內(nèi)存不足。
struct pollfd??
struct pollfd 結構當中包含三個成員:
fd:特定的文件描述符,若設置為負值則忽略events字段并且revents字段返回0。
events:需要監(jiān)視該文件描述符上的哪些事件。
revents:poll函數(shù)返回時告知用戶該文件描述符上的哪些事件已經(jīng)就緒。
events 和 revents 的取值:
這些取值實際都是以宏的方式進行定義的,它們的二進制序列中有且僅有一個比特位是1,且為 1 的比特位是各不相同的
因此在調(diào)用poll函數(shù)之前,可以通過 | 運算符將要監(jiān)視的事件添加到 events 中。poll 返回后,可以通過 & 運算符檢測 revents 成員中是否包含特定事件,以得知對應文件描述符的特定事件是否就緒
poll 服務器??
poll 工作流程和 select 是基本類似的,這里我們也實現(xiàn)一個簡單poll服務器,該服務器也只是讀取客戶端發(fā)來的數(shù)據(jù)并進行打印。
PollServer類??
PollServer類當中只需要包含監(jiān)聽套接字和端口號兩個成員變量,在 poll 服務器綁定時直接將 IP 地址設置為INADDR_ANY
即可。
在構造 PollServer 對象時,需要指明 poll 服務器端口號,當然也可以在初始化 poll 服務器的時候指明。
在初始化 poll 服務器的時候調(diào)用 Socket 類中的函數(shù),依次進行套接字的創(chuàng)建、綁定和監(jiān)聽即可,這里的 Socket 類和之前實現(xiàn)的一樣。
在析構函數(shù)中可以選擇調(diào)用 close 函數(shù)將監(jiān)聽套接字進行關閉,但實際也可以不進行該動作,因為服務器運行后一般是不退出的。
代碼如下:
#pragma once
#include "socket.hpp"
#include <poll.h>
#define BACK_LOG 5
class PollServer{
private:
int _listen_sock; //監(jiān)聽套接字
int _port; //端口號
public:
PollServer(int port)
: _port(port)
{}
void InitPollServer()
{
_listen_sock = Socket::SocketCreate();
Socket::SocketBind(_listen_sock, _port);
Socket::SocketListen(_listen_sock, BACK_LOG);
}
~PollServer()
{
if (_listen_sock >= 0){
close(_listen_sock);
}
}
};
運行服務器??
服務器初始化完后開始運行,而 poll 服務器要做的就是不斷調(diào)用 poll 函數(shù),當事件就緒時執(zhí)行動作即可。
首先,在 poll 服務器開始死循環(huán)調(diào)用 poll 函數(shù)之前,需要定義一個 fds
數(shù)組,該數(shù)組當中的每個位置都是一個struct pollfd結構,后續(xù)調(diào)用 poll 函數(shù)時會作為參數(shù)進行傳入。先將 fds 數(shù)組當中每個位置初始化為無效,并將監(jiān)聽套接字添加到 fds 數(shù)組當中,表示服務器剛開始運行時只需要監(jiān)視監(jiān)聽套接字的讀事件。
此后,poll 服務器就不斷調(diào)用 poll 函數(shù)監(jiān)視讀事件是否就緒。如果返回值大于0,說明 poll 調(diào)用成功,此時已經(jīng)有文件描述符的讀事件就緒,接下來就應該對就緒事件進行處理。如果返回值等于0,則說明 timeout 時間耗盡,此時直接準備進行下一次 poll 調(diào)用即可。如果返回值為-1,則說明 poll 調(diào)用失敗,此時也讓服務器準備進行下一次 poll 調(diào)用,但實際應該進一步判斷錯誤碼,根據(jù)錯誤碼來判斷是否應該繼續(xù)調(diào)用 poll 函數(shù)。
#pragma once
#include "socket.hpp"
#include <poll.h>
#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1
class PollServer{
private:
int _listen_sock; //監(jiān)聽套接字
int _port; //端口號
public:
void Run()
{
struct pollfd fds[NUM];
ClearPollfds(fds, NUM, DFL_FD); //清空數(shù)組中的所有位置
SetPollfds(fds, NUM, _listen_sock); //將監(jiān)聽套接字添加到數(shù)組中,并關心其讀事件
for (;;){
switch (poll(fds, NUM, -1)){
case 0:
std::cout << "timeout..." << std::endl;
break;
case -1:
std::cerr << "poll error" << std::endl;
break;
default:
//正常的事件處理
//std::cout<<"有事件發(fā)生..."<<std::endl;
HandlerEvent(fds, NUM);
break;
}
}
}
private:
void ClearPollfds(struct pollfd fds[], int num, int default_fd)
{
for (int i = 0; i < num; i++){
fds[i].fd = default_fd;
fds[i].events = 0;
fds[i].revents = 0;
}
}
bool SetPollfds(struct pollfd fds[], int num, int fd)
{
for (int i = 0; i < num; i++){
if (fds[i].fd == DFL_FD){ //該位置沒有被使用
fds[i].fd = fd;
fds[i].events |= POLLIN; //添加讀事件到events當中
return true;
}
}
return false; //fds數(shù)組已滿
}
};
事件處理??
當 poll 檢測到有文件描述符的讀事件就緒,就會在其對應的 struct pollfd 結構中的 revents 成員中添加讀事件并返回,接下來 poll 服務器就應該對就緒事件進行處理了,事件處理過程如下:
首先遍歷 fds 數(shù)組中的每個 struct pollfd 結構,如果該結構當中的 fd 有效,且 revents 當中包含讀事件,則說明該文件描述符的讀事件就緒,接下來就需要進一步判斷該文件描述符是監(jiān)聽套接字還是與客戶端建立的套接字。
如果是監(jiān)聽套接字的讀事件就緒,則調(diào)用 accept 函數(shù)將底層建立好的連接獲取上來,并將獲取到的套接字添加到 fds 數(shù)組當中,表示下一次調(diào)用 poll 函數(shù)時需要監(jiān)視該套接字的讀事件。
如果是與客戶端建立的連接對應的讀事件就緒,則調(diào)用 read 函數(shù)讀取客戶端發(fā)來的數(shù)據(jù),并將讀取到的數(shù)據(jù)在服務器端進行打印。如果在調(diào)用 read 時發(fā)現(xiàn)客戶端將連接關閉或 read 調(diào)用失敗,則服務器也應關閉對應的連接,并將該連接對應的文件描述符從 fds 數(shù)組當中清除,表示下一次調(diào)用 poll 時無需再監(jiān)視該套接字的讀事件。
#pragma once
#include "socket.hpp"
#include <poll.h>
#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1
class PollServer{
private:
int _listen_sock; //監(jiān)聽套接字
int _port; //端口號
public:
void HandlerEvent(struct pollfd fds[], int num)
{
for (int i = 0; i < num; i++){
if (fds[i].fd == DFL_FD){ //跳過無效的位置
continue;
}
if (fds[i].fd == _listen_sock&&fds[i].revents&POLLIN){ //連接事件就緒
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0){ //獲取連接失敗
std::cerr << "accept error" << std::endl;
continue;
}
std::string peer_ip = inet_ntoa(peer.sin_addr);
int peer_port = ntohs(peer.sin_port);
std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;
if (!SetPollfds(fds, NUM, sock)){ //將獲取到的套接字添加到fds數(shù)組中,并關心其讀事件
close(sock);
std::cout << "poll server is full, close fd: " << sock << std::endl;
}
}
else if (fds[i].revents&POLLIN){ //讀事件就緒
char buffer[1024];
ssize_t size = read(fds[i].fd, buffer, sizeof(buffer)-1);
if (size > 0){ //讀取成功
buffer[size] = '\0';
std::cout << "echo# " << buffer << std::endl;
}
else if (size == 0){ //對端連接關閉
std::cout << "client quit" << std::endl;
close(fds[i].fd);
UnSetPollfds(fds, i); //將該文件描述符從fds數(shù)組中清除
}
else{
std::cerr << "read error" << std::endl;
close(fds[i].fd);
UnSetPollfds(fds, i); //將該文件描述符從fds數(shù)組中清除
}
}
}
}
private:
bool SetPollfds(struct pollfd fds[], int num, int fd)
{
for (int i = 0; i < num; i++){
if (fds[i].fd == DFL_FD){ //該位置沒有被使用
fds[i].fd = fd;
fds[i].events |= POLLIN; //添加讀事件到events當中
return true;
}
}
return false; //fds數(shù)組已滿
}
void UnSetPollfds(struct pollfd fds[], int pos)
{
fds[pos].fd = DFL_FD;
fds[pos].events = 0;
fds[pos].revents = 0;
}
};
因為這里將fds數(shù)組的大小是固定設置的,因此在將新獲取連接對應的文件描述符添加到fds數(shù)組時,可能會因為fds數(shù)組已滿而添加失敗,這時poll服務器只能將剛剛獲取上來的連接對應的套接字進行關閉。
服務器測試??
運行 poll 服務器時也需要先實例化出一個 PollServer 對象,對 poll 服務器進行初始化后就可以運行服務器了:
#include "poll_server.hpp"
#include <string>
static void Usage(std::string proc)
{
std::cerr << "Usage: " << proc << " port" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 2){
Usage(argv[0]);
exit(1);
}
int port = atoi(argv[1]);
PollServer* svr = new PollServer(port);
svr->InitPollServer();
svr->Run();
return 0;
}
p o l l 的優(yōu)點: \color{red} {poll 的優(yōu)點:} poll的優(yōu)點:
- struct pollfd 結構當中包含了 events 和 revents,相當于將 select 的輸入輸出型參數(shù)進行分離,因此在每次調(diào)用 poll 之前,不需要像 select 一樣重新對參數(shù)進行設置。
- poll可監(jiān)控的文件描述符數(shù)量沒有限制。
- poll也可以同時等待多個文件描述符,能夠提高IO的效率。
說明一下:
雖然代碼中將 fds 數(shù)組的元素個數(shù)定義為1024,但 fds 數(shù)組的大小是可以繼續(xù)增大的,poll 函數(shù)能夠幫你監(jiān)視多少個文件描述符是由傳入 poll 函數(shù)的第二個參數(shù)決定的。而 fd_set 類型只有1024個比特位,因此 select 函數(shù)最多只能監(jiān)視1024個文件描述符。文章來源:http://www.zghlxwxcb.cn/news/detail-497557.html
p o l l 的缺點: \color{red} {poll 的缺點:} poll的缺點:文章來源地址http://www.zghlxwxcb.cn/news/detail-497557.html
- 和 select 一樣,當 poll 返回后,需要遍歷 fds 數(shù)組來獲取就緒的文件描述符。
- 每次調(diào)用 poll,都需要把大量的 struct pollfd 結構從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷也會隨著 poll 監(jiān)視的文件描述符數(shù)目的增多而增大。
- 同時每次調(diào)用 poll 都需要在內(nèi)核遍歷傳遞進來的所有fd,這個開銷在 fd 很多時也很大。
到了這里,關于Linux 多路轉接 —— poll的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!