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 | /* 需包含的头文件 */ |
【函数说明】该函数用于实现I/O
多路复用,它与select
类似,但是去除了select
中文件描述符数量(select
一般最大是1024
)的限制,并且也取消了select
用三个位图描述,而用整体的pollfd
结构体指针实现。在poll()
函数中,我们需要构造一个struct pollfd
类型的数组,每个数组元素指定一个文件描述符以及我们对该文件描述符所关心的条件(数据可读、可写或异常情况)。
【函数参数】
fds
:struct pollfd
类型的结构体指针变量,我们一般会用它指向一个struct pollfd
类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件。
点击查看 struct pollfd 结构体的详细说明
我们在使用man
手册的时候,下边会有该结构体的原型:
1 | struct pollfd |
【成员说明】
fd
:int
类型,是一个文件描述符,用于表示我们关心的文件描述符。events
:short
类型,位掩码,我们初始化events
成员来指定需要为文件描述符fd
做检查的事件。revents
:short
类型,位掩码,当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
,这两种方法都可用来关闭对某个文件描述符的检查。
nfds
:nfds_t
类型,指定了fds
数组中的元素个数(我的理解就是我们定义的数组中实际可以使用的元素的个数),数据类型nfds_t
实际为无符号整形。timeout
:int
类型,用于决定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
变量不为0
的struct pollfd
对象的数量。
【使用格式】后边直接看实例就好。
【注意事项】
(1)每当调用poll()
函数之后,系统不会清空这个struct pollfd
类型数组。
1.2 使用实例
暂无
2. ppoll()
函数
2.1 函数说明
在linux
下可以使用man 2 ppoll
命令查看该函数的帮助手册。
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于实现I/O
多路复用,该函数是一个 防止信号干扰的增强型 poll()
函数。
【函数参数】
fds
:struct pollfd
类型的结构体指针变量,该参数与poll()
一样,我们一般会用它指向一个struct pollfd
类型的数组,数组中的每个元素都会指定一个文件描述符以及我们对该文件描述符所关心的条件。
点击查看 struct pollfd 结构体的详细说明
我们在使用man
手册的时候,下边会有该结构体的原型:
1 | struct pollfd |
【成员说明】
fd
:int
类型,是一个文件描述符,用于表示我们关心的文件描述符。events
:short
类型,位掩码,我们初始化events
成员来指定需要为文件描述符fd
做检查的事件。revents
:short
类型,位掩码,当poll()
函数返回时,revents
变量由poll()
函数内部进行设置,用于说明文件描述符fd
发生了哪些事件,这里需要注意,poll()
没有更改events
变量,我们可以对revents
进行检查,判断文件描述符fd
发生了什么事件。
nfds
:nfds_t
类型,该参数与poll()
一样,指定了fds
数组中的元素个数(我的理解就是我们定义的数组中实际可以使用的元素的个数),数据类型nfds_t
实际为无符号整形。tmo_p
:struct timespec
类型,用于决定ppoll()
函数的阻塞行为,如果设置为NULL
,那么ppoll()
函数将会一直阻塞。
点击查看 struct timespec 说明
在使用man
手册的时候,会有该结构体的说明,该结构体的成员如下:
1 | struct timespec |
sigmask
:sigset_t
类型指针变量,表示信号掩码,指定一个需要屏蔽的信号集。
【返回值】int
类型,返回值与poll()
一样,有以下几种情况:
- 返回
-1
:表示有错误发生,并且会设置errno
表示错误类型。 - 返回
0
:表示该调用在任意一个文件描述符成为就绪态之前就超时了。 - 返回一个正整数:表示有一个或多个文件描述符处于就绪态,这个时候的返回值具体表示
fds
数组中返回的revents
变量不为0
的struct pollfd
对象的数量。
【使用格式】none
【注意事项】none
2.2 使用实例
暂无
二、poll()
使用实例
这里我们就不使用服务器和客户端的例子了(主要是自己觉得客户端关闭的时候,服务器端处理较为麻烦,后边暂时还有更重要的东西要学习,所以这里先写一个其他的实例,后边用到了再补充吧),我们来尝试一种新的方式:读取鼠标和键盘,键盘的话是标准输入,文件描述符为0
,那鼠标呢?它其实也是一种输入设备,它对应的设备文件在/dev/intput
目录下,我们输入以下命令,查看我当前使用的Ubuntu
下边的输入设备:
1 | ls /dev/input |
然后我们会看到如下信息输出:
这里有很多个文件,我们怎么知道哪个是鼠标呢?通常情况下是mouseX
(X
表示序号0
、1
、2
),但也不一定,也有可能是eventX
,如何确定到底是哪个设备文件,可以通过对设备文件进行读取来判断,例如od
命令:
1 | sudo od -x /dev/input/event2 |
注意,这里需要添加sudo
,在Ubuntu
系统下,普通用户是无法对设备文件进行读取或写入操作。当执行命令之后,移动鼠标或按下鼠标、松开鼠标都会在终端打印出相应的数据的话,这个便是代表鼠标的设备文件啦:
接下来我们就开始写我们的poll()
函数实例,使用poll()
来检测键盘和鼠标的数据:
1 |
|
然后我们执行以下命令,编译和运行程序:
1 | gcc test.c -Wall # 生成可执行文件 a.out |
由于这里用到了鼠标的设备文件,所以这里需要加上sudo
然后,终端会有以下信息显示: