1、基本知识

  poll是Linux中的字符设备驱动中的一个函数。Linux 2.5.44版本后,poll被epoll取代。和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列。

  poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

poll优点

(1)poll() 不要求开发者计算最大文件描述符加一的大小。
(2)poll() 在应付大数目的文件描述符的时候速度更快,相比于select。
(3)它没有最大连接数的限制,原因是它是基于链表来存储的。
(4)在调用函数时,只需要对参数进行一次设置就好了

poll缺点

(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
(2)与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符,这样会使性能下降
(3)同时连接的大量客户端在一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降

elect优点:
目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点
select缺点:
(1)每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd,这个开销在 fd 很多时也很大。
(2)单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低
(3)select函数在每次调用之前都要对参数进行重新设定,这样做比较麻烦,而且会降低性能

2、poll函数

  函数格式如下所示:

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

  pollfd结构体定义如下:

struct pollfd
{ int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;

  

每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:

  POLLIN         有数据可读。

  POLLRDNORM       有普通数据可读。

  POLLRDBAND      有优先数据可读。

  POLLPRI         有紧迫数据可读。

  POLLOUT            写数据不会导致阻塞。

  POLLWRNORM       写普通数据不会导致阻塞。

  POLLWRBAND        写优先数据不会导致阻塞。

  POLLMSGSIGPOLL     消息可用。

  此外,revents域中还可能返回下列事件:
  POLLER     指定的文件描述符发生错误。

  POLLHUP   指定的文件描述符挂起事件。

  POLLNVAL  指定的文件描述符非法。

这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。

  使用poll()和select()不一样,你不需要显式地请求异常情况报告。
  POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。

  timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。

  返回值和错误代码
  成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
  EBADF         一个或多个结构体中指定的文件描述符无效。

  EFAULTfds   指针指向的地址超出进程的地址空间。

  EINTR      请求的事件之前产生一个信号,调用可以重新发起。

  EINVALnfds  参数超出PLIMIT_NOFILE值。

  ENOMEM       可用内存不足,无法完成请求。

3、测出程序

  编写一个echo server程序,功能是客户端向服务器发送信息,服务器接收输出并原样发送回给客户端,客户端接收到输出到终端。

  服务器端程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h> #include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h> #define IPADDRESS "127.0.0.1"
#define PORT 8787
#define MAXLINE 1024
#define LISTENQ 5
#define OPEN_MAX 1000
#define INFTIM -1 //函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用poll
static void do_poll(int listenfd);
//处理多个连接
static void handle_connection(struct pollfd *connfds,int num); int main(int argc,char *argv[])
{
int listenfd,connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
listenfd = socket_bind(IPADDRESS,PORT);
listen(listenfd,LISTENQ);
do_poll(listenfd);
return 0;
} static int socket_bind(const char* ip,int port)
{
int listenfd;
struct sockaddr_in servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if (listenfd == -1)
{
perror("socket error:");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
{
perror("bind error: ");
exit(1);
}
return listenfd;
} static void do_poll(int listenfd)
{
int connfd,sockfd;
struct sockaddr_in cliaddr;
socklen_t cliaddrlen;
struct pollfd clientfds[OPEN_MAX];
int maxi;
int i;
int nready;
//添加监听描述符
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
//初始化客户连接描述符
for (i = 1;i < OPEN_MAX;i++)
clientfds[i].fd = -1;
maxi = 0;
//循环处理
for ( ; ; )
{
//获取可用描述符的个数
nready = poll(clientfds,maxi+1,INFTIM);
if (nready == -1)
{
perror("poll error:");
exit(1);
}
//测试监听描述符是否准备好
if (clientfds[0].revents & POLLIN)
{
cliaddrlen = sizeof(cliaddr);
//接受新的连接
if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)
{
if (errno == EINTR)
continue;
else
{
perror("accept error:");
exit(1);
}
}
fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
//将新的连接描述符添加到数组中
for (i = 1;i < OPEN_MAX;i++)
{
if (clientfds[i].fd < 0)
{
clientfds[i].fd = connfd;
break;
}
}
if (i == OPEN_MAX)
{
fprintf(stderr,"too many clients.\n");
exit(1);
}
//将新的描述符添加到读描述符集合中
clientfds[i].events = POLLIN;
//记录客户连接套接字的个数
maxi = (i > maxi ? i : maxi);
if (--nready <= 0)
continue;
}
//处理客户连接
handle_connection(clientfds,maxi);
}
} static void handle_connection(struct pollfd *connfds,int num)
{
int i,n;
char buf[MAXLINE];
memset(buf,0,MAXLINE);
for (i = 1;i <= num;i++)
{
if (connfds[i].fd < 0)
continue;
//测试客户描述符是否准备好
if (connfds[i].revents & POLLIN)
{
//接收客户端发送的信息
n = read(connfds[i].fd,buf,MAXLINE);
if (n == 0)
{
close(connfds[i].fd);
connfds[i].fd = -1;
continue;
}
// printf("read msg is: ");
write(STDOUT_FILENO,buf,n);
//向客户端发送buf
write(connfds[i].fd,buf,n);
}
}
}

  客户端代码如下所示:

#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h> #define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787 #define max(a,b) (a > b) ? a : b static void handle_connection(int sockfd); int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//处理连接描述符
handle_connection(sockfd);
return 0;
} static void handle_connection(int sockfd)
{
char sendline[MAXLINE],recvline[MAXLINE];
int maxfdp,stdineof;
struct pollfd pfds[2];
int n;
//添加连接描述符
pfds[0].fd = sockfd;
pfds[0].events = POLLIN;
//添加标准输入描述符
pfds[1].fd = STDIN_FILENO;
pfds[1].events = POLLIN;
for (; ;)
{
poll(pfds,2,-1);
if (pfds[0].revents & POLLIN)
{
n = read(sockfd,recvline,MAXLINE);
if (n == 0)
{
fprintf(stderr,"client: server is closed.\n");
close(sockfd);
}
write(STDOUT_FILENO,recvline,n);
}
//测试标准输入是否准备好
if (pfds[1].revents & POLLIN)
{
n = read(STDIN_FILENO,sendline,MAXLINE);
if (n == 0)
{
shutdown(sockfd,SHUT_WR);
continue;
}
write(sockfd,sendline,n);
}
}
}

  4、程序测试结果

IO多路复用——poll的更多相关文章

  1. Linux IO多路复用 poll

    Linux IO多路复用 poll 之前曾经提到过 select poll 跟select类似,poll改进了select的一个确定,就是poll没有监听上限 不过poll还是需要遍历以及频繁的把数组 ...

  2. linux IO多路复用POLL机制深入分析

    POLL机制的作用这里就不进行介绍,根据linux man手册,解释为在一个文件描述符上等待某个事件.按照抽象一点的理解,当某个事件被触发(条件被满足),文件描述符变为有状态,那么用户空间可以根据此进 ...

  3. IO多路复用之poll总结

    1.基本知识 poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制.poll和selec ...

  4. IO多路复用之poll

    1.基本知识 poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制.poll和selec ...

  5. 网络编程基础【day10】:IO多路复用

    这些名词比较绕口,理解涵义就好.一个epoll场景:一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就 ...

  6. 聊聊IO多路复用之select、poll、epoll详解

    本文转载自: http://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666538922&idx=1&sn=e6b436ef ...

  7. Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)

    Linux下测试代码: http://www.linuxhowtos.org/C_C++/socket.htm TCP模型 //TCPClient.c #include<string.h> ...

  8. 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

    下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...

  9. 网络通信 --> IO多路复用之select、poll、epoll详解

    IO多路复用之select.poll.epoll详解      目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,I/O多路复用就是通过一种机制,一个进程可以监视 ...

随机推荐

  1. PCA:主成分分析

    PCA的概念: 主要思想是将n维特征映射到k维上,这k维是全新的正交特征,这k维特征被称为主成分,在原数据的基础上重新构造出来k维.就是从原始的空间顺序的找出一组相互正交的坐标轴,新坐标轴的选择和数据 ...

  2. Docker 创建 Redis 容器

    Docker 创建 Redis 容器 # 配置文件映射: # -v /root/redis/redis.conf:/etc/redis/redis.conf # 数据目录映射: # -v /root/ ...

  3. 公众号对接百度翻译API

    有时候在公众号中需要对接一些翻译的功能或者其他.最常见的翻译API就是中英互译,程序员用的最多的也就是中译英. 1.到百度翻译官网申请账号 http://api.fanyi.baidu.com/api ...

  4. Java & Android未捕获异常处理机制

    一.背景 无论是Java还是Android项目,往往都会用到多线程.不管是主线程还是子线程,在运行过程中,都有可能出现未捕获异常.未捕获异常中含有详细的异常信息堆栈,可以很方便的去帮助我们排查问题. ...

  5. struts2拦截器的实现机制

    前言 最近老大让每周写一篇技术性的博客,想想也没啥写,就想着随便拿个以前的项目去研究研究五大框架的底层代码.本人水平有限,有不对的地方还望大家勿喷,指正! 开始之前先了解下strtus2的工作流程: ...

  6. service的yaml说明

    apiVersion: v1 kind: Service metadata: name: nginx-service labels: app: nginx spec: ports: - port: 8 ...

  7. oracle 获取表\视图的列名

     select COLUMN_NAME FROM user_col_comments WHERE TABLE_NAME='视图名'  select COLUMN_NAME from all_tab_c ...

  8. C# 简单日志帮助类LogHelper

    调用: LogHelper.Debug(""); LogHelper.Info(""); LogHelper.Error(""); 项目添加 ...

  9. ADO.NET中使用事务

    using (SqlConnection conn = new SqlConnection(k2ConnStr)) { SqlCommand cmd = new SqlCommand(sql, con ...

  10. Java诊断利器Arthas优雅排查生产环境

    前言 Arthas 是Alibaba开源的Java诊断工具.在线排查问题,无需重启:动态跟踪Java代码:实时监控JVM状态.对分秒必争的线上异常,Arthas可帮助我们快速诊断相关问题. 下载安装 ...