以下内容均为本人学习比价,若有错误,欢迎指出
在网络编程中,poll可能实际中用到的不是很多,所以作为了解性内容
上一篇为 IO多路转接之select:select
多路转接之poll
接口了解
1.pollfd结构接口了解
/* Data structure describing a polling request. */
struct pollfd
{
int fd; /* File descriptor to poll. */
short int events; /* Types of events poller cares about. */
short int revents; /* Types of events that actually occurred.
};
- fd 为文件描述符
- events 可以理解为一个位图,里面每一位存放对该文件描述符监听的事件集合
- revents 也可以理解为一个位图,里面每一位表述返回该文件描述符的事件集合
这里的 event 是一个short类型的数据,只有16位,表示所关心的16个事件
我们可以看到,这里将你要关心的事件和返回的事件分离开了,这点也是优于select
表1.常见的 events 和 revents 取值和含义
事件 | 取值 | 描述 |
---|---|---|
POLLIN | 0x001 | 数据可读(普通数据和优先数据) |
POLLPRI | 0x002 | 高优先级数据可读(例如TCP外带数据) |
POLLOUT | 0x003 | 数据可写(普通数据和优先数据) |
1.poll函数接口了解
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 第一个参数为 pollfd 结构体数组指针,数组的元素位每一个所要监听的 文件描述符结构
- 第二个参数为pollfd 结构体数组的大小
- 第三个参数为超时时间(ms)(这里为整型,不同于select)
tmieout < 0 阻塞式等待
timeout = 0 非阻塞式等待
timeout > 0 具体等待时间
实例代码
例一:使用 poll 监控标准输入
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(/*int argc,char * argv[]*/)
{
//定义pollfd结构,因为这里只监测标准输入,可以不用构造数组
struct pollfd poll_fd;
poll_fd.fd = 0;//所监测的文件描述符,这里为标准输入
poll_fd.events = POLLIN;//所监测文件的事件 这里为读事件
//循环读取标准输入
while(true)
{
//使用poll实现监测文件描述符读就绪状态
int ret = poll(&poll_fd,1,0);
if(ret < 0)
{
perror("poll");
continue;
}
if(ret == 0)
{
printf("time out\n");
}
//poll正确返回,开始进行读数据
char buf[1024] = {0};
ssize_t read_size = read(0,buf,sizeof(buf) - 1);
if(read_size < 0)
{
perror("read");
return -1;
}
if(read_size == 0)
{
printf("read done\n");
return 0;
}
buf[read_size] = '\0';
//将读取的的内容再输出到标准输出
printf("%s",buf);
}
return 0;
}
例二:使用 poll 实现回显服务器
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/poll.h>
//poll一次等待的最多的文件描述符的个数
const int Max_size = 1024;
//启动server
int start_server(const char * ip,short port)
{
//创建socket
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
return -1;
}
//绑定ip和端口号
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
addr.sin_port = htons(port);
int ret = bind(sock,(sockaddr *)&addr,sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
//使sock处于被动监听状态
ret = listen(sock,5);
if(ret < 0)
{
perror("listen");
return -1;
}
return sock;
}
//对poll_list 进行初始化
void init_poll_list(pollfd *poll_list,int max_size)
{
int i = 0;
for(i = 0; i < max_size;++i )
{
poll_list[i].fd = -1;
poll_list[i].events = 0;
poll_list[i].revents = 0;
}
}
//对要关注的文件描述符进行设置
//此时只是关注读就绪的文件描述符
void add_poll_list(pollfd *poll_list,int max_size,int fd)
{
int i = 0;
for(i = 0;i < max_size ; ++i)
{
if(poll_list[i].fd == -1)
{
poll_list[i].events = POLLIN;
poll_list[i].fd = fd;
break;
}
}
}
//main函数
int main(int argc,char * argv[])
{
if(argc != 3)
{
printf("Usage ./server ip port\n");
return 1;
}
int listen_socket = start_server(argv[1],atoi(argv[2]));
if(listen_socket < 0)
{
printf("server start falid\n");
return 2;
}
printf("server start ok\n");
//定义一个pollfd数组
pollfd poll_list[Max_size];
//对pollfd数组进行初始化
init_poll_list(poll_list,Max_size);
//将listen_socket进行设置
add_poll_list(poll_list,Max_size,listen_socket);
while(true)
{
//借助poll实现等待文件描述符状态
int ret = poll(poll_list,Max_size,-1);//阻塞式等待
//printf("poll done\n");
if(ret < 0)
{
perror("poll");
return -1;
}
if(ret == 0)
{
printf("time out\n");
}
//下面就是poll正确返回
int i = 0;
for(i = 0; i < Max_size;++i)
{
if(poll_list[i].fd == -1)
{
//无效的文件描述符
continue;
}
if(poll_list[i].revents != POLLIN)
{
//该文件描述符的读事件还没有就绪
continue;
}
//处理已经就绪的文件描述符
sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
if(poll_list[i].fd == listen_socket)
{
//printf("listen_socket ok\n");
//说明listen_socket已经都就绪
//那么就要对listen_socket进行处理
int new_socket = accept(listen_socket,(sockaddr *)&peer,&peer_len);
if(new_socket < 0)
{
perror("accpet");
continue;
}
//已经获得new_socket 那么就将new_socket进行在poll_list中设置
//继续循环,使poll继续等待new_socket的读事件就绪
add_poll_list(poll_list,Max_size,new_socket);
continue;
}
else
{
//printf("new_socket ok\n");
//这种情况就是new_socket已经就绪
//我们就看可以开始进行读事件了
char buf[1024] = {0};
ssize_t read_size = read(poll_list[i].fd,buf,sizeof(buf) - 1);
if(read_size < 0)
{
perror("read");
close(poll_list[i].fd);
//从pollfd数组中删除不再关注的文件描述符
poll_list[i].fd = -1;
continue;
}
if(read_size == 0)
{
printf("read done\n");
close(poll_list[i].fd);
//从pollfd数组中删除不再关注的文件描述符
poll_list[i].fd = -1;
continue;
}
//这种情况就是正确读取到内容了
//接收客户端发来的消息
buf[read_size] = '\0';
printf("[client %s:%d .%d]say : %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),poll_list[i].fd,buf);
//这里是回显服务器
//然后将读取到的内容写回给客户端
write(poll_list[i].fd,buf,strlen(buf));
//这里只读一次,是因为等待下一次文件描述符就绪应该让poll来等待
}
}
}//end while()
return 0;
}//end main
poll特点总结
poll的优点
这里的优点其实也是相较于select poll所做出优化的地方
1. 接口更加简单,pollfd 结构包含的events 和 revents 将输入和输出分离
2. 可监测的文件描述符没有上限,数组的大小(select局限于fd_set 的大小)
poll的缺点
1. 每次调用poll,都要将pollfd 结构从用户态拷贝至内核态,并且因为为每个文件描述符创建一个pollfd结构,所拷贝的数据量更大(相较于select)
2. poll返回,我们仍旧需要遍历整个数组来获取文件描述符(同select)
3. 同时连接的文件特别多,但是活跃的特别少,监视数量太大,效率低
下一篇:多路转接之 epoll
完。