简介

poll是linux的事件轮询机制函数,每个进程可以管理一个pollfd队列,由poll函数进行事件注册和查询。

pollfd数据结构:

struct pollfd {int   fd;         /* file descriptor */short events;     /* requested events */short revents;    /* returned events */
};

fd是文件描述符,用来指示linux给当前pollfd分配的文件。编程时需要给events注册我们想要的事件,之后使用poll函数对pollfd队列进行轮询,轮询结束后,revents由内核设置为实际发生的事件。如果fd是负数,那么会忽略events,而且revents会置为0。事件的编码在poll.h头文件中定义了。

poll函数结构:

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

fds是队列的对头指针,nfds是队列的长度,timeout是时间控制机制,超时返回0,计时使用毫秒。

上述所有的参数参考:http://man7.org/linux/man-pages/man2/poll.2.html

代码实例

开发环境: Ubuntu 18.04 LTS
编译器:g++ 7.2

服务器

服务器接收客户端的请求,并向客户端返回客户端发来的消息。

/** 借助于poll实现I/O复用模型,* poll与select最大的区别在于该模型基于事件驱动。*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>#define TRUE 1
#define FALSE 0int main(int argc, char* argv[]) {if (argc != 2) {printf("Usage: %s <port of server>\n", argv[0]);return -1;}int len, rc, on = 1;int listen_sd = -1, new_sd = -1;int end_server = FALSE, compress_array = FALSE;int close_conn;char buffer[80];struct sockaddr_in addr;int timeout;struct pollfd fds[200];  // poll队列int nfds = 1, current_size = 0;int port = atoi(argv[1]);if (port <= 1024) {perror("port error\n");return -1;}bzero(&addr, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = htonl(INADDR_ANY);listen_sd = socket(AF_INET, SOCK_STREAM, 0);if (listen_sd < 0) {perror("socket() error\n");return -1;}rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR,(char*)&on, sizeof(on));if (rc < 0) {perror("setsockopt() failed\n");close(listen_sd);return -1;}rc = ioctl(listen_sd, FIONBIO, (char*)&on);if (rc < 0) {perror("setsockopt() failed\n");close(listen_sd);return -1;}rc = bind(listen_sd, (struct sockaddr*)&addr, sizeof(addr));if (rc < 0) {perror("listen() error\n");close(listen_sd);return -1;}memset(fds, 0, sizeof(fds));fds[0].fd = listen_sd;fds[0].events = POLLIN;timeout = (3 * 60 * 1000);rc = listen(listen_sd, 32);if (rc < 0) {perror("listen() failed\n");close(listen_sd);return -1;}do {printf("Waiting on poll()...\n");rc = poll(fds, nfds, timeout);if (rc < 0) {perror("poll() failed\n");break;}if (rc == 0) {printf("poll timed out. End porgram\n");break;}current_size = nfds;for (int i = 0; i < current_size; ++i) {if (fds[i].revents == 0)  // 没有事件的状态 continue;if (fds[i].revents != POLLIN) {  // 必须是写入事件!printf("Error! revents = %d\n", fds[i].revents);end_server = TRUE;break;}if (fds[i].fd == listen_sd) {  // 监听到新的信号printf("Listening socket is readable\n");do {new_sd = accept(listen_sd, NULL, NULL);if (new_sd < 0) {if (errno != EWOULDBLOCK) {perror("accept() failed\n");end_server = TRUE;}break;}printf("New incomming connection - %d\n", new_sd);fds[nfds].fd = new_sd;fds[nfds].events = POLLIN;nfds++;} while (new_sd != -1);} else {  // 已经建立连接的socket收到消息printf("Descriptor %d is readable\n", fds[i].fd);close_conn = FALSE;do {// 处理接收到客户端的信息,死循环是为了接收完所有可能的数据// 注意这里,recv本身是一个阻塞的函数,所以只要客户端不主动关闭连接,// 那么服务器会一直阻塞在这里,又因为使用了while(TRUE)方式循环接收,// 因此出现了如果使用多个客户端进行连接,只有当前面的关闭连接后,// 后面的才会收到数据。在高性能的服务器编程中,客户端的连接应该使用// 多线程或者多进程的方式处理。如果资源充足,应该给每个客户端一个进程// 或者线程,当然这样可能也会出现资源不足的情况。更好的方式是多线程(进程)结合// 心跳检测机制,把下面的send发送数据替换成心跳函数。如果收不到心跳,// 就认定已经断线,此时把客户端的连接剔除即可。本例子中客户端主动断开// 连接也会被剔除,因为send函数收不到回复了。// 当然,这个例子只是一个示范poll的作用,没有那么复杂。rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);if (rc < 0) {if (errno != EWOULDBLOCK) {perror("recv() failed\n");close_conn = TRUE;}break;}if (rc == 0) {printf("Connection closed\n");close_conn = TRUE;break;}len = rc;printf("%d bytes received\n", len);rc = send(fds[i].fd, buffer, len, 0);if (rc < 0) {perror("send() failed\n");close_conn = TRUE;break;}} while (TRUE);if (close_conn) {close(fds[i].fd);fds[i].fd = -1;compress_array = TRUE;}} }// 压缩poll队列,就是顺序表删除中间节点的方法// 后边的数据依次覆盖前边的,时间复杂度是O(n)if (compress_array) {compress_array = FALSE;for (int i = 0; i < nfds; ++i) {if (fds[i].fd == -1) {for (int j = i; j < nfds; ++j) {fds[i].fd = fds[j + 1].fd;}--i;--nfds;}}}} while(end_server == FALSE);// 清理所有打开的socketfor (int i = 0; i < nfds; ++i) {if (fds[i].fd >= 0) {close(fds[i].fd);}}return 0;
}

客户端

每隔2秒向服务器发送一次信息。

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char* argv[]) {if (argc != 3) {printf("Usage: %s <ip of server> <port of server>\n", argv[0]);return -1;} int port = atoi(argv[2]);if (port < 1024) {perror("port error\n");return -1;}struct sockaddr_in serv_addr;bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(port);if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr.s_addr) < 0) {perror("IP error\n");return -1;}int socketfd = socket(AF_INET, SOCK_STREAM, 0);if (socketfd < 0) {perror("socket() error\n");return -1;}if (connect(socketfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {perror("connect() error\n");return -1;}char buffer[80];memset(buffer, 0, sizeof(buffer));int i = 0;while(1) {int rc = send(socketfd, buffer, sizeof(buffer), 0);if (rc < 0) {perror("send() error\n");return -1;} else if (rc == 0) {printf("send nothing !\n");} else {printf("send successfully!\n");}rc = recv(socketfd, buffer, sizeof(buffer), 0);if (rc < 0) {perror("recv() error\n");return -1;} else if (rc == 0) {printf("receive nothing\n");  } else {printf("receive %dth data: '%s'", i, buffer);}++i;  sleep(2);}return 0;
}

参考资料

  • http://man7.org/linux/man-pages/man2/poll.2.html
  • https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rzab6/poll.htm
  • Linux高性能服务器编程

Linux系统下poll的使用方式相关推荐

  1. Linux系统下select的使用方式

    select连接以及使用方式 select用于监视和操作文件描述符,通过管理进程的fd_set来通知是否可以进行I/O有关的操作. int select(int nfds, fd_set *readf ...

  2. linux的软件包是独立的,Linux系统下软件包的安装

    (以下内容是云课堂Linux课程的笔记,个人纯手工记录,课程以RedHat系列为主) Linux系统下软件包的安装方式包括:源代码安装.本地二进制包安装(rpm命令手工安装).在线二进制包安装(yum ...

  3. linux启用ipmi服务,使用 ipmitool 实现 Linux 系统下对服务器的 ipmi 管理

    简介: IPMI 是一种可扩展的标准,它定义了如何监控硬件和传感器.控制系统部件以及记录重大事件,随着 ipmi 技术在服务器中的应用,利用 ipmi 的众多优势就成为服务器管理特别是集群管理中不可缺 ...

  4. linux中split分割文件打开方式,Linux系统下使用split命令分割大文件 (转载)

    [小蜗牛闲情之作 ] 我想给一个朋友传一个大视频,有几百M,尝试多种传输办法失败后,最后想到的是把视频切开一片片"邮递"过去给他,让它自己组装起来吧. [root@pps publ ...

  5. Linux系统下便捷使用中国知网的方式

    本篇文章讲解在linux平台下合理使用中国知网的方式,包括:论文下载,文件名乱码处理等内容,并提供相应代码. (1)论文下载 背景:由于cnki并未提供linux平台下的caj阅读器,同时使用模拟器运 ...

  6. Linux系统下I/O操作讲解,深入了解实战高级I/O编程

    Linux系统下I/O 一.I/O简介 I/O(输入/输出)是在主存和外部设备(磁盘驱动器.网络.终端)之间复制数据的过程.输入是从外部设备复制到主存,输出是从主存复制到外部设备. 在Linux系统中 ...

  7. linux的翻译系统开发,Linux系统下的翻译神器——Goldendict

    Linux系统下的翻译神器--Goldendict 学习Linux时明显感受到学习英文的重要性.绝大多数Linux的发行版英文版的功能要远强于中文.因此一款好的翻译软件是了解熟悉Linux系统的必需品 ...

  8. 实战证明LINUX系统下密钥对验证的安全性

    实战证明LINUX系统下密钥对验证的安全性  密钥对验证:要求提供匹配的密钥信息才能通过验证,首先在客户端创建一对密钥文件(公钥.私钥),后把公钥文件放到需要远程连接的服务器中.远程登录时,系统将使用 ...

  9. 查看磁盤使用情况linux,在Linux系统下安装Filelight来查看磁盘使用情况

    在 Linux 系统下要查看硬盘的使用情况有非常多的命令可以实现,但是本文给你介绍一款软件:Filelight,开源并免费使用,我们在 Linux 下安装上它用图形化界面来显示磁盘的使用情况,它以彩色 ...

最新文章

  1. QOS的qmtoken 1
  2. BZOJ 3566: [SHOI2014]概率充电器
  3. eclipse无法自动识别出svn项目
  4. PNAS最新研究揭示大脑如何学习语言
  5. python程序题斐波那契数列_Python_经典题_斐波那契数列
  6. mockmvc get请求 tm的 一直404_大家快来看看404的兄弟姐妹
  7. sqliteorm的sync_schema介绍
  8. 写一个方法判断一个整数是否为质数_请写一个函数来检查用户提交的数据是否为整数...
  9. 鸿蒙什么时候出来,鸿蒙系统什么时候发布
  10. 二进制数除法 matlab,MATLAB求出不可约多项式(实现二进制加法、除法)
  11. 实用的汉字拼音转换工具
  12. MATLAB的数据类型
  13. kindle不能接收qq邮箱超大附件
  14. 04_iTween_第三天--相机路径跟随(PutOnPath)
  15. 边缘计算的发展和应用前景
  16. Your task is to Calculate a + b.
  17. 设置Latex页眉页脚边距——fancyhdr的使用
  18. 全国计算机等级考试有java吗,请问全国计算机等级考试有没有二级Java?
  19. 总算有人讲明白了什么是特性阻抗什么是阻抗匹配
  20. Fedora安装字体方法和Ubuntu非常不同!

热门文章

  1. python tkinter背景图片_如何在tkinter中有背景图像和按钮?
  2. STL常用函数总结-map
  3. Python按照索引访问list
  4. ~~堆优化版dijkstra
  5. Android中JNI编程出现“Invalid arguments ' Candidates are: void * malloc(?) '”的解决方案
  6. PyTorch nn.GRU 使用详解
  7. 贪吃蛇大作战游戏攻略
  8. java基于jxl.jar包生成Excel文件到指定目录和服务器下载两种模式的使用方法
  9. LVGL(2)Visual Studio模拟器使用
  10. git remote传到远程