目錄
poll初識
poll函數(shù)
poll服務(wù)器
poll的優(yōu)點
poll的缺點
poll初識
poll也是系統(tǒng)提供的一個多路轉(zhuǎn)接接口。
- poll系統(tǒng)調(diào)用也可以讓我們的程序同時監(jiān)視多個文件描述符上的事件是否就緒,和select的定位是一樣的,適用場景也是一樣的。
poll函數(shù)
poll函數(shù)
poll函數(shù)的函數(shù)原型如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數(shù)說明:
- fds:一個poll函數(shù)監(jiān)視的結(jié)構(gòu)列表,每一個元素包含三部分內(nèi)容:文件描述符、監(jiān)視的事件集合、就緒的事件集合。
- nfds:表示fds數(shù)組的長度。
- timeout:表示poll函數(shù)的超時時間,單位是毫秒(ms)。
參數(shù)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,同時錯誤碼會被設(shè)置。
poll調(diào)用失敗時,錯誤碼可能被設(shè)置為:
-
EFAULT
:fds數(shù)組不包含在調(diào)用程序的地址空間中。 -
EINTR
:此調(diào)用被信號所中斷。 -
EINVAL
:nfds值超過RLIMIT_NOFILE值。 -
ENOMEM
:核心內(nèi)存不足。
struct pollfd結(jié)構(gòu)
struct pollfd結(jié)構(gòu)當(dāng)中包含三個成員:
- fd:特定的文件描述符,若設(shè)置為負(fù)值則忽略events字段并且revents字段返回0。
- events:需要監(jiān)視該文件描述符上的哪些事件。
- revents:poll函數(shù)返回時告知用戶該文件描述符上的哪些事件已經(jīng)就緒。
events和revents的取值:
這些取值實際都是以宏的方式進行定義的,它們的二進制序列當(dāng)中有且只有一個比特位是1,且為1的比特位是各不相同的。
- 因此在調(diào)用poll函數(shù)之前,可以通過或運算符將要監(jiān)視的事件添加到events成員當(dāng)中。
- 在poll函數(shù)返回后,可以通過與運算符檢測revents成員中是否包含特定事件,以得知對應(yīng)文件描述符的特定事件是否就緒。
poll服務(wù)器
poll的工作流程和select是基本類似的,這里我們也實現(xiàn)一個簡單poll服務(wù)器,該服務(wù)器也只是讀取客戶端發(fā)來的數(shù)據(jù)并進行打印。
PollServer類
PollServer類當(dāng)中也只需要包含監(jiān)聽套接字和端口號兩個成員變量,在poll服務(wù)器綁定時直接將IP地址設(shè)置為INADDR_ANY盡即可。
- 在構(gòu)造PollServer對象時,需要指明poll服務(wù)器的端口號,當(dāng)然也可以在初始化poll服務(wù)器的時候指明。
- 在初始化poll服務(wù)器的時候調(diào)用Socket類當(dāng)中的函數(shù),依次進行套接字的創(chuàng)建、綁定和監(jiān)聽即可,這里的Socket類和之前實現(xiàn)的一模一樣。
- 在析構(gòu)函數(shù)中可以選擇調(diào)用close函數(shù)將監(jiān)聽套接字進行關(guān)閉,但實際也可以不進行該動作,因為服務(wù)器運行后一般是不退出的。
代碼如下:
#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);
}
}
};
?運行服務(wù)器
服務(wù)器初始化完畢后就可以開始運行了,而poll服務(wù)器要做的就是不斷調(diào)用poll函數(shù),當(dāng)事件就緒時對應(yīng)執(zhí)行某種動作即可。
- 首先,在poll服務(wù)器開始死循環(huán)調(diào)用poll函數(shù)之前,需要定義一個fds數(shù)組,該數(shù)組當(dāng)中的每個位置都是一個struct pollfd結(jié)構(gòu),后續(xù)調(diào)用poll函數(shù)時會作為參數(shù)進行傳入。先將fds數(shù)組當(dāng)中每個位置初始化為無效,并將監(jiān)聽套接字添加到fds數(shù)組當(dāng)中,表示服務(wù)器剛開始運行時只需要監(jiān)視監(jiān)聽套接字的讀事件。
- 此后,poll服務(wù)器就不斷調(diào)用poll函數(shù)監(jiān)視讀事件是否就緒。如果poll函數(shù)的返回值大于0,則說明poll函數(shù)調(diào)用成功,此時已經(jīng)有文件描述符的讀事件就緒,接下來就應(yīng)該對就緒事件進行處理。如果poll函數(shù)的返回值等于0,則說明timeout時間耗盡,此時直接準(zhǔn)備進行下一次poll調(diào)用即可。如果poll函數(shù)的返回值為-1,則說明poll調(diào)用失敗,此時也讓服務(wù)器準(zhǔn)備進行下一次poll調(diào)用,但實際應(yīng)該進一步判斷錯誤碼,根據(jù)錯誤碼來判斷是否應(yīng)該繼續(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ù)組中,并關(guān)心其讀事件
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當(dāng)中
return true;
}
}
return false; //fds數(shù)組已滿
}
};
事件處理
當(dāng)poll檢測到有文件描述符的讀事件就緒,就會在其對應(yīng)的struct pollfd結(jié)構(gòu)中的revents成員中添加讀事件并返回,接下來poll服務(wù)器就應(yīng)該對就緒事件進行處理了,事件處理過程如下:
- 首先遍歷fds數(shù)組中的每個struct pollfd結(jié)構(gòu),如果該結(jié)構(gòu)當(dāng)中的fd有效,且revents當(dāng)中包含讀事件,則說明該文件描述符的讀事件就緒,接下來就需要進一步判斷該文件描述符是監(jiān)聽套接字還是與客戶端建立的套接字。
- 如果是監(jiān)聽套接字的讀事件就緒,則調(diào)用accept函數(shù)將底層建立好的連接獲取上來,并將獲取到的套接字添加到fds數(shù)組當(dāng)中,表示下一次調(diào)用poll函數(shù)時需要監(jiān)視該套接字的讀事件。
- 如果是與客戶端建立的連接對應(yīng)的讀事件就緒,則調(diào)用read函數(shù)讀取客戶端發(fā)來的數(shù)據(jù),并將讀取到的數(shù)據(jù)在服務(wù)器端進行打印。
- 如果在調(diào)用read函數(shù)時發(fā)現(xiàn)客戶端將連接關(guān)閉或read函數(shù)調(diào)用失敗,則poll服務(wù)器也直接關(guān)閉對應(yīng)的連接,并將該連接對應(yīng)的文件描述符從fds數(shù)組當(dāng)中清除,表示下一次調(diào)用poll函數(shù)時無需再監(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ù)組中,并關(guān)心其讀事件
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){ //對端連接關(guān)閉
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當(dāng)中
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;
}
};
說明一下:文章來源地址http://www.zghlxwxcb.cn/news/detail-652158.html
- 因為這里將fds數(shù)組的大小是固定設(shè)置的,因此在將新獲取連接對應(yīng)的文件描述符添加到fds數(shù)組時,可能會因為fds數(shù)組已滿而添加失敗,這時poll服務(wù)器只能將剛剛獲取上來的連接對應(yīng)的套接字進行關(guān)閉。
poll服務(wù)器測試
運行poll服務(wù)器時也需要先實例化出一個PollServer對象,對poll服務(wù)器進行初始化后就可以運行服務(wù)器了。
代碼如下:
#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;
}
因為我們編寫的poll服務(wù)器在調(diào)用poll函數(shù)時,將timeout的值設(shè)置成了-1,因此運行服務(wù)器后如果沒有客戶端發(fā)來連接請求,那么服務(wù)器就會在調(diào)用poll函數(shù)后進行阻塞等待。
當(dāng)我們用telnet工具連接poll服務(wù)器后,poll服務(wù)器調(diào)用的poll函數(shù)在檢測到監(jiān)聽套接字的讀事件就緒后就會調(diào)用accept獲取建立好的連接,并打印輸出客戶端的IP和端口號,此時客戶端發(fā)來的數(shù)據(jù)也能夠成功被poll服務(wù)器收到并進行打印輸出。
此外,poll服務(wù)器也是一個單進程服務(wù)器,但是它也可以同時為多個客戶端提供服務(wù)。
當(dāng)服務(wù)器端檢測到客戶端退出后,也會關(guān)閉對應(yīng)的連接,并將對應(yīng)的套接字從fds數(shù)組當(dāng)中清除。
文章來源:http://www.zghlxwxcb.cn/news/detail-652158.html
poll的優(yōu)點
- struct pollfd結(jié)構(gòu)當(dāng)中包含了events和revents,相當(dāng)于將select的輸入輸出型參數(shù)進行分離,因此在每次調(diào)用poll之前,不需要像select一樣重新對參數(shù)進行設(shè)置。
- poll可監(jiān)控的文件描述符數(shù)量沒有限制。
- 當(dāng)然,poll也可以同時等待多個文件描述符,能夠提高IO的效率。
說明一下:
- 雖然代碼中將fds數(shù)組的元素個數(shù)定義為1024,但fds數(shù)組的大小是可以繼續(xù)增大的,poll函數(shù)能夠幫你監(jiān)視多少個文件描述符是由傳入poll函數(shù)的第二個參數(shù)決定的。
- 而fd_set類型只有1024個比特位,因此select函數(shù)最多只能監(jiān)視1024個文件描述符。
poll的缺點
- 和select函數(shù)一樣,當(dāng)poll返回后,需要遍歷fds數(shù)組來獲取就緒的文件描述符。
- 每次調(diào)用poll,都需要把大量的struct pollfd結(jié)構(gòu)從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷也會隨著poll監(jiān)視的文件描述符數(shù)目的增多而增大。
- 同時每次調(diào)用poll都需要在內(nèi)核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大。
到了這里,關(guān)于【Linux】IO多路轉(zhuǎn)接——poll接口的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!