I/O复用:select和poll函数

本文详细介绍了I/O复用的基本概念及其在select和poll函数中的应用,对比了多种I/O模型的特点,并探讨了shutdown函数及拒绝服务型攻击的相关解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

I/O复用:select和poll函数

1. 概述

进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪,它就通知进程。这个能力称为 I/O 复用。

2. I/O模型

  • 阻塞式I/O;
  • 非阻塞式I/O;
  • I/O复用(select和poll);
  • 信号驱动式I/O(SIGIO);
  • 异步I/O 。

2.1 阻塞式I/O模型

默认情况下,所有套接字都是阻塞的。

image

进程调用 recvfrom ,其系统调用直到数据报到达且被复制到应用进程的缓冲区中发生错误才返回。

2.2 非阻塞式I/O模型

进程把一个套接字设置成非阻塞是在通知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。

image

当一个应用进程像这样循环调用 recvfrom 时,我们称之为轮询(polling)

2.3 I/O 复用模型

有了I/O复用,就可以调用select和poll,阻塞在这两个系统调用中的某一个之上,而不是阻塞在真正的I/O系统调用上。

image

使用 select 的优势在于我们可以等待多个描述符就绪。

2.4 信号驱动式I/O模型

用信号,让内核在描述符就绪时发送 SIGIO 信号通知我们。称这种模型为信号驱动式I/O 。

image

2.5 异步 I/O 模型

信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

image

该信号直到数据已复制到应用进程缓冲区才产生,这一点不同于信号驱动式I/O模型。

2.6 各种I/O模型的比较

image

3. select 函数

该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。

调用 select 告知内核对哪些描述符(就读、写或异常条件)感兴趣以及等待多长时间。

#include <sys/select.h>
#include <sys/time.h>

//返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
struct timeval
{
    long tv_sec;    /* seconds */
    long tv_usec;   /* microseconds */
}

timeout参数有三种可能:
1. 永远等待下去:仅在有一个描述符准备好I/O时才返回。置为空指针。
2. 等待一段固定时间:在有一个描述符准备好I/O时返回。
3. 根本不等待:检查描述符后立即返回,称为轮询(polling)。

尽管timeval结构允许我们指定了一个微秒级的分辨率,然而内核支持的真实分辨率往往粗糙的多。

中间的三个参数 readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述符。

目前支持的异常条件只有两个:
1. 某个套接字的带外数据到达。
2. 某个已置为分组模式的伪终端存在可从其主端读取的控制状态信息。

select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符。例如,假设使用32位整数,那么该数组的第一个元素对应于描述符0~31,第二个元素对应于描述符32!63 .

fd_set 的数据类型和以下四个宏:

void FD_ZERO(fd_set *fdset);         /* clear all bits in fdset */

void FD_SET(int fd, fd_set *fdset);  /* turn on the bit for fd in fdset */

void FD_CLR(int fd, fd_set *fdset);  /* trun off the bit for fd in fdset */

int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset? */

select函数的的中间三个参数readset、writeset和exceptset中,如果我们对某一个的条件不感兴趣,就可以把它设为空指针。

maxfdp1参数指定待测试的描述符个数,它的值是待测试的最大描述符加 1 .

例:

fd_set rset;

FD_ZERO(&rset);
FD_SET(1, &rset);
FD_SET(4, &rset);
FD_SET(5, &rset);

maxfdp1值就是 6 .

select函数修改由指针readset、writeset和exceptset所指向的描述符集,因而这三个参数都是值-结果参数。

描述符集内任何与未就绪描述符对应的位返回时均清成0.

3.1 描述符就绪条件

满足下列四个条件中的任何一个时,一个套接字准备好读。
  1. 该套接字接受缓冲区中的数据字节数大于等于套接字接受缓冲区低水位标记的当前大小。
  2. 该连接的读半部关闭。
  3. 该套接字是一个监听套接字且已完成的连接数不为 0 .
  4. 其上有一个套接字错误待处理。
下列四个条件中的任何一个满足时,一个套接字准备好写
  1. 该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小,并且或者该套接字已连接,或者该套接字不需要连接。
  2. 该连接的写半部关闭。
  3. 使用非阻塞式connect的套接字已建立连接,或者connect已经以失败告终。
  4. 其上有一个套接字错误待处理。

4. shutdown 函数

终止网络连接的通常方法是调用close函数。

close函数有两个限制,却可以使用shutdown函数来避免。
1. close把描述符的引用计数减 1 ,仅在该计数变为 0 时才关闭套接字。使用shutdown可以不管引用计数就激发TCP的正常连接终止序列。
2. close终止读和写两个方向的数据传送。

#include <sys/socket.h>

//返回:若成功则为0;若出错则为-1
int shutdown(int sockfd, int howto);

函数的行为依赖于howto的值:
SHUT_RD 关闭连接的读这一半–套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。

SHUT_WR 关闭连接的写这一半–对于TCP套接字,这称为半关闭。

SHUT_RDWR 连接的读半部和写半部都关闭–这与调用shutdown两次等效。

5. 拒绝服务型攻击

当一个服务器在处理多个客户时,它绝对不能阻塞于只与单个客户相关的某个函数调用。否则可能导致服务器被挂起,拒绝为所有其它客户提供服务。这就是拒绝服务型攻击。

可能的解决办法包括:
1. 使用非阻塞式I/O 。
2. 让每个客户由单独的控制线程提供服务。
3. 对I/O操作设置一个超时。

6. pselect 函数

pselect 函数是由POSIX发明的。

#include <sys/select.h>
#include <signal.h>
#include <time.h>

//返回:若有就绪描述符则为其数目,若超时则为 0, 若出错则为 -1
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timespec *timeout, const sigset_t *sigmask);

pselect 相对于select有两个变化:
1. pselect使用timespec结构,而不使用timeval结构。

struct timesepc
{
    time_t tv_spec;   /* seconds */
    long   tv_nsec;   /* nanoseconds */
}

新结构的 tv_nsec 指定纳秒数,而旧结构的改成员tv_usec指定指定微秒数。
2. 增加了第 6 个参数。一个指定信号掩码的指针。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止信号的信号处理函数设置的全局变量,然后调用pselect,告诉它重新设置信号掩码。

7. poll 函数

poll 提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。

#include <poll.h>

//返回:若有就绪描述符则为其数目,若超时则为0,若出错则为 -1
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);

第一个参数是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。

struct pollfd
{
    int fd;         /* descriptor to check */
    short events;   /* events of interest on fd */
    short revents;  /* events that occurred on fd */
}

要测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态,每个描述符都有两个变量,一个为调用值,一个为返回结果。

内容概要:本文针对国内加密货币市场预测研究较少的现状,采用BP神经网络构建了CCi30指数预测模型。研究选取2018年3月1日至2019年3月26日共391天的数据作为样本,通过“试凑法”确定最优隐结点数目,建立三层BP神经网络模型对CCi30指数收盘价进行预测。论文详细介绍了数据预处理、模型构建、训练及评估过程,包括数据归一化、特征工程、模型架构设计(如输入层、隐藏层、输出层)、模型编译与训练、模型评估(如RMSE、MAE计算)以及结果可视化。研究表明,该模型在短期内能较准确地预测指数变化趋势。此外,文章还讨论了隐层节点数的优化方法及其对预测性能的影响,并提出了若干改进建议,如引入更多技术指标、优化模型架构、尝试其他时序模型等。 适合人群:对加密货币市场预测感兴趣的研究人员、投资者及具备一定编程基础的数据分析师。 使用场景及目标:①为加密货币市场投资者提供一种新的预测工具方法;②帮助研究人员理解BP神经网络在时间序列预测中的应用;③为后续研究提供改进方向,如数据增强、模型优化、特征工程等。 其他说明:尽管该模型在短期内表现出良好的预测性能,但仍存在一定局限性,如样本量较小、未考虑外部因素影响等。因此,在实际应用中需谨慎对待模型预测结果,并结合其他分析工具共同决策。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值