Linux学习——IO多路复用知识

1.IO模型

发现下面这个博主的内容写得很好,可以看看连接如下

【Linux系统编程】------深度理解5种IO模型_直接io-CSDN博客

在unix/linu下主要有四种I/O模式:

**阻塞I/O:**最常用

大部分程序使用的都是阻塞模式的I/O

缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式

读操作:read,recv,recvfrom

写操作:write,send

其他操作:accept,connect

以read函数为例

进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没有数据可读,函数

read将发送阻塞

它会一直阻塞下去,等待套接字的接收缓冲区中有数据可读

经过一段时间后,缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据

如果在进程阻塞过程中,对方发生故障,那这个进程将永远阻塞下去。
UDP不用等待,没有实际的发送缓冲区 ,所以UDP协议中不存在发送缓冲区满的情况,在

UDP套接字上执行的写操作永远都不会阻塞。

写阻塞

在写操作时发送阻塞的情况要比读操作少。主要发生在要写入的缓冲区的大小小于要写入的数

据量的情况下。

这时,写操作不进行任何拷贝工作,将发送阻塞。

一但发送缓冲区内有足够多的空间,内核将唤醒进程,将数据从用户缓冲区中拷贝到相应的发

送数据缓冲区。

UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在发送缓冲区满的情况,

在UDP套接字上执行的写操作永远都不会阻塞,sendto。

非阻塞I/O:可防止进程阻塞在I/O操作上,需要轮询

当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核"当我请求的I/O操作不能够马上完

成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。"

当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停的测试是否一个文件描述 符有数据可读(polling)。

应用程序不停的polling内核来检查是否I/O操作已经就绪,这将是一个极浪费CPU资源的操作。

这种模式使用中不普遍。

1.fcntl()函数

当你一开始建立一个套接字描述符的时候,系统内核 将其设置为阻塞IO模式。 可以使用函数fcntl()

设置一个套接字的标志位为O_NOBLOCK来实现非阻塞。

复制代码
int fcntl(int fd, int cmd, long arg)
int flag;
flag = fcntl(sockfd,F_GETFL,0)
flag |= O_NOBLOCK;
fcntl(sockfd,F_SETFL,flag);

2.ioctl()函数

复制代码
int b_on = 1;
ioctl(sock_fd, FIONBIO,&b_on);

I/O多路复用:允许同时对多个I/O进行控制

基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符, 文件描述符的特点:

1.非负整数

2.从最小可用的数字来分配

3.每个进程启动时默认打开0、1、2三个文件描述符

多路复用针对不止套接字fd,也针对普通的文件描述符fd。

应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;

若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;

若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;

比较好的方法是使用IO多路复用,其基本思想是:

先构造一张又关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回。

函数返回时告诉进程那个描述符已就绪,可以进行IO操作

步骤:

复制代码
1.fd_set
    void FD_zero(fd_set* fdset) //对集合清零
    void FD_set(int fd, fd_set * fdset) //把fd加入集合
    void FD_CLR(int fd,fd_set *fdset) //从集合中清除fd
    void FD_ISSET(int fd,fd_set *fdset) //判断fd是否在fd_set中

int select(int nfds, fd_set *readfds, fd_set *writefds, 
           fd_set *exceptfds, struct timeval *timeout);

nfds: 这个参数是监控的文件描述符集合中最大文件描述符的值加1。在使用select函数时,必须确保这个参数正确设置,以便函数能监视所有相关的文件描述符。

readfds, writefds, exceptfds: 这三个参数分别代表读、写和异常监视的文件描述符集合。
它们使用fd_set类型表示,这是一种通过位图来管理文件描述符的数据结构。
以下是对fd_set操作的常用宏定义:
    FD_SET(fd, &set): 将文件描述符fd添加到集合set中。
    FD_CLR(fd, &set): 从集合set中移除文件描述符fd。
    FD_ISSET(fd, &set): 检查文件描述符fd是否已被加入集合set。
    FD_ZERO(&set): 清空集合set中的所有文件描述符。

timeout: 这是一个指向timeval结构的指针,该结构用于设定select等待I/O事件的超时时间。
结构定义如下:
    struct timeval {
            long tv_sec;  // seconds
            long tv_usec; // microseconds
            };

timeout的设定有三种情况:
    当timeout为NULL时,select会无限等待,直到至少有一个文件描述符就绪。
    当timeout设置为0时(即tv_sec和tv_usec都为0),select会立即返回,用于轮询。
    设置具体的时间,select将等待直到该时间过去或者有文件描述符就绪。

返回值与错误处理

select函数的返回值有三种可能:
    大于0:表示就绪的文件描述符数量,即有多少文件描述符已经准备好进行I/O操作。
    等于0:表示超时,没有文件描述符在指定时间内就绪。
    小于0:发生错误。错误发生时,应使用perror或strerror函数来获取具体的错误信息。
 
2.select()
int select(int maxfdp, //maxfd+1
           fd_set *readfds, //读集合
           fd_set *writefds, //写集合
           fd_set *errorfds, //异常集合,正常写NULL
           struct timeval *timeout //超时
          );
一般:填写集合,写集合填空,异常集合(带位数据),
超时:

struct timeval{
long tv_sec; //秒
long tv_usec; //微妙
};

select退出后:集合表示有数据的集合
if(FD_ISSET(fd,set))
{
//1.如果监听套接字有数据,新的客户端进行连接,则accept
//2.若建立连接的套接字有数据,则去读read
}

编程步骤

1.把关心的文件描述符放入到集合中fd_set

2.调用select()/poll函数去监控集合fd_set中哪些文件描述符 发生了堵塞等待集合中一个或多个文件描述符有数据

3.有数据时,退出select()阻塞

4.依次判断哪个文件描述符有数据

5.依次处理有数据的文件描述符上的数据

复制代码
int main(void)
{
    struct timeval tout;
    fd_set rset;
    int maxfd = ‐1;
    fd = socket(...);
    bind(fd,...);
    listen(fd,...);
    while(1)
    {
        maxfd = fd;
        FD_ZERO(&rset);
        FD_SET(&rset); //依次把已经建立好连接fd加入到集合中,记录最大的文件描述符
        tout.tv_sec = 5;
        tout.tv_user = 0;
        select(maxfd+1,&rset,NULL,NULL,&tout); //阻塞
        if(FD_ISSET(fd,&rset)) //有1个或者多个文件描述符有数据
        {
            newfd = accept(fd,...);
            //依次判断已建立连接的客户端是否有数据
            //xxx
        }
    }
}

select()里面的各个文件描述符fd_set集合的参数在select()前后发生了变化

在select前:表示关心的文件描述符集合

在select后:有数据的集合(如不是在超时还回情况下)

kernel对集合进行了改变

信号驱动I/O:一种异步通信模型

SIGIO

相关推荐
tan180°1 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
典学长编程1 小时前
Linux操作系统从入门到精通!第二天(命令行)
linux·运维·chrome
wuk9982 小时前
基于MATLAB编制的锂离子电池伪二维模型
linux·windows·github
好好研究2 小时前
学习栈和队列的插入和删除操作
数据结构·学习
新中地GIS开发老师3 小时前
新发布:26考研院校和专业大纲
学习·考研·arcgis·大学生·遥感·gis开发·地理信息科学
SH11HF4 小时前
小菜狗的云计算之旅,学习了解rsync+sersync实现数据实时同步(详细操作步骤)
学习·云计算
Frank学习路上4 小时前
【IOS】XCode创建firstapp并运行(成为IOS开发者)
开发语言·学习·ios·cocoa·xcode
snoopyfly~4 小时前
Ubuntu 24.04 LTS 服务器配置:安装 JDK、Nginx、Redis。
java·服务器·ubuntu
独行soc4 小时前
#渗透测试#批量漏洞挖掘#HSC Mailinspector 任意文件读取漏洞(CVE-2024-34470)
linux·科技·安全·网络安全·面试·渗透测试
BD_Marathon5 小时前
Ubuntu下Tomcat的配置
linux·ubuntu·tomcat