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

参考:


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

Linux NIO 系列(04-2) poll的更多相关文章

  1. Linux NIO 系列(04-4) select、poll、epoll 对比

    目录 一.API 对比 1.1 select API 1.2 poll API 1.3 epoll API 二.总结 2.1 支持一个进程打开的 socket 描述符(FD)不受限制(仅受限于操作系统 ...

  2. Linux NIO 系列(02) 阻塞式 IO

    目录 一.环境准备 1.1 代码演示 二.Socket 是什么 2.1 socket 套接字 2.2 套接字描述符 2.3 文件描述符和文件指针的区别 三.基本的 SOCKET 接口函数 3.1 so ...

  3. Linux NIO 系列(03) 非阻塞式 IO

    目录 一.非阻塞式 IO 附:非阻塞式 IO 编程 Linux NIO 系列(03) 非阻塞式 IO Netty 系列目录(https://www.cnblogs.com/binarylei/p/10 ...

  4. Linux NIO 系列(04-3) epoll

    目录 一.why epoll 1.1 select 模型的缺点 1.2 epoll 模型优点 二.epoll API 2.1 epoll_create 2.2 epoll_ctl 2.3 epoll_ ...

  5. Linux NIO 系列(04-1) select

    目录 一.select 机制的优势 二.select API 介绍与使用 2.1 select 2.2 fd_set 集合操作 2.3 select 使用范例 三.深入理解 select 模型: 四. ...

  6. Java NIO系列教程(七) selector原理 Epoll版的Selector

    目录: Reactor(反应堆)和Proactor(前摄器) <I/O模型之三:两种高性能 I/O 设计模式 Reactor 和 Proactor> <[转]第8章 前摄器(Proa ...

  7. Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. c/c++ linux epoll系列1 创建epoll

    linux epoll系列1 创建epoll 据说select和poll的弱点是,随着连接(socket)的增加,性能会直线下降. epoll不会随着连接(socket)的增加,性能直线下降. 知识点 ...

  9. (4)top详解 (每周一个linux命令系列)

    (4)top详解 (每周一个linux命令系列) linux命令 top详解 引言:今天的命令是用来看cpu信息的top top 我们先看man top top - display Linux pro ...

随机推荐

  1. django简单实现注册登录模块

    源码下载:https://files.cnblogs.com/files/hardykay/login.zip 新建项目(我使用pycharm开发,也可以使用如下命令建立项目 ) cmd命令行,前提需 ...

  2. 学习php的步骤是什么?

    PHP应该学什么,如何学好PHP (注:原文来自传智播客) 一些共性问题,大致是: 1. 应该怎样学习PHP,学习的顺序是怎样的? 2. PHP学好后,可以做什么事情? 3. 听得懂课,但是一旦自己独 ...

  3. python post 发送字符串

    python post 发送一段字符串 把字符串写在表单里,表单用字典格式,字符串作value import requests data={key:str} r=requests.post(url,d ...

  4. java虚拟机规范(se8)——class文件格式(七)

    4.7.5 Exceptions 属性 Exceptions 属性是一个变长属性,它位于 method_info(§4.6)结构的属性表中. Exceptions 属性指出了一个方法需要检查的可能抛出 ...

  5. vue证明题四,使用组件

    vue的开发方式,基本上是以组件为主的,至于为啥,我也不好去论述,网上看别人的 所谓渐进式开发,也是源自于单页面应用这一说,而注册一个域名以后,指定了首页,爬虫爬取链接都是从首页开始的 如果一个网址, ...

  6. go中加号的用法

    // + 的用法 package main import "fmt" func main() { // 加号两端都是数字的话,做加法运算 var i = 1 var j = 1 v ...

  7. js与html中unicode编码的使用

    js中 // unicode在字符串中使用格式:\u四位编码,一定是四位,否则报错 // 显示 @ 符 console.log(`十六进制 unicode 编码 0x0040 对应的字符是:${'\u ...

  8. 【串线篇】mybatis-config.xml配置事项

    一.术语 properties 属性 settings 设置 typeAliases 类型命名 typeHandlers 类型处理器 objectFactory 对象工厂, plugins 插件, e ...

  9. 查看tomcat的版本号

    本经验主要介绍在windows下,如何查看tomcat的版本号. 工具/原料 安装了tomcat server的操作系统. 一.绿色版tomcat版本查看--命令catalina version 或者 ...

  10. C# 在Word表格中插入新行(表格含合并行)

    public string CreateWordFile(string CheckedInfo)         {             string message = "" ...