一、回顧前面的select
select優(yōu)點(diǎn):
目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點(diǎn)
select缺點(diǎn):
1.每次調(diào)用 select(),都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在 fd 很多時會很大,同時每次調(diào)用 select() 都需要在內(nèi)核遍歷傳遞進(jìn)來的所有 fd,這個開銷在 fd 很多時也很大。
2.單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低
二、poll函數(shù)概述
select() 和 poll() 系統(tǒng)調(diào)用的本質(zhì)一樣,poll() 的機(jī)制與 select() 類似,與 select() 在本質(zhì)上沒有多大差別,管理多個描述符也是進(jìn)行輪詢,根據(jù)描述符的狀態(tài)進(jìn)行處理,但是 poll() 沒有最大文件描述符數(shù)量的限制(但是數(shù)量過大后性能也是會下降)。poll() 和 select() 同樣存在一個缺點(diǎn)就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。
poll()函數(shù)介紹
頭文件:
#include <poll.h>
函數(shù)體:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
監(jiān)視并等待多個文件描述符的屬性變化
參數(shù):
fds:指向一個結(jié)構(gòu)體數(shù)組的第0個元素的指針,每個數(shù)組元素都是一個struct pollfd結(jié)構(gòu),用于指定測試某個給定的fd的條件
struct pollfd{
int fd; //文件描述符
short events; //等待的事件
short revents; //實(shí)際發(fā)生的事件
};
fd:每一個 pollfd 結(jié)構(gòu)體指定了一個被監(jiān)視的文件描述符,可以傳遞多個結(jié)構(gòu)體,指示 poll() 監(jiān)視多個文件描述符。
events:指定監(jiān)測fd的事件(輸入、輸出、錯誤),每一個事件有多個取值,如下:
revents:revents 域是文件描述符的操作結(jié)果事件,內(nèi)核在調(diào)用返回時設(shè)置這個域。events 域中請求的任何事件都可能在 revents 域中返回.
注意:每個結(jié)構(gòu)體的 events 域是由用戶來設(shè)置,告訴內(nèi)核我們關(guān)注的是什么,而 revents 域是返回時內(nèi)核設(shè)置的,以說明對該描述符發(fā)生了什么事件
nfds:用來指定第一個參數(shù)數(shù)組元素個數(shù)
timeout: 指定等待的毫秒數(shù),無論 I/O 是否準(zhǔn)備好,poll() 都會返回.
返回值:
成功時,poll() 返回結(jié)構(gòu)體中 revents 域不為 0 的文件描述符個數(shù);如果在超時前沒有任何事件發(fā)生,poll()返回 0;
失敗時,poll() 返回 -1,并設(shè)置 errno 為下列值之一:
EBADF:一個或多個結(jié)構(gòu)體中指定的文件描述符無效。
EFAULT:fds 指針指向的地址超出進(jìn)程的地址空間。
EINTR:請求的事件之前產(chǎn)生一個信號,調(diào)用可以重新發(fā)起。
EINVAL:nfds 參數(shù)超出 PLIMIT_NOFILE 值。
ENOMEM:可用內(nèi)存不足,無法完成請求。
三、poll示例舉例
用poll實(shí)現(xiàn)udp同時收發(fā)
代碼:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
int main(int argc,char *argv[])
{
int udpfd = 0;
int ret = 0;
struct pollfd fds[2];//監(jiān)測文件描述結(jié)構(gòu)體數(shù)組:2個
struct sockaddr_in saddr;
struct sockaddr_in caddr;
bzero(&saddr,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8000);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bzero(&caddr,sizeof(caddr));
caddr.sin_family = AF_INET;
caddr.sin_port = htons(8000);
//創(chuàng)建套接字
if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)
{
perror("socket error");
exit(-1);
}
//套接字端口綁字
if(bind(udpfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)
{
perror("bind error");
close(udpfd);
exit(-1);
}
printf("input: \"sayto 192.168.220.X\" to sendmsg to somebody\033[32m\n");
fds[0].fd = 0; //標(biāo)準(zhǔn)輸入描述符
fds[1].fd = udpfd; //udp描述符
fds[0].events = POLLIN; // 普通或優(yōu)先級帶數(shù)據(jù)可讀
fds[1].events = POLLIN; // 普通或優(yōu)先級帶數(shù)據(jù)可讀
while(1)
{
// 監(jiān)視并等待多個文件(標(biāo)準(zhǔn)輸入,udp套接字)描述符的屬性變化(是否可讀)
// 沒有屬性變化,這個函數(shù)會阻塞,直到有變化才往下執(zhí)行,這里沒有設(shè)置超時
ret = poll(fds, 2, -1);
write(1,"UdpQQ:",6);
if(ret == -1){ // 出錯
perror("poll()");
}
else if(ret > 0){ // 準(zhǔn)備就緒的文件描述符
char buf[100] = {0};
if( ( fds[0].revents & POLLIN ) == POLLIN ){ // 標(biāo)準(zhǔn)輸入
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
if(strncmp(buf, "sayto", 5) == 0)
{
char ipbuf[16] = "";
inet_pton(AF_INET, buf+6, &caddr.sin_addr);//給addr套接字地址再賦值.
printf("\rsay to %s\n",inet_ntop(AF_INET,&caddr.sin_addr,ipbuf,sizeof(ipbuf)));
continue;
}
else if(strcmp(buf, "exit")==0)
{
close(udpfd);
exit(0);
}
sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&caddr, sizeof(caddr));
}
else if( ( fds[1].revents & POLLIN ) == POLLIN ){ //udp套接字
struct sockaddr_in addr;
char ipbuf[INET_ADDRSTRLEN] = "";
socklen_t addrlen = sizeof(addr);
bzero(&addr,sizeof(addr));
recvfrom(udpfd, buf, 100, 0, (struct sockaddr*)&addr, &addrlen);
printf("\r\033[31m[%s]:\033[32m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf);
}
}
else if(0 == ret){ // 超時
printf("time out\n");
}
}
return 0;
}
運(yùn)行結(jié)果:
