目录
多路IO-poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 函数说明: 跟select类似, 委托内核监控可读, 可写, 异常事件
- 函数参数:
- fds: 一个struct pollfd结构体数组的首地址
struct pollfd { int fd; //要监控的文件描述符,如果fd为-1, 表示内核不再监控 short events; //输入参数, 表示告诉内核要监控的事件, 读事件, 写事件, 异常事件 short revents;//输出参数, 表示内核告诉应用程序有哪些文件描述符有事件发生 };
- events/revents:
- POLLIN:可读事件
- POLLOUT: 可写事件
- POLLERR: 异常事件
- nfds: 告诉内核监控的范围, 具体是: 数组下标的最大值+1
- timeout:
- =0: 不阻塞, 立刻返回
- -1: 表示一直阻塞, 直到有事件发生
- >0: 表示阻塞时长, 在时长范围内若有事件发生会立刻返回; 如果超过了时长也会立刻返回
使用poll模型开发服务端流程
{ 1 创建socket, 得到监听文件描述符lfd----socket() 2 设置端口复用----setsockopt() 3 绑定----bind() 4 监听----listen() 5 struct pollfd client[1024]; client[0].fd = lfd; client[0].events = POLLIN; int maxi = 0; for(i=1; i<1024; i++) { client[i].fd = -1; } while(1) { nready = poll(client, maxi+1, -1); //异常情况 if(nready<0) { if(errno==EINTR) // 被信号中断 { continue; } break; } //有客户端连接请求到来 if(client[0].revents==POLLIN) { //接受新的客户端连接 cfd = accept(lfd, NULL, NULL); //寻找在client数组中可用位置 for(i=0; i<1024; i++) { if(client[i].fd==-1) { client[i].fd = cfd; client[i].events = POLLIN; break; } } //客户端连接数达到最大值 if(i==1024) { close(cfd); continue; } //修改client数组下标最大值 if(maxi<i) { maxi = i; } if(--nready==0) { continue; } } //下面是有客户端发送数据的情况 for(i=1; i<=maxi; i++) { sockfd = client[i].fd; //如果client数组中fd为-1, 表示已经不再让你内核监控了, 已经close了 if(client[i].fd==-1) { continue; } if(client[i].revents==POLLIN) { //read 数据 n = read(sockfd, buf, sizeof(buf)); if(n<=0) { close(sockfd); client[i].fd = -1; } else { //发送数据给客户端 write(sockfd, buf, n); } if(--nready==0) { break; } } } } close(lfd); }
代码实现
//IO多路复用技术poll函数的使用 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/in.h> #include <poll.h> #include "wrap.h" int main() { int i; int n; int lfd; int cfd; int ret; int nready; int maxfd; char buf[1024]; socklen_t len; int sockfd; fd_set tmpfds, rdfds; struct sockaddr_in svraddr, cliaddr; //创建socket lfd = Socket(AF_INET, SOCK_STREAM, 0); //允许端口复用 int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)); //绑定bind svraddr.sin_family = AF_INET; svraddr.sin_addr.s_addr = htonl(INADDR_ANY); svraddr.sin_port = htons(8888); ret = Bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in)); //监听listen ret = Listen(lfd, 128); struct pollfd client[1024]; for(i=0; i<1024; i++) { client[i].fd = -1; } //将监听文件描述符委托给内核监控----监控读事件 client[0].fd = lfd; client[0].events = POLLIN; maxfd = 0; //maxfd表示内核监控的范围 while(1) { nready = poll(client, maxfd+1, -1); if(nready<0) { perror("poll error"); exit(1); } //有客户端连接请求 if(client[0].fd==lfd && (client[0].revents & POLLIN)) { cfd = Accept(lfd, NULL, NULL); //寻找client数组中的可用位置 for(i=1; i<1024; i++) { if(client[i].fd==-1) { client[i].fd = cfd; client[i].events = POLLIN; break; } } //若没有可用位置, 则关闭连接 if(i==1024) { Close(cfd); continue; } if(maxfd<i) { maxfd = i; } if(--nready==0) { continue; } } //下面是有数据到来的情况 for(i=1; i<=maxfd; i++) { //若fd为-1, 表示连接已经关闭或者没有连接 if(client[i].fd==-1) { continue; } sockfd = client[i].fd; memset(buf, 0x00, sizeof(buf)); n = Read(sockfd, buf, sizeof(buf)); if(n<=0) { printf("read error or client closed,n==[%d]\n", n); Close(sockfd); client[i].fd = -1; //fd为-1,表示不再让内核监控 } else { printf("read over,n==[%d],buf==[%s]\n", n, buf); write(sockfd, buf, n); } if(--nready==0) { break; } } } Close(lfd); return 0; }
注意事项
- 当poll函数返回的时候, 结构体当中的fd和events没有发生变化, 究竟有没有事件发生由revents来判断, 所以poll是请求和返回分离
- struct pollfd结构体中的fd成员若赋值为-1, 则poll不会监控
- 相对于select, poll没有本质上的改变; 但是poll可以突破1024的限制
- 在/proc/sys/fs/file-max查看一个进程可以打开的socket描述符上限.
- 如果需要可以修改配置文件: /etc/security/limits.conf
- 加入如下配置信息, 然后重启终端即可生效.
* soft nofile 1024 * hard nofile 100000
- soft和hard分别表示ulimit命令可以修改的最小限制和最大限制
- 【注】:参考黑马linux C++教程