当前位置: 首页 > java >正文

Linux高级IO

Linux高级IO

  • 1、理解IO
  • 2、五种IO模型
  • 3、非阻塞IO
    • 3.1、fcntl
    • 3.2、测试阻塞IO与非阻塞IO
  • 4、I/O多路转接之select
    • 4.1、select函数接口
    • 4.2、基于select的EchoServer
    • 4.3、完整代码
    • 4.4、select的特点和缺点
  • 5、I/O多路转接之poll
    • 5.1、poll函数接口
    • 5.2、基于poll的EchoServer
    • 5.3、poll的缺点
  • 6、I/O多路转接之epoll
    • 6.1、epoll函数接口
    • 6.2、epoll的原理
    • 6.3、基于epoll的EchoServer
    • 6.4、epoll的工作模式
  • 7、Reactor模式
  • 8、多执行流Reactor模式
    • 8.1、多进程Reactor模式
    • 8.2、多线程Reactor模式
    • 8.3、协程

1、理解IO

关于IO,我们调用read、write,有数据就能直接读取上来,没有数据就得等,这就是阻塞式的IO。
网络通信的本质就是进程间通信,进程间通信就是IO,I:INPUT,O:OUTPUT。
当我们收到数据是网卡先收到的,网卡会给CPU针脚发送硬件中断,操作系统会根据中断号去中断向量表找到对应方法并执行,将数据从网卡拷贝到操作系统中,操作系统再不断向上解包分用,最后拷贝到TCP的接收缓冲区,上层用户读取,由于TCP是面向字节流的,所以上层用户还要解决数据包的粘包问题。I:就是网卡收到数据,然后拷贝到操作系统内,再拷贝到TCP的接收缓冲区,用户上层调用read/recv读取。O:上层用户将数据拷贝到TCP的发送缓冲区,向下封装通过网卡发送到网络中。站在进程的角度,I就是将数据读取上来交给进程,O就是进程将数据交给网卡发送。
进程如何IO呢?——通过read、write、recv、send系统调用。

如何更好的理解IO,什么叫作高效的IO?
当服务器进程启动了,并不是就直接进行IO了,服务器大部分时间都是在等,比如客户端服务器链接已经建立好了,客户端给服务器发送数据,但是数据在网络中转发可能需要花很长时间,所以接收方就得等。甚至发送方发送缓冲区没有数据,发送方不发送数据,服务端调用recv/read就得一直等。所以在网络IO中,大部分时间都是在等待的。当等了一端时间,将数据从你的发送缓冲区拷贝到我的接收缓冲区,这时候就会将我的接收缓冲区中的数据拷贝到用户空间,这个拷贝才是真正意义上的IO。
所以,IO = 等 + 拷贝

等<->不用等,从等到不用等或者从不用等到要等,涉及了条件变化。
比如你要发送数据,如果发送缓冲区满了,那么你就要等,等到发送缓冲区有空间了,才能将数据拷贝到发送缓冲区,这是从等到不用等的过程。再比如你要读取数据,如果接收缓冲区没有数据你就要等,当接受缓冲区有数据了,这时候就可以将接收缓冲区的数据拷贝到用户空间,这也是从等到不用等的过程。而当你把发送缓冲区写满了,这时候你就不能再写入了,这时候就需要等,这是从不用等到等的过程。
因此在IO中涉及条件变化,比如read读取,刚开始接收缓冲区没有数据,要等到接收缓冲区有数据了才能读取,这就是条件发生了变化。这时候就是从等->不用等。而我们把IO中条件变化称为IO事件。

IO = 等 + 拷贝,其中等是主要矛盾,我们大部分时间都是在等。那么什么叫做高效的IO呢?
首先我们要知道,任何通信场景,IO效率一定是有上限的。就比如一个花盆里的小树苗是不可能长成参天大树的。IO的上限受到了硬件的上限。
所以高效的IO就是:IO = 拷贝。这种情况带来的结果就是充分利用硬件资源。所以我们要提高IO效率,就是要降低IO中等的比重。
高效的IO:单位时间内,等的比重越低,IO效率就越高。


2、五种IO模型

下面讲几个故事,以钓鱼为例,我们假设钓鱼的前置所有工作都做好了,现在已经坐在岸边了,那么这时候钓鱼就分为两步,钓鱼 = 等 + 钓。钓鱼的主要矛盾就是等,要高效的钓鱼就是减少等的比重。

张三:张三是村里的年轻人,属于一个新入坑钓鱼的,今天张三过来村里的河边把前置工作都做好了,鱼钩挂上鱼饵,然后就把鱼钩扔到河里,但是张三钓鱼有个特点,钓鱼的时候全身心投入,眼睛死死的盯着鱼漂,谁叫他他都不答应,鱼漂不动他也不动,当鱼漂动了,他就把鱼竿拉上来,成功钓上来一条鱼。

李四:大概过了一个多小时,又来一个钓友叫做李四,李四已经钓了一两年了,是一个有一点经验的钓友了。李四也带上和张三相同的七七八八的装备,李四看到张三就跟张三说话,但是张三理都不理,李四自讨没趣,但是张三和李四是认识的,所以李四就坐在张三旁边,把鱼钩扔到河里开始钓鱼。李四钓鱼也有特点,李四检测一下鱼漂,发现鱼漂没有什么大动作很平稳,李四就转过头继续跟张三说话,但是张三还是不理他,李四就掏出手机刷了会抖音,玩了一会又觉得没意思继续掏出报纸来看,过了一会儿又觉得没意思就瞥了一眼鱼漂,发现还是没有鱼咬钩,然后重复上述动作。当发现鱼咬钩了,李四马上站起来收杆,李四也钓上来一条鱼。

王五:中午王五吃完饭也带着自己的装备来了,王五是很有经验的钓鱼佬,瞥了一眼这两人,很不屑,两个新手,一个一动不动,一个一直在动。王五也做好准备工作,然后从口袋里摸出来一个铃铛,把铃铛挂在了鱼竿的顶部,把鱼钩扔到河里,然后鱼竿插在岸边。从此之后王五头也不抬,一直在玩手机,过了一会王五突然听到铃铛响了,王五继续头也不抬拉起鱼竿,钓上了一条鱼。然后重复上述动作。

赵六:过了一会又来了个赵六,赵六是村里的首富,赵六也是一个资深的钓鱼佬,赵六开着一辆三轮车过来,拉来了一车鱼竿(100根鱼竿),把这一百根鱼竿全部挂上鱼饵,将鱼钩都扔到河里然后插在岸边。然后赵六就来回踱步,从左向右,从右向左,不断检测是否有鱼咬钩了。当有鱼咬钩了,赵六直接就把那个鱼竿抬起来,成功钓上来一条鱼,然后将鱼竿恢复继续插在岸边,重复来回检测。

田七:后来来了一家公司的上市老总田七,田七也是从这个村里出来的,之前也是经常钓鱼,田七是方圆五百公里的首富。田七坐在车后排,司机带着他刚好路过河边,田七看到这四个货在这钓鱼,田七说我也想钓鱼。但是田七在心里盘算着,我不是喜欢钓鱼,我是喜欢吃鱼。田七就给司机小王说,小说把车停路边,我下去钓会鱼。可是当车停下来,田七刚下车,突然打来一个电话,公司要破产了,有一个特别紧急的会议,田七你赶紧过来参加会议。田七这时候就想,我还是得去处理我的事情,但是我也挺想吃鱼的,所以田七从车后备箱拿出一堆渔具给司机小王,特别是还拿了一个水桶和电话。告诉司机小王说,小王我想吃鱼,但是公司有紧急的事情,你去帮我钓鱼,当你把水桶钓满了,你打电话给我,我开车来接你。说完田七开着车扬长而去忙他的事情了,小王就去钓鱼了。当小王在钓鱼的时候田七就在公司里面开会,两个人各忙各的。

我们在进行IO都是通过文件描述符进行IO的,张三李四王五等人都有自己的鱼竿,鱼竿就是对应的文件描述符。张三李四王五等人本质就是一个进程。钓鱼就是系统调用/函数调用。
1、张三这种我们称之为阻塞IO。
2、李四检测没有鱼咬钩不会卡住,而是会做自己的事情,我们称之为非阻塞IO。但是检测一次没有就绪直接返回这是1次,而李四会不断重复上述过程,因此是轮询,李四就是非阻塞IO + 轮询。
3、王五在钓鱼头也不抬,但是王五在钓鱼之前他知道铃铛响了自己该做什么,甚至还没有钓鱼,在家里的时候王五就知道铃铛响了该做什么,王五不对鱼漂是否动做检测,只有当铃铛响了才会做对应动作,这种我们称为信号驱动式IO。
4、赵六一次可以等待多个文件描述符的方式我们称为IO多路转接或IO多路复用。
5、田七并没有钓鱼,田七只是发起了钓鱼,让别人给他钓。田七给别人缓冲区(水桶),通知方式(电话)。当缓冲区写满了,通过一定的方式来通知田七。田七这种钓鱼方式我们称为异步IO。

问题1:阻塞 VS 非阻塞
阻塞与非阻塞有什么区别?IO = 等 + 拷贝,它们拷贝的动作都是一样的,本质区别在于等的方式不同。阻塞是一直在等,非阻塞是不会卡住等待,非阻塞如果底层没有数据就直接返回了。
非阻塞IO效率高?------>从IO的效率上讲,阻塞与非阻塞是没有本质区别的。当你是一条河里的鱼,抬头一开有两个鱼钩,一个是张三的,一个是李四的,你会随机去咬其中一个。所以IO的效率是取决于对方的,服务方为什么在等?是发送方导致的。张三和李四都只有一个鱼竿,鱼咬钩的概率都是一样的,所以他们两个没有本质区别。
但是为什么说非阻塞IO效率高?因为李四可以在同样的时间内做更多的其他事情。其他事情也可能包含其他的IO。

问题2:五种IO模型中,谁的IO效率是最高的?
赵六是最高的,IO效率可能受自己的影响,但更多的是受对方的影响。今天你是河里的一条鱼,抬头发现张三的一个鱼钩,李四的一个鱼钩,王五的一个鱼钩,田七的一个鱼钩,还有赵六的100个鱼钩,一共有104条虫子,赵六鱼竿被咬钩的概率是要比其他四个人大得多的。鱼咬钩对于其他人的概率是1/104,而对于赵六是100/104。赵六任意一个fd,数据就绪的概率很高。而IO = 等 + 拷贝,所以等的比重就降低了,更多的时间会在拷贝数据上面。本质上就是可以检测多个文件描述符,这种技术就叫做多路转接。

前面四种IO我们称为同步IO,最后一种是异步IO。

问题3:信号驱动式IO的特点和效率?有没有参与IO呢?
信号驱动式IO最大的特点就是逆转了获取就绪事件的方式。IO = 等 + 拷贝,只是等的方式变了,拷贝还是需要自己参与。所以张三、李四、王五在IO这件事上面,只有等的方式不同,但是还是要自己拷贝。所以还是要深度参与钓鱼。但是信号不是异步的吗?为什么信号驱动式IO是同步IO呢?因为信号驱动式IO还是要参与IO的过程,因为要拷贝数据,所以属于同步IO范畴。

只要参与了IO的过程,就是同步IO。
那效率问题呢,王五和张三李四本质上没有区别,都是只有一个鱼竿,而且鱼咬钩的概率都是一样的。只不过王五和李四类似,可以在等期间做其他事情。

问题4:同步IO VS 异步IO
同步IO与线程那里的同步没有任何关系。田七没有等也没有钓,田七只是发起了钓鱼,所以田七是异步IO。
同步和异步的本质区别是:有没有参与IO的具体过程,参与了(局部参与也算)就是同步IO,没有参与就是异步IO。
异步IO通常扮演的是IO任务的发起者,而小王就是操作系统。


阻塞IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞方式。
阻塞IO是最常见的IO模型。

在这里插入图片描述


非阻塞IO:如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对CPU来说是较大的浪费,一般只有特定场景下才使用。

在这里插入图片描述


信号驱动IO:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
在这里插入图片描述


IO多路转接:虽然从流程图上看起来和阻塞IO类似。实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。
在这里插入图片描述


异步IO:由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
在这里插入图片描述

任何IO过程中,都包含两个步骤。第一是等待,第二是拷贝。而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间。让IO更高效,最核心的办法就是让等待的时间尽量少。


3、非阻塞IO

3.1、fcntl

其实系统调用提供了可以让我们进行非阻塞IO的方式,比如open函数的flags参数可以进行设置。有很多方式,但是这些方式都五花八门,记忆成本太高了,我们访问资源就是关注文件描述符,只要将文件描述符设置为非阻塞,就可以以统一的方式进行非阻塞IO。
在这里插入图片描述
在这里插入图片描述
系统调用fcntl(f control),是用来设置文件描述符标记位的函数。内核中struct file对象就包含了flags和mode,fcntl就是用来修改struct file对象中的flags和mode字段的。第一个参数fd表示文件描述符,第二个参数cmd表示功能,后面可变参数就是将来要设置传入的值。
fcntl 函数有 5 种功能:
• 复制一个现有的描述符(cmd=F_DUPFD)。
• 获得/设置文件描述符标记(cmd=F_GETFD 或 F_SETFD)。
• 获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL)。
• 获得/设置异步 I/O 所有权(cmd=F_GETOWN 或 F_SETOWN)。
• 获得/设置记录锁(cmd=F_GETLK,F_SETLK 或 F_SETLKW)。

我们只关注获取/设置文件状态标记:F_GETFL和F_SETFL。

在这里插入图片描述
先调用fcntl获取文件描述符对应文件的状态标记,然后再设置原来老的加上O_NONBLOCK非阻塞。


3.2、测试阻塞IO与非阻塞IO

首先来看阻塞IO:

#include <iostream>
#include <string>
#include <unistd.h>int main()
{std::string tips = "Please Enter# ";while (true){write(1, tips.c_str(), tips.size());char buffer[1024];int n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0){std::cout << "read file end!" << std::endl;break;}else {std::cout << "read error, n: " << n << std::endl;break;}}return 0;
}

在这里插入图片描述
我们让read从标准输入中读取数据,如果键盘不输入数据,那么read就会阻塞住一直等,当我们输入数据read返回然后输出buffer的内容。最后我们按下ctrl d,在Linux中ctrl d表示输入结束,所以read返回值为0表示读到文件结尾。

下面我们设置文件描述符0为非阻塞,再次进行测试:

#include <iostream>
#include <string>
#include <unistd.h>
#include <fcntl.h>void SetNonBlock(int fd)
{int fl = fcntl(fd, F_GETFL);if (fl < 0){perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int main()
{SetNonBlock(0);std::string tips = "Please Enter# ";while (true){write(1, tips.c_str(), tips.size());char buffer[1024];int n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0){std::cout << "read file end!" << std::endl;break;}else {std::cout << "read error, n: " << n << std::endl;}sleep(1);}return 0;
}

在这里插入图片描述
设置为非阻塞后,我们读取的时候直接以出错的方式返回了,返回值为-1。当我们输入数据按下回车后就可以读取到数据并输出。
O_NONBLOCK:让该fd以非阻塞方式工作。非阻塞,我们如果不输入,数据不就绪,以出错的方式返回。
但是read也有读取失败的情况,读取失败也是返回-1。读取不就绪不算失败。但是读取不就绪和失败都是返回-1,怎么区分呢?因为失败和底层数据不就绪对于我们后续的处理动作是不同的!

当读取失败,错误码表示了更详细的出错原因。errno表示最近一次系统调用的出错码。
下面我们把出错码打印出来看看:
在这里插入图片描述
在这里插入图片描述
所以当read出错返回,我们需要对错误码进行判断,如果错误码是EAGAIN,EAGAIN本质上是一个值为11的宏,或者错误码是EWOULDBLOCK,EWOULDBLOCK就是EAGAIN,就代表底层数据不就绪。
在这里插入图片描述

当我们调用系统调用read读取数据时,默认就是阻塞状态。如果这时候进程正在阻塞等待底层数据就绪,收到了信号,可能就会被唤醒。进程为什么会阻塞?这是因为大部分IO类系统调用本身包含了对IO事件的判断,以及对进程挂起的逻辑。如果fd就是阻塞的,当收到信号进程会被唤醒,处理完信号捕捉动作后就会继续检测,底层数据不就绪就会继续挂起。但是如果fd是非阻塞的,当进程正在进行数据拷贝,拷贝到一半被信号中断,就会直接返回。
IO过程会受到信号的影响,如果fd是非阻塞的,正在拷贝收到了信号,可能导致读取出错。如果信号中断导致IO工作没做完,出错码会被设置成EINTR。

所以读取出错返回我们还要判断errno是否是EINTR。

#include <iostream>
#include <string>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>void SetNonBlock(int fd)
{int fl = fcntl(fd, F_GETFL);if (fl < 0){perror("fcntl");return;}fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int main()
{SetNonBlock(0);std::string tips = "Please Enter# ";while (true){write(1, tips.c_str(), tips.size());char buffer[1024];int n = read(0, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0){std::cout << "read file end!" << std::endl;break;}else {if (errno == EAGAIN || errno == EWOULDBLOCK){std::cout << "底层数据,没有就绪" << std::endl;}else if (errno == EINTR){std::cout << "被中断,重新来" << std::endl;}else{std::cout << "read error, n: " << n << ", errno: " << errno << std::endl;break;}}sleep(1);}return 0;
}

4、I/O多路转接之select

4.1、select函数接口

多路转接/多路复用有三种:select、poll、epoll。
多路转接核心作用:对多个文件描述符进行等待(手段),通知上层哪些fd已经就绪。本质是一种对IO事件就绪的通知机制。

在这里插入图片描述
select系统调用包含于头文件<sys/select.h>
1、参数nfds:表示等待的多个文件描述符中,fd最大值+1。比如你要等待fd为1、2、3、4、5、6,那么传参nfds的值就是7,也就是最大值6 + 1 = 7。

2、参数timeout:timeout是一个struct timeval*的指针类型,而struct timeval中含有两个成员,tv_sec表示秒,tv_usec表示微秒。这个参数用来设置底层select等待文件描述符的方式。
2.1、阻塞等待:将timeout设置为NULL,表示select底层阻塞等待传入的多个文件描述符。只要至少有一个fd继续,select就会返回。
2.2、非阻塞等待:设置timeout对应结构体对象为{0, 0},表示select底层非阻塞等待多个文件描述符。如果多个fd没有一个就绪,立即返回。如果有就绪,也是立即返回。
2.3、timeout方式:设置timeout对应的结构体对象为{5, 0},表示select底层5秒以内阻塞等待多个文件描述符,超时立即返回。如果5秒以内有任何一个fd就绪立即返回,如果没有fd就绪就阻塞等待,超时立即返回。

参数timeout是输入输出型参数,输入型参数用于设置等待时常,输出型参数表示剩余时间。比如传入{5, 0},5秒内没有就绪fd,超时返回,此时输出型参数就是{0, 0}表示时间耗尽。如果第3秒有fd就绪了,立即返回,输出型参数timeout为{2, 0},表示还剩下2秒。

3、select返回值:
3.1、返回值n > 0,表示有多少个fd就绪了。
3.2、返回值n < 0,表示select等待失败,通常n=-1,比如等待的多个文件描述符中有一个已经关闭了,这是被关的fd是非法的fd,但是你传给select,select检测到就出错返回。
3.3、返回值n == 0,表示底层没有fd就绪,也没有出错。

4、中间的三个参数readfds、writefds、exceptfds分别表示读文件描述符集、写文件描述符集、异常文件描述符集。因此select可以等待多个文件描述符。
fd_set是一种集合数据类型,OS给用户提供的数据类型。fd_set可以添加多个文件描述符。fd_set就是位图。
我们以读取为例,比如0000 1011,从右向左依次表示fd为0、1、2…,所以该位图表示的就是fd为0、1、3的文件描述符需要关心读事件是否就绪。所以比特位的位置表示文件描述符fd,比特位的内容表示要不要关心。
读文件描述符集—>关心读事件—>fd是否可读—>接收缓冲区是否有数据
写文件描述符集—>关心写事件—>fd是否可写—>发送缓冲区是否有空间
异常文件描述符集—>关心异常事件—>fd是否出现异常—>fd是错误的文件描述符?
如果只关心读,就只添加到readfds。如果既关心读又关心写,可以同时添加到readfds和writefds。

关于位图:
在这里插入图片描述
类似如图所示的数据结构,比如34,将来进行查找的时候先:34 / 32 = 1,表示在下标为1的元素中,然后:34 % 32 = 2,表示在该元素的从右往左数第二个比特位。
fd_set就是一个struct + 数组的位图,fd_set是一种具体的数据类型,并且是有固定大小的。这也就说明了fd_set能够包含的fd是有上限的,所以select能够管理的fd个数是有上限的。

在这里插入图片描述
我们定义一个fd_set类型,然后sizeof计算出字节数,一个字节有8个比特位,所以再乘以8,最终算出比特位个数就是可以管理的fd个数。因此select最多可以管理1024个fd。

5、readfds、writefds、exceptfds都是输入输出型参数,现在我们聚焦到readfds,readfds搞懂了另外两个也就懂了。
readfds
5.1、作为输入型参数的时候:用户告诉内核,你要帮我关心readfds位图中,被设置了的fd上的读事件。此时比特位位置表示fd具体的编号,比特位内容表示是否关心。
5.2、作为输出型参数的时候:内核告诉用户,你让我关心的readfds中,有哪些readfds已经就绪了。此时比特位的位置表示fd具体的编号,比特位内容表示是否就绪。
当用户对输出型参数中已经就绪的fd进行读取的时候,此时一定不会被阻塞。

对fd_set位图操作有如下方法:
在这里插入图片描述
FD_ZERO把位图清空,FD_SET将fd设置进fd_set,FD_CLR清理掉位图中的fd,FD_ISSET判断位图中fd是否就绪。


4.2、基于select的EchoServer

在这里插入图片描述
需要使用之前的Socket.hpp、InetAddr.hpp,Common.hpp、Log.hpp,日志带了锁所以Mutex.hpp也要加进来。然后实现SelectServer.hpp,在Main.cc中启动EchoServer。
我们先只关心读事件就绪!

1、实现SelectServer基本框架

#pragma once#include <iostream>
#include <memory>
#include "Socket.hpp"using namespace SocketModule;class SelectServer
{
public:SelectServer(uint16_t port):_port(port),_listen_socket(std::make_unique<TcpSocket>()),_isrunning(false){}void Init(){_listen_socket->BuildTcpSocketMethod(_port);}void Start(){}~SelectServer(){}
private:uint16_t _port;std::unique_ptr<Socket> _listen_socket;bool _isrunning;
};

2、实现Start函数

#pragma once#include <iostream>
#include <memory>
#include <sys/select.h>
#include "Socket.hpp"using namespace SocketModule;class SelectServer
{
public:SelectServer(uint16_t port): _port(port), _listen_socket(std::make_unique<TcpSocket>()), _isrunning(false){}void Init(){_listen_socket->BuildTcpSocketMethod(_port);}void Start(){fd_set rfds;_isrunning = true;while (_isrunning){FD_ZERO(&rfds);FD_SET(_listen_socket->Fd(), &rfds);struct timeval timeout = {10, 0};int n = select(_listen_socket->Fd() + 1, &rfds, nullptr, nullptr, &timeout);switch (n){case 0:std::cout << "time out..." << std::endl;break;case -1:perror("select");break;default:std::cout << "有读事件就绪啦..." << " timeout: " << timeout.tv_sec << ":" << timeout.tv_usec << std::endl;break;}}_isrunning = false;}~SelectServer(){}private:uint16_t _port;std::unique_ptr<Socket> _listen_socket;bool _isrunning;
};

在这里插入图片描述
当我们从listensock获取新连接,本质也是一种IO,我们这种IO只关心读事件是否就绪。
我们启动服务,用另一台机器telnet进行连接,然后我们发现左侧循环输出有读事件就绪,这是因为我们设置监听套接字进rfds,当就绪了之后我们只是打印输出信息,并没有将底层的新连接获取上来,所以再次调用select由于读事件就绪直接返回。


3、实现HandlerEvents

#pragma once#include <iostream>
#include <memory>
#include <sys/select.h>
#include "Socket.hpp"using namespace SocketModule;class SelectServer
{
public:SelectServer(uint16_t port): _port(port), _listen_socket(
http://www.xdnf.cn/news/954.html

相关文章:

  • 【Python爬虫实战篇】--爬取豆瓣电影信息(静态网页)
  • RS232 串行通信:C++ 实现指南
  • 微信小程序 == 倒计时验证码组件 (countdown-verify)
  • 5V 1A充电标准的由来与技术演进——从USB诞生到智能手机时代的电力革命
  • I/O复用函数的使用——select
  • Linux系统管理与编程13:基于CentOS7.x的LAMP环境部署
  • BGP路由控制实验
  • Linux论坛安装
  • vite安装及使用
  • arkTs:UIAbility 组件的生命周期
  • Linux——系统安全及应用
  • 内网穿透实践:cpolar快速入门教程
  • 大模型学习笔记 day01 提示工程入门1.One-shot Few-shot提示学习法
  • 【mongodb】--下载
  • Flink介绍——实时计算核心论文之MillWheel论文总结
  • 探索大语言模型(LLM):ReAct、Function Calling与MCP——执行流程、优劣对比及应用场景
  • `useLayoutEffect` 和 `useEffect`区别与联系
  • Spring Boot 整合 JavaFX 核心知识点详解
  • C++ explicit
  • vscode使用remote ssh插件连接服务器的问题
  • 阿狸电视桌面固件包分享-阿狸桌面功能详细使用教程
  • map和set封装
  • Python进程与线程的深度对比
  • C++学习:六个月从基础到就业——内存管理:自定义内存管理(上篇)
  • Java 并发包核心机制深度解析:锁的公平性、异步调度、AQS 原理全解
  • 【上位机——MFC】菜单类与工具栏
  • 单例模式 (Singleton Pattern)
  • DeepSeek R1模型微调怎么做?从入门到实战
  • 关于敏感文件或备份 安全配置错误 禁止通过 URL 访问 Vue 项目打包后的 .gz 压缩文件
  • RS232转Profibus DP网关:技术革新!