Linux NIO 系列(04-2) poll
Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)
一、select 和 poll 比较
select() 和 poll() 系统调用的本质一样,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
二、poll API
poll()函数介绍
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(1) 功能:
监视并等待多个文件描述符的属性变化
(2) 参数:
-
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: 可用内存不足,无法完成请求。
附1:linux 每个进程IO限制
# 当前计算机所能打开的最大文件个数。受硬件影响,这个值也可以改(通过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 # 最大上限值
附2:poll 网络编程
#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!
");
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 PORT : %d
", 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!
");
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!
",i);
close(sockfd);
client[i].fd = -1;
} else {
perror("recv error!
");
exit(1);
}
} else if(recvbytes == 0) {
printf("client[%d],close!
",i);
close(sockfd);
client[i].fd = -1;
} else {
send(sockfd, recv_buf, recvbytes, 0);
}
if(--nready == 0) {
break;
}
}
}
}
return 0;
}
参考:
每天用心记录一点点。内容也许不重要,但习惯很重要!