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. python 查看以及更新安装包

    查看 在终端(windows:电脑win+R, linux:ctrl+alt+T)输入: pip list 或者 conda list 更新 在终端(windows:电脑win+R, linux:ct ...

  2. 使用vue配合组件--转载

    1.由饿了么 UED (知乎专栏)设计的桌面端组件库已经开源,文档:Element,仓库: GitHub - ElemeFE/element: Desktop UI elements for Vue. ...

  3. [Fw] assembly code in gas syntax

    Address operand syntax There are up to 4 parameters of an address operand that are presented in the ...

  4. 自定义 MessageBox 组件

    效果: 公共组件页面: js部分: <script>   export default {     props: {       title: {         type: String ...

  5. eclipse导入了jar查看不了源码解决方法

    当导入源码的时候ctrl+左键的时候,还是查看不了源码.问题一直困扰我很久,百度了很多方法.现在分享下解决的方法. 经过简单整理,如下: 第一步:下载JAD . jad官方地址的官方下载地址是: ht ...

  6. Linux帮助用法

    内部命令: help COMMAND 或 man bash 外部命令: (1) COMMAND --helpCOMMAND -h(2) 使用手册(manual)man COMMAND(3) 信息页in ...

  7. mysql创建用户账号出错

    在数据库中输入“create user 'tom'@'%' identified by '123456';”时,出现“ERROR 1819 (HY000): Your password does no ...

  8. Codeforces 364D 随机算法

    题意:给你一个序列,定义ghd为一个序列中任意n / 2个数的gcd中最大的那个,现在问这个序列的ghd为多少. 思路:居然是论文题...来自2014年国家集训队论文<随机化算法在信息学竞赛中的 ...

  9. SQL Ssever 安装.NET3.5 框架

    SQL Ssever 安装.NET3.5 框架 我们在安装 SQL Sever 2014 的时候必须要安装 .NET3.5 框架,然后才能继续安装 SQL Server 2014. 您可能在安装 SQ ...

  10. vs code常用插件(python)

    1.chinese 作用:vscode设置为中文. 使用方法:Ctrl+Shift+P:输入 "config":选择zh 2.python 作用:调试 3.autoDocstrin ...