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!\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;
}

参考:


每天用心记录一点点。内容也许不重要,但习惯很重要!

最新文章

  1. LINQ系列:Linq to Object相等操作符
  2. BZOJ3764 : Petya的序列
  3. Mac OS 中设置VPN(pptp连接方式)
  4. 在WPF中使用文件夹选择对话框
  5. ZXing二维码的生成和解析
  6. mysqldump原理3
  7. 《深度解析SDN》学习小结
  8. ECOS-Mongodb安装
  9. POJ2251-Dungeon Master
  10. 使用vue实现自定义搜索功能
  11. 一个kubeadm.config文件--定义了token,扩展了默认端口,外部ETCD集群,自定义docker仓库,基于ipvs的kubeproxy
  12. 自动部署tomcat 脚本
  13. Arrays 类的一些常见用法
  14. hog行人检测
  15. BFS &amp;&amp; DFS
  16. 【.NET中AOP的实现方案】静态代理
  17. Axis开发Web Service
  18. Xcode 7.0正式版发布了
  19. 使用简单的Java代码在SAP C4C里创建销售订单
  20. accept 和 connect API深入 重点accept阻塞和非阻塞问题学习

热门文章

  1. 使用matplotlib画出log的图像
  2. 如何让Jmeter压力测试减少压力机的资源消耗
  3. Python里面search()和match()的区别?
  4. 区间节点的lca
  5. poj3252 Round Numbers(数位dp)
  6. 【转】vim 配置文件 ,高亮+自动缩进+行号+折叠+优化
  7. VUE $SET源码
  8. Vue路由组件vue-router
  9. PHP实现笛卡尔积算法
  10. Socket网络通信——IO、NIO、AIO介绍以及区别