poll 系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
fds:是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。
struct pollfd {
int fd; /* file descriptor 待监听的文件描述符 */
short events; /* requested events 待监听文件描述符对应事件*/
short revents; /* returned events 文件描述符实际发生事件*/
};
nfds:实际有效监听个数(被监听事件集合fds大小)
timeout:指定poll超时值(单位毫秒)
-1:永久阻塞,#define INFTIM -1
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
返回值:
返回满足对应监听事件的文件描述符总个数
poll实现流程与select相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多,管理多个描述符也是进行轮询。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
using namespace std;
//定义服务端端口号
#define SERVER_PORT 9527
#define OPEN_MAX 1024
int main (int argc ,char*argv[])
{
int i,n;
//int client[FD_SETSIZE];//自定义数组,大小为1024
int nready=0;//保存poll函数返回值,记录满足监听事件的fd个数
int maxfd=0;//记录最大的文件描述符
int lfd=0;//用于监听的套接字
int cfd=0;//用于通信的套接字
int sockfd=0;
int maxi;//用于检索客户端文件描述符的下标
char buf[BUFSIZ],str[INET_ADDRSTRLEN];
//创建pollfd结构体 fd:记录文件描述符 events:设置监听事件 revents:实际发生的事件
struct pollfd client[OPEN_MAX];
//创建地址结构
struct sockaddr_in server_addr,client_addr;
socklen_t client_addr_len;
//创建套接字
lfd=Socket(AF_INET,SOCK_STREAM,0);
//设置端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//初始化
//memset(&server_addr,0,sizeof(server_addr));//将地址结构清零
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
//绑定地址结构
Bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
//设置监听
Listen(lfd,128);
//将需要监听的第一个文件描述符存入到client数组中
client[0].fd=lfd; //存文件描述符
client[0].events=POLLIN;//events用于设置监听事件
//初始化结构体文件描述符
for(i=0;i<OPEN_MAX;i++)
{
client[i].fd=-1;//将文件描述符初始化为-1,意味着为无效状态
}
maxi=0;//client数组有效元素最大下标值
//需要循环设置监听
while(1)
{
//调用poll函数阻塞监听是否有客户端连接请求
nready=poll(client,maxi+1,-1);
//检查是否成功返回
if(nready<0)
{
sys_err("poll error");
}
//判断监听描述符上发生的事件是否为POLLIN
//如返回为真则说明有新的客户端进行连接请求
if(client[0].revents&POLLIN)
{
client_addr_len=sizeof(client_addr);
//调用accept函数接收客户端请求
cfd=Accept(lfd,(struct sockaddr*)&client_addr,&client_addr_len);
cout<<"received from "<<inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str))
<<"at PORT"<<ntohs(client_addr.sin_port)<<endl;
//将返回的文件描述符存入client数组
for(i=1;i<OPEN_MAX;i++)
{
if(client[i].fd<0)
{
client[i].fd=cfd;//将返回的文件描述符存入到client数组(设置监听文件描述符)
break;
}
}
//达到监听上限报错 1024
if(i==OPEN_MAX)
{
cout<<"too many clients"<<endl;
}
client[i].events=POLLIN;//设置监听文件描述符对应的事件
//保证maxi存的为数组的最后一个元素下标
if(i>maxi)
{
maxi=i;
}
//判断是否只有lfd事件,若为真则只有lfd事件,后续代码不需要执行
if(0==--nready)
continue;
}
//循环检测哪个客户端数据就绪
for(i=1;i<maxi;i++)
{
if((sockfd=client[i].fd)<0)//将文件描述符存到sockfd临时变量中
{
continue;
}
//判断事件是否满足为实际发生的事件(在这里为读事件)
if(client[i].revents&POLLIN)
{
//调用read函数读取数据
n =Read(sockfd,buf,sizeof(buf));
if(n<0)
{
if(errno==ECONNRESET)//收到RST标志
{
Close(sockfd);//关闭文件描述符
client[i].fd=-1;//将文件描述符置-1,意为无效
}
else
sys_err("read error");
}
//判断是否读到结尾,若是就将该文件描述符关闭
else if(n==0)
{
Close(sockfd);
client[i].fd=-1;
}
//没有读到结尾,则对读取到的数据完成大小写转换
else if(n>0)
{
for(int j=0;j<n;j++)
{
//大小写转换
buf[j]=toupper(buf[j]);
}
//写回buf
Write(sockfd,buf,n);
//写到屏幕输出
Write(STDOUT_FILENO, buf, n);
}
}
}
}
Close(lfd);
return 0;
}
同多进程客户端程序
略
优点:
1.没有最大文件描述符数量的限制
2.传入,传出事件分离
缺点:
1.大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2.poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
select、poll、epoll对比分析