LV06-03-网络编程-07-多路复用IO-02-poll

本文主要是网络编程——多路复用IO中的poll的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
--- ---

一、poll相关函数

1. poll()函数

1.1 函数说明

linux下可以使用man 2 poll命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <poll.h>

/* 函数声明 */
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

【函数说明】该函数用于实现I/O多路复用,它与select类似,但是去除了select中文件描述符数量(select一般最大是1024)的限制,并且也取消了select用三个位图描述,而用整体的pollfd结构体指针实现。在poll()函数中,我们需要构造一个struct pollfd类型的数组,每个数组元素指定一个文件描述符以及我们对该文件描述符所关心的条件(数据可读、可写或异常情况)。

【函数参数】

  • fdsstruct pollfd类型的结构体指针变量,我们一般会用它指向一个struct pollfd类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件。
点击查看 struct pollfd 结构体的详细说明

我们在使用man手册的时候,下边会有该结构体的原型:

1
2
3
4
5
6
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

【成员说明】

  • fdint类型,是一个文件描述符,用于表示我们关心的文件描述符。
  • eventsshort类型,位掩码,我们初始化events成员来指定需要为文件描述符fd做检查的事件。
  • reventsshort类型,位掩码,当poll()函数返回时,revents变量由poll()函数内部进行设置,用于说明文件描述符fd发生了哪些事件,这里需要注意,poll()没有更改events变量,我们可以对revents进行检查,判断文件描述符fd发生了什么事件。

我们应将每个数组元素的events成员设置成下表中的一个或几个标志,多个标志通过位或运算符( | )组合起来,通过这些值告诉内核我们关心的是该文件描述符的哪些事件。同样,返回时,revents变量由内核设置为下表所示的一个或几个标志。

分组取值是否可作为events的值是否可作为revents的值说明
第一组POLLIN有数据可以读取
POLLRDNORM有数据可以读取,等同于 POLLIN
POLLRDBAND可以读取优先级数据(Linux上通常不使用)
POLLPRI可读取高优先级数据
POLLRDHUP对端套接字关闭
第二组POLLOUT可写入数据
POLLWRNORM 可写入数据,等同于 POLLOUT
POLLWRBAND 优先级数据可写入
第三组POLLERR有错误发生
POLLHUP发生挂起
POLLNVAL文件描述符未打开

其中,第一组标志与数据可读相关;第二组标志与可写数据相关;第三组标志是设定在revents变量中用来返回有关文件描述符的附加信息,如果在events变量中指定了这三个标志,则会被忽略,相当于没有设置。

【注意】如果我们对某个文件描述符上的事件不感兴趣,则可将events变量设置为0;另外,将fd变量设置为文件描述符的负值(取文件描述符fd的相反数-fd),将导致对应的events变量被poll()忽略,并且revents变量将总是返回0,这两种方法都可用来关闭对某个文件描述符的检查。

  • nfdsnfds_t类型,指定了fds数组中的元素个数(我的理解就是我们定义的数组中实际可以使用的元素的个数),数据类型nfds_t实际为无符号整形。
  • timeoutint类型,用于决定poll()函数的阻塞行为,单位为ms
点击查看 timeout 用法
timeout = -1 poll() 会一直阻塞(与 select() 函数的 timeout 等于 NULL 相同),直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号时返回。
timeout = 0 poll() 不会阻塞,只是执行一次检查看看哪个文件描述符处于就绪态。
timeout >0表示设置 poll() 函数阻塞时间的上限值,意味着 poll() 函数最多阻塞 timeout 毫秒,直到 fds 数组中列出的文件描述符有一个达到就绪态或者捕获到一个信号为止。

【返回值】int类型,返回值与select()类似,有以下几种情况:

  • 返回-1:表示有错误发生,并且会设置errno表示错误类型。
  • 返回0:表示该调用在任意一个文件描述符成为就绪态之前就超时了。
  • 返回一个正整数:表示有一个或多个文件描述符处于就绪态,这个时候的返回值具体表示fds数组中返回的revents变量不为0struct pollfd对象的数量。

【使用格式】后边直接看实例就好。

【注意事项】

(1)每当调用poll()函数之后,系统不会清空这个struct pollfd类型数组。

1.2 使用实例

暂无

2. ppoll()函数

2.1 函数说明

linux下可以使用man 2 ppoll命令查看该函数的帮助手册。

1
2
3
4
5
/* 需包含的头文件 */
#include <poll.h>

/* 函数声明 */
int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask);

【函数说明】该函数用于实现I/O多路复用,该函数是一个 防止信号干扰的增强型 poll()函数。

【函数参数】

  • fdsstruct pollfd类型的结构体指针变量,该参数与poll()一样,我们一般会用它指向一个struct pollfd类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件。
点击查看 struct pollfd 结构体的详细说明

我们在使用man手册的时候,下边会有该结构体的原型:

1
2
3
4
5
6
struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

【成员说明】

  • fdint类型,是一个文件描述符,用于表示我们关心的文件描述符。
  • eventsshort类型,位掩码,我们初始化events成员来指定需要为文件描述符fd做检查的事件。
  • reventsshort类型,位掩码,当poll()函数返回时,revents变量由poll()函数内部进行设置,用于说明文件描述符fd发生了哪些事件,这里需要注意,poll()没有更改events变量,我们可以对revents进行检查,判断文件描述符fd发生了什么事件。
  • nfdsnfds_t类型,该参数与poll()一样,指定了fds数组中的元素个数(我的理解就是我们定义的数组中实际可以使用的元素的个数),数据类型nfds_t实际为无符号整形。
  • tmo_pstruct timespec类型,用于决定ppoll()函数的阻塞行为,如果设置为NULL,那么ppoll()函数将会一直阻塞。
点击查看 struct timespec 说明

在使用man手册的时候,会有该结构体的说明,该结构体的成员如下:

1
2
3
4
5
struct timespec
{
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
  • sigmasksigset_t类型指针变量,表示信号掩码,指定一个需要屏蔽的信号集。

【返回值】int类型,返回值与poll()一样,有以下几种情况:

  • 返回-1:表示有错误发生,并且会设置errno表示错误类型。
  • 返回0:表示该调用在任意一个文件描述符成为就绪态之前就超时了。
  • 返回一个正整数:表示有一个或多个文件描述符处于就绪态,这个时候的返回值具体表示fds数组中返回的revents变量不为0struct pollfd对象的数量。

【使用格式】none

【注意事项】none

2.2 使用实例

暂无

二、poll()使用实例

这里我们就不使用服务器和客户端的例子了(主要是自己觉得客户端关闭的时候,服务器端处理较为麻烦,后边暂时还有更重要的东西要学习,所以这里先写一个其他的实例,后边用到了再补充吧),我们来尝试一种新的方式:读取鼠标和键盘,键盘的话是标准输入,文件描述符为0,那鼠标呢?它其实也是一种输入设备,它对应的设备文件在/dev/intput目录下,我们输入以下命令,查看我当前使用的Ubuntu下边的输入设备:

1
ls /dev/input

然后我们会看到如下信息输出:

image-20220703171421815

这里有很多个文件,我们怎么知道哪个是鼠标呢?通常情况下是mouseXX表示序号012),但也不一定,也有可能是eventX,如何确定到底是哪个设备文件,可以通过对设备文件进行读取来判断,例如od命令:

1
sudo od -x /dev/input/event2

注意,这里需要添加sudo,在Ubuntu系统下,普通用户是无法对设备文件进行读取或写入操作。当执行命令之后,移动鼠标或按下鼠标、松开鼠标都会在终端打印出相应的数据的话,这个便是代表鼠标的设备文件啦:

image-20220703171650804

接下来我们就开始写我们的poll()函数实例,使用poll()来检测键盘和鼠标的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>

#define MOUSE "/dev/input/event2"

int main(void)
{
char buf[100];
int fd = -1;
int ret = 0;
int flag;
struct pollfd fds[2];
/* 打开鼠标设备文件 */
fd = open(MOUSE, O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("open error");
exit(-1);
}
/* 将键盘设置为非阻塞方式 */
flag = fcntl(0, F_GETFL); /* 先获取原来的flag */
flag |= O_NONBLOCK; /* 将O_NONBLOCK标准添加到flag */
fcntl(0, F_SETFL, flag); /* 重新设置flag */
/* 同时读取键盘和鼠标 */
fds[0].fd = 0;
fds[0].events = POLLIN; /* 只关心数据可读 */
fds[0].revents = 0;
fds[1].fd = fd;
fds[1].events = POLLIN; /* 只关心数据可读 */
fds[1].revents = 0;
while(1)
{
ret = poll(fds, 2, -1);
if (ret < 0)
{
perror("poll error");
break;
}
else if (ret == 0)
{
fprintf(stderr, "poll timeout!\n");
continue;
}
/* 检查键盘是否为就绪态 */
if(fds[0].revents & POLLIN)
{
ret = read(0, buf, sizeof(buf));
if (ret > 0)
printf("keyboard: read<%d>Bytes data!\n", ret);
}
/* 检查鼠标是否为就绪态 */
if(fds[1].revents & POLLIN)
{
ret = read(fd, buf, sizeof(buf));
if (ret > 0)
printf("mouse: read<%d>Bytes data!\n", ret);
}
}
close(fd);
return 0;
}

然后我们执行以下命令,编译和运行程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
sudo ./a.out # 执行可执行程序

由于这里用到了鼠标的设备文件,所以这里需要加上sudo然后,终端会有以下信息显示:

image-20220703172628646