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. shell函数的存储和显示

  2. 常用的kubectl命令

    本文主要介绍kubernetes排查问题时经常用到的命令.这里主要借助kubectl命令来实现.以下列出常用命令,后面会对每个命令进行详细解释,并举例: kubectl核心命令 get  获取列出一个 ...

  3. css3 实现可以中英切换的导航条

    html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <ti ...

  4. Python3.5-20190516-廖老师-自我笔记-匿名函数-装饰器

    当函数很简单的时候采用匿名函数很方便.

  5. Electron-vue实战(一)—搭建项目与安装Element UI

    Electron-vue实战—搭建项目与安装Element UI 作者:狐狸家的鱼 本文链接 GitHub:sueRimn 一.新建项目1.初始化项目打开cmd,新建一个项目,我使用的是electro ...

  6. python3.x current_question

    扩散四维过程遇到的问题,暂不能解决,但先收集起来. ''' list_str=['test', None, '', 'str', ' ', 'END'] data = filter(lambda st ...

  7. python 常用技巧 — 数组 (array)

    目录: 1. 数组每一行除以这一行的总数(numpy divide row by row sum) 2. 数组每一行或者每一列求平均 (python average array columns or ...

  8. range类型(Python)

    range 不是 iterator >>> R = range(3) >>> next(R) Traceback (most recent call last): ...

  9. 【leetcode】923. 3Sum With Multiplicity

    题目如下: Given an integer array A, and an integer target, return the number of tuples i, j, k  such tha ...

  10. c# 6.0 语法特性

    namespace _6._0新特性 { using static _6._0新特性.Statics.StaticClass; class Program { static void Main(str ...