Linux NIO 系列(04-2) polllinux
Netty 系列目录(http://www.javashuo.com/article/p-hskusway-em.html)编程
select() 和 poll() 系统调用的本质同样,管理多个描述符也是进行轮询,根据描述符的状态进行处理,可是 poll() 没有最大文件描述符数量的限制(可是数量过大后性能也是会降低)。poll() 和 select() 一样存在一个缺点就是,包含大量文件描述符的数组被总体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增长而线性增大。api
poll()函数介绍数组
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(1) 功能:网络
监视并等待多个文件描述符的属性变化数据结构
(2) 参数:socket
fds:指向一个结构体数组的第 0 个元素的指针,每一个数组元素都是一个 struct pollfd 结构,用于指定测试某个给定的 fd 的条件函数
struct pollfd { int fd; // 文件描述符 short events; // 等待的事件 short revents; // 实际发生的事件 };
nfds:用来指定第一个参数数组元素个数。性能
timeout:指定等待的毫秒数,不管 I/O 是否准备好,poll() 都会返回。
(3) pollfd 数据结构
fd:每个 pollfd 结构体指定了一个被监视的文件描述符,能够传递多个结构体,指示 poll() 监视多个文件描述符。
events:指定监测 fd 的事件(输入、输出、错误),每个事件有多个取值,以下:
POLLIN 有数据可读 POLLRDNORM 有普通数据可读,等效与POLLIN POLLPRI 有紧迫数据可读 POLLOUT 写数据不会致使阻塞 POLLER 指定的文件描述符发生错误 POLLHUP 指定的文件描述符挂起事件 POLLNVAL 无效的请求,打不开指定的文件描述符
revents:revents 域是文件描述符的操做结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件均可能在 revents 域中返回。
注意:每一个结构体的 events 域是由用户来设置,告诉内核咱们感兴趣的事件是什么,而 revents 域是返回时内核设置的,以说明对该描述符发生了什么事件。
(4) 返回值
成功时,poll() 返回结构体中 revents 域不为 0 的文件描述符个数;若是在超时前没有任何事件发生,poll() 返回 0;
失败时,poll() 返回 -1,并设置 errno 为下列值之一:
EBADF: 一个或多个结构体中指定的文件描述符无效。 EFAULT: fds 指针指向的地址超出进程的地址空间。 EINTR: 请求的事件以前产生一个信号,调用能够从新发起。 EINVAL: nfds 参数超出 PLIMIT_NOFILE 值。 ENOMEM: 可用内存不足,没法完成请求。
# 当前计算机所能打开的最大文件个数。受硬件影响,这个值也能够改(经过limits.conf) cat /proc/sys/fs/file-max # 查看一个进程能够打开的socket描述符上限。缺省为1024 ulimit -a # 修改成默认的最大文件个数。【注销用户,使其生效】 ulimit -n 2000 # soft软限制 hard硬限制。所谓软限制是能够用命令的方式修改该上限值,但不能大于硬限制 vi /etc/security/limits.conf * soft nofile 3000 # 设置默认值。可直接使用命令修改 * hard nofile 20000 # 最大上限值
#include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #include<sys/types.h> #include<netinet/in.h> #include<sys/socket.h> #include<sys/wait.h> #include<arpa/inet.h> #include<unistd.h> #include<ctype.h> #include<poll.h> #define SERVER_PORT 8888 #define OPEN_MAX 3000 #define BACKLOG 10 #define BUF_SIZE 1024 int main() { int i, j, maxi; int listenfd, connfd, sockfd; // 定义套接字描述符 int nready; // 接受 pool 返回值 int recvbytes; // 接受 recv 返回值 char recv_buf[BUF_SIZE]; // 发送缓冲区 struct pollfd client[OPEN_MAX]; // struct pollfd* fds // 定义 IPV4 套接口地址结构 struct sockaddr_in seraddr; // server 地址 struct sockaddr_in cliaddr; // client 地址 int cliaddr_len; // 初始化IPV4套接口地址结构 seraddr.sin_family = AF_INET; // 指定该地址家族 seraddr.sin_port = htons(SERVER_PORT); // 端口 seraddr.sin_addr.s_addr = INADDR_ANY; // IPV4的地址 bzero(&(seraddr.sin_zero), 8); // socket()函数 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket error"); exit(1); } // 地址重复利用 int on = 1; if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { perror("setsockopt error"); exit(1); } // bind() 函数 if(bind(listenfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr)) == -1) { perror("bind error"); exit(1); } // listen()函数 if(listen(listenfd, BACKLOG) == -1) { perror("listen error"); exit(1); } client[0].fd = listenfd; // 将 listenfd 加入监听序列 client[0].events = POLLIN; // 监听读事件 // 初始化client[]中剩下的元素 for(i = 1;i < OPEN_MAX;i++) { client[i].fd = -1; //不能用 0,0 也是文件描述符 } maxi = 0; //client[]中最大元素下标 while(1) { nready = poll(client, maxi + 1, -1);//阻塞监听 if(nready < 0) { perror("poll error!\n"); exit(1); } if(client[0].revents & POLLIN) { //位与操做;listenfd的读事件就绪 cliaddr_len = sizeof(cliaddr); if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &cliaddr_len))==-1) { perror("accept error"); exit(1); } printf("client IP: %s\t PORT : %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); //将sockfd加入监听序列 for(i = 1; i < OPEN_MAX; i++) { if(client[i].fd < 0) { client[i].fd = connfd; break; } } if(i == OPEN_MAX) { perror("too many clients!\n"); exit(1); } client[i].events = POLLIN;//监听connfd的读事件 if(i > maxi) { maxi = i; } //判断是否已经处理完事件 if(--nready == 0) { continue; } } // 检测客户端是否发来消息 for(i = 1; i <= maxi; i++) { if((sockfd = client[i].fd) < 0) { continue; } if(client[i].revents & POLLIN) { memset(recv_buf, 0, sizeof(recv_buf)); recvbytes = recv(sockfd, recv_buf, BUF_SIZE, 0); if(recvbytes < 0) { // `errno == EINTR` 被异常中断,须要重启。收到 RST 标志 // `errno == EAGIN 或 EWOULDBLOCK` 以非阻塞方式读数据,但没有数据,须要再次读 // `errno == ECONNRESET` 链接被重置,须要 close,移除链接 // `errno == other` 其它异常 if(errno == ECONNRESET) { // RET标志 printf("client[%d] aborted connection!\n",i); close(sockfd); client[i].fd = -1; } else { perror("recv error!\n"); exit(1); } } else if(recvbytes == 0) { printf("client[%d],close!\n",i); close(sockfd); client[i].fd = -1; } else { send(sockfd, recv_buf, recvbytes, 0); } if(--nready == 0) { break; } } } } return 0; }
参考:
天天用心记录一点点。内容也许不重要,但习惯很重要!