1. poll介绍
系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。
核心函数:
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定测试某个给定的fd的条件
nfds:用来指定第一个参数数组元素个数
timeout: 指定等待的毫秒数,无论 I/O 是否准备好,poll() 都会返回.
-1:永远等待,直到事件发生0 :立即返回大于0:等待指定数目的毫秒数
struct pollfd{int fd; //文件描述符short events; //等待的事件short revents; //实际发生的事件};
fd:每一个 pollfd 结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示 poll() 监视多个文件描述符。
events:指定监测fd的事件(输入、输出、错误)
revents:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回。
poll函数的返回值
成功时,poll() 返回结构体中 revents 域不为 0 的文件描述符个数;如果在超时前没有任何事件发生,poll()返回 0;
失败时,poll() 返回 -1,并设置 errno 为下列值之一:
EBADF:一个或多个结构体中指定的文件描述符无效。EFAULT:fds 指针指向的地址超出进程的地址空间。EINTR:请求的事件之前产生一个信号,调用可以重新发起。EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。ENOMEM:可用内存不足,无法完成请求。
2. select和poll的异同
select/poll的缺点在于:
每次调用时要重复地从用户态读入参数。每次调用时要重复地扫描文件描述符。每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
select和poll的不同点
select()的fd_set是一个位掩码(bit mask),因此fd_set有固定的长度。
使用者在调用poll()时需要自定义pollfd结构体数组并且需要指定数组的大小,所以呢这里原理上讲就是没有限制的。
3. poll的具体实例
服务端
#include <unistd.h>#include <sys/types.h>#include <fcntl.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <signal.h>#include <sys/wait.h>#include <poll.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <vector>#include <iostream>//动态数组typedef std::vector<struct pollfd> PollFdList;int main(void){signal(SIGPIPE, SIG_IGN);//TIME_WAIT状态 忽略pipe信号,避免僵尸进程signal(SIGCHLD, SIG_IGN);//int listenfd;//创建套接字if ((listenfd = socket(PF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP)) < 0){//创建socket套接字ERR_EXIT("socket");}//填充地址struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));//先清零servaddr.sin_family = AF_INET;//协议族servaddr.sin_port = htons(5188);//端口号servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//本地的IPint on = 1;//setsockopt用来设置套接字的参数if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0){//设置地址的重新利用ERR_EXIT("setsockopt");}if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){//绑定地址和端口ERR_EXIT("bind");}if (listen(listenfd, SOMAXCONN) < 0){//监听端口ERR_EXIT("listen");}//pollstruct pollfd pfd;pfd.fd = listenfd;pfd.events = POLLIN;//关注pollin事件,表明有事件可读PollFdList pollfds;//创建一个动态数组(向量)pollfds.push_back(pfd);//把文件描述符添加到数组里面int nready;struct sockaddr_in peeraddr;socklen_t peerlen;int connfd;while (1){ //动态数组首地址pollfds.data()C++11//参数:结构体指针,所监听文件描述符的个数,超时时间nready = poll(&*pollfds.begin(), pollfds.size(), -1);//负数表示无限等待,直到发生事件才返回if (nready == -1){//出错if (errno == EINTR)continue;ERR_EXIT("poll");}if (nready == 0) //无事件发生continue;if (pollfds[0].revents & POLLIN)//监听的pollin事件到来{peerlen = sizeof(peeraddr);//accept4为一个新的函数,可以添加选项,非阻塞//接受监听connfd = accept4(listenfd, (struct sockaddr*)&peeraddr,&peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);if (connfd == -1)ERR_EXIT("accept4");//把时间加入监听的事件中pfd.fd = connfd;pfd.events = POLLIN;pfd.revents = 0;//目前还没有任何事件返回,置为零pollfds.push_back(pfd);--nready;// 连接成功,打印IP和端口信息std::cout<<"ip="<<inet_ntoa(peeraddr.sin_addr)<<" port="<<ntohs(peeraddr.sin_port)<<std::endl;if (nready == 0)//事件都处理完了continue;}//遍历查看哪些已连接套接字产生事件(迭代器)for (PollFdList::iterator it=pollfds.begin()+1;it != pollfds.end() && nready >0; ++it){if (it->revents & POLLIN)//如果是pollin事件{--nready;connfd = it->fd;char buf[1024] = {0};int ret = read(connfd, buf, 1024);//读取消息内容if (ret == -1)//出错ERR_EXIT("read");if (ret == 0)//{std::cout<<"client close"<<std::endl;it = pollfds.erase(it);//这里就会自动定位了--it;//循环有++,所以这里要先--close(connfd);continue;}std::cout<<buf;//打印收到的消息内容write(connfd, buf, strlen(buf));}}}return 0;}
客户端
#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <stdlib.h>#include <stdio.h>#include <errno.h>#include <string.h>#include <iostream>#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(void){int sock;//创建套接字if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)ERR_EXIT("socket");//填充服务器地址和端口struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(5188);servaddr.sin_addr.s_addr = inet_addr("这个填写你的IP");//链接到服务器if (connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)ERR_EXIT("connect");struct sockaddr_in localaddr;socklen_t addrlen = sizeof(localaddr);if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)ERR_EXIT("getsockname");//输出IP和端口信息std::cout<<"ip="<<inet_ntoa(localaddr.sin_addr)<<" port="<<ntohs(localaddr.sin_port)<<std::endl;char sendbuf[1024] = {0};char recvbuf[1024] ={0};while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL){//发送和接受write(sock, sendbuf, strlen(sendbuf));read(sock, recvbuf, sizeof(recvbuf));fputs(recvbuf, stdout);//清空缓冲区memset(sendbuf, 0, sizeof(sendbuf));memset(recvbuf, 0, sizeof(recvbuf));}close(sock);return 0;}