2.1.2 事件驱动reactor的原理与实现

LINUX 精通 2

day14 20240513
day15 20240514
算法刷题:2维前缀和,一二维差分 耗时 135min
习题课 4h

课程补20240425 耗时:4h

课程链接地址

回顾

  1. 怎么学0voice课
  2. 网络io——一请求一线程,一个client一个连接再accpet分配io fd文件描述符

注意:rm -rf是一个非常强大和危险的命令,它会递归地删除目录及其内容,而不进行任何确认。请谨慎使用此命令,以免意外删除重要文件或系统关键组件

每次上课前统一一下代码

  1. gitlab.0voice.com

    找到代码,然后在Linux终端里 git clone 可以挂梯子, 用户名是邮箱

    在本地下连ftp shell好像不太行

  2. gcc -o networkio networkio.c -lpthread

问题

  1. client断开后问题

    运行以后./networkic 连三个 发三个,然后断开一个,会一直send recv 一瞬间cpu占用100%, 因为没有处理断开的

我没碰到,因为老师改过了void *client_thread加了count==0处理的

if (count == 0) { // disconnect
			printf("client disconnect: %d\n", clientfd);
			close(clientfd);
			break;
		}
  1. fd就是网络io 是int型

    开了sockfd在这里插入图片描述

    fd(不论是sockfd 还是clientfd) 从3开始,0 1 2系统默认stdin stdout stderror;往上增加

    ls /dev/fd
    

    在这里插入图片描述

    看ulimit -a文件描述符fd数量max (open files)

    在这里插入图片描述

    为什么能一直增加:

    linux下操作,一切皆是文件FD(file descriptor)

    可以隔段时间再send

  2. client断开后,隔段时间再连接,fd变了吗

    变了

    disconnect以后就 close(clientfd)了!!!!

    4没了,被回收了,变成了7

    等一段时间,又变成4了

    io回收时间 系统默认60s,set可以设置time_wait

2.1.2 事件驱动reactor的原理与实现

还是用我自己的版本

一请求一线程

优点:代码逻辑简单

缺点:不利于并发 1k,通过创建线程实现并发

所以用多路复用io

调试技巧

在命令行打man select函数名 就能看解释

select poll epoll

fdset
  1. 到底是什么东西?

    1. 答案:它是比特位集合

      把fd放一起的set集合

      干嘛的:比如你时间管理大师,处了好多个fd 对象

    2. 为什么要设置一个集合fdset, 然后传进select,传来传去

      传入3456,系统返回34可读

    在这里插入图片描述

    ​ 所有通讯底层的server io多路复用都是这么写的

    ​ 云里雾里的头痛,乱七八糟的,send可以,但是收不到!!!!我 爆炸了裂开

    现在是main里一个线程,多路io fd,fd间不影响!!

    ​ 可读返回,不可读阻塞在select,对着标准答案改了终于行了

    1. fd_set结构体

      select头文件里就1个struct, 4个宏定义,1个函数不难的

      宏定义FD_ZERO, FD_SET, FD_CLR,

      select()函数的参数:可读 可写,错误,

      timeout=null 默认一直阻塞, 如果阻塞超过市场就往下走,可以做一个定时器,就是为了切换线程,等待就绪再次被执行

  2. 大小

    fd_setsize大小可以改

    在posix_types.h里看到

    在这里插入图片描述

    fd_setsize = 1024

    8因为一个字节byte = 8bit

    sizeof(long)因为前面是unsigned long 所以要除它, 假设long是4 byte

    所以一个fd大小是1024/(8*4) = 32 byte

select

特点/运行机制

  1. 每次调用select需要把fd_set集合,从用户空间copy到kernel内核空间

  2. maxfd, 为了遍历fd是否set置一了,设置的最大的fd,需要人为设置

    for( int i = 0; i< maxfd; i ++)

    ps:rfds, rset区别

fd_set rfds, rset; 
//rfds返回数据dataset, 是应用层的,用户设置的
// rset是复制rfds的, 用于被复制到内核空间,用于判断的

优点:实现只用一个thread进程就能多路io复用

缺点: 参数太多,麻烦

 
#else

    fd_set rfds, rset; 
    //rfds返回数据dataset, 是应用层的,用户设置的
    // rset是复制rfds的, 用于被复制到内核空间,用于判断的

    FD_ZERO(&rfds); //先清空
    FD_SET(sockfd, &rfds); // 再把sockfd 设置在可读rfds里,置1

    int maxfd = sockfd; //用来方便遍历set用到的最大值

    while(1){
        rset = rfds; //关联,


        // maxfd +1因为下标从0开始,数量比最后多1
        //  int select(int nfds, fd_set *readfds, fd_set *writefds,
        //   fd_set *exceptfds, struct timeval *timeout);
        int nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //返回就绪的fd数量,就绪的bit位是1

        if(FD_ISSET(sockfd, &rset)){//sockfd位是否置1
            // accept 如果监听的sockfd置1了,就开始accept连接
            int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
            printf("accept finished: %d\n", clientfd);

            FD_SET(clientfd, &rfds); //有一个fd,就set一下,maxfd也变了
            if(maxfd < clientfd) maxfd = clientfd; //clientfd当client断连了,会被回收,所以要判断一下
        }

        // recv
        int i = 0;
        for(i = sockfd +1; i<= maxfd; i++){ //i就是fd
            if (FD_ISSET(i, &rset)){//判i可读rset吗
                char buffer[1024] = {0};
                int count = recv(i, buffer, 1024, 0); //Read N bytes into BUF from socket FD.
                
                if (count == 0) { // disconnect
                    printf("client disconnect: %d\n", i);
                    close(i);
                    FD_CLR(i, &rfds); //FD_CLR是一个宏,用于从fd_set数据结构中清除指定的文件描述符
                    continue;
                }
		        
				printf("RECV: %s\n", buffer);

				count = send(i, buffer, count, 0);
				printf("SEND: %d\n", count);
			}
		}
	}

#endif
poll
  1. struct pollfd里

    比如上面传入3456 只返回34可读

    fd 是哪一个fd

    events 传入的事件

    revents 返回的事件

    基本代码和select差不多,就是4个宏还有select函数要改成poll才有的 写法

    别进错文件夹编译

    gcc -o networkio networkio.c
    ./networkio
    

    开三个网络助手——connect——send——recv吗

    在这里插入图片描述

    成功啦啦啦啦啦啦啦

  2. 总结poll有什么呢

    1. pollfd是个结构体:fd 是哪一个fd;events 传入的事件;revents 返回的事件

    2. 宏定义:pollin可读,pollout等等

    3. poll函数是系统调用 每次把fds copy到内核kernel里

      系统用for遍历maxfd个数量,判断这个io fd是否就绪

  3. poll有什么独特使用场景

    1. 底层逻辑类似select参数更少

    2. 问: 假设5个fd一起来,阻塞,假设都不能可读一直等,直到1可读立即返回?

      答:不对,因为内核做不了微秒级,第一次与第二次进while的select往下几乎没有处理上的差别

      没懂???

#else
    //  进pollfd 看参数
    struct pollfd fds[1024]= {0}; //fdset就是
    fds[sockfd].fd = sockfd;
    fds[sockfd].events= POLLIN; //pollin就是可读,设置为POLLIN表示对该文件描述符上是否有可读数据感兴趣

    int maxfd = sockfd; //来遍历用的,检查哪个fd set了 
    while(1){
        int nread = poll(fds, maxfd + 1, -1);//set, set大小, timeout =-1一直阻塞等待
        if (fds[sockfd].revents & POLLIN){
            // pollin是x十六进制0x0001,变成8位2进制00000001
           
           
            // 如果有可读的,用accept处理分配io,复制上面
            int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
            printf("accept finished: %d\n", clientfd);

            // FD_SET(clientfd, &rfds); //select的这句改成poll的下面两行
            fds[clientfd].fd= clientfd;
            fds[clientfd].events=POLLIN;

            if(maxfd < clientfd) maxfd = clientfd; //clientfd当client断连了,会被回收,所以要判断一下
        }

            // 抄上面recv
            int i = 0;
            for(i = sockfd +1; i<= maxfd; i++){ //i就是fd
                // if (FD_ISSET(i, &rset)){//判i可读rset吗 select有,poll没有
                if(fds[i].revents & POLLIN){ //判i可读吗,和Pollin位与
                    char buffer[1024] = {0};
                    int count = recv(i, buffer, 1024, 0); //Read N bytes into BUF from socket FD.
                    
                    if (count == 0) { // disconnect
                        printf("client disconnect: %d\n", i);
                        close(i);
                        // FD_CLR(i, &rfds); //FD_CLR是fdset里的一个宏,select有;poll没有
                        fds[i].fd= -1; //因为从0开始,置-1
                        fds[i].events= 0;
                        continue;
                    }
                    
                    printf("RECV: %s\n", buffer);

                    count = send(i, buffer, count, 0);
                    printf("SEND: %d\n", count);
                }
            }
    }
epoll

linux 2.4以前,没有听过linux做server的,也没有云主机。当时server都是Windows,unix,十几年后现在云主机很多系统都是Linux,因为linux2.6以后引入epoll,server对io的数量更多

为什么?select与poll底层都需要进while的select/poll阻塞检查,再for判断 accept recv ,epoll不用

#else
    int epfd = epoll_create(1);

    struct epoll_event ev; //构建事件,只用来add和delete,control里没用
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); //control


    while(1){
        struct epoll_event events[1024] = {0};
        int nready = epoll_wait(epfd, events, 1024, -1);

		int i = 0;
		for (i = 0;i < nready;i ++) {
			int connfd = events[i].data.fd;
			if (connfd == sockfd) {
                
                
                // accept				
				int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);
				printf("accept finshed: %d\n", clientfd);
                // 创建events, 添到ctl里
				ev.events = EPOLLIN;
				ev.data.fd = clientfd;
                // 这里ev不写也可以
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
				
			} else if (events[i].events & EPOLLIN) {

				char buffer[1024] = {0};
				
				int count = recv(connfd, buffer, 1024, 0);
				if (count == 0) { // disconnect
					printf("client disconnect: %d\n", connfd);
					close(connfd);
                    // 改了这里
					epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
					// epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, &ev); 也可以
					
					continue;
				}

				printf("RECV: %s\n", buffer);

				count = send(connfd, buffer, count, 0);
				printf("SEND: %d\n", count);

			}

		}

	}
	
	

总结

  1. 一个struct结构体+三个函数给应用层提供的接口

    在这里插入图片描述

  2. int epoll_create(int size);知识点1:epoll_create返回传入都是int。

    epoll一开始size代表一次性就绪的io数量,后来就绪从数组改成链表,此时size没作用只要size不为0,效果都一样。 size为了兼容老版本留下来了

  3. 为什么epoll不用遍历?

    住户是IO或者fd,epoll类似快递员,但是去丰巢,由io自己去丰巢,事件events 有两个= 收+寄;poll、select是每家敲门,没法时时敲门还

    在这里插入图片描述

    1. epoll_create:用struct组织200总集200个住户IO;用struct组织快递柜丰巢——就绪,并聘请快递员
    2. epoll_ctl: 住户搬走EPOLL_CTL_DELET,搬进ADD,换楼层位置MOD
    3. epoll_wait: 快递员多久去一次丰巢,timeout市场;events是小车取出就绪内容,maxevent是小车多大
  4. 为什么能大并发?

    1. select(maxfd, rfds, wfds, efds, err);五个参数

      if 100万 io,需要把100万全部copy到对应fds里判断可读、写…

    2. 但是epoll不用每次从用户应用层copy到内核里,epoll create是一个个添加到内核里积累起来,有读写事件来了,wait就从就绪里操作

    3. 就绪里是真正处理的事件:微信号称3亿用户同时在线,就是IO整集大小,但是不代表server处理同时发消息

      每个client对应io,可能就绪队列在发消息的才一百万不到

  5. 思考题:

    1. 整集用什么数据结构存
      • 数组:将整数按顺序存储在数组中,并通过索引访问。这种方式简单直接,但插入和删除操作的时间复杂度较高。
      • 链表:将每个整数存储在链表节点中,并使用指针连接节点。这种方式对于频繁的插入和删除操作更为高效,但随机访问的性能较差。
      • 哈希集合:利用哈希函数将整数映射到不同的桶中,在每个桶内使用链表或红黑树来处理冲突。这种方式可以实现快速查找、插入和删除操作。
    2. 就绪用什么数据结构存
      • 位图(Bitmask):使用一个二进制位代表一个文件描述符,当某个文件描述符就绪时,相应位设置为1。该方法适用于文件描述符数量较少且连续排列的情况。
      • 数组:将就绪的文件描述符存储在数组中。该方法适用于文件描述符数量不多且无需频繁变动的情况。
      • 数据结构依赖具体的多路复用机制:例如,epoll使用红黑树来管理就绪事件,将文件描述符作为节点进行存储;而select和poll则使用fd_set结构体数组来存储就绪文件描述符。

引出reactor反应堆

  1. epoll:io数量很多;

    poll:io少, <10

    select: 无poll epoll 比如Linux2.4前

  2. 都是对网络应用层的IO事件处理4种,根本没有对用户层的业务service处理还,只处理了IO里的事件,告诉你吃饭events事件了,没告诉你什么饭怎么吃具体的service

  3. 过程:server listen——client connect——server accept——client send ——server receive同时回传

  4. 事件:

    所以是IO事件触发——水平触发,边沿触发

    核心是events事件,一个IO的生命周期=无数多个events

    server关心events,不是具体io,对不同events执行不同callback回调函数cb,模块化!!!

  5. reactor:核反应堆=不同IO事件处理callback回调函数 cb的集合

    封装起来,给以后用户层service用,更符合人的逻辑

    **什么是reactor:**注册一个events,当events发生,就从reactor查返回回调函数,做反应(类似留下名片只要有事就call 你)

    为什么要用reactor因为可以更好关注事件而不是io,io太多也不是都活着,从io管理➡️ 事件管理

网络IO

  1. accept——》listenfd/sockfd
  2. send/recv——》clientfd

网络应用是所有服务的基石

ps:

要用形象的东西,记忆更牢,也不反人性,少掉头发少烧脑

5/15晚上把github同步自己代码搞定!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/625668.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日两题 / 437. 路径总和 III 105. 从前序与中序遍历序列构造二叉树(LeetCode热题100)

437. 路径总和 III - 力扣&#xff08;LeetCode&#xff09; 前序遍历时&#xff0c;维护当前路径&#xff08;根节点开始&#xff09;的路径和&#xff0c;同时记录路径上每个节点的路径和 假设当前路径和为cur&#xff0c;那么ans 路径和(cur - target)的出现次数 /*** D…

C语言:指针(3)

1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* ; 本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。上⾯代码的意思是把⼀个常量字符串的⾸字符 h 的地址存放到指针变量 pstr 中。 2. 数组指针变量 2.1 数组指针变量是什么&#xff1f; 答案…

2024年高考倒计时精品网页

2024年高考倒计时精品网页 前言效果图部分代码领取源码下期更新预报 前言 随着季风轻轻掠过&#xff0c;岁月如梭&#xff0c;再次迎来了这个属于青春与梦想交汇的时刻——高考。这是一场知识的较量&#xff0c;更是一次意志的考验。在这最后的冲刺阶段&#xff0c;每一刻都显…

注意力机制篇 | YOLOv8改进之在C2f模块引入反向残差注意力模块iRMB | CVPR 2023

前言:Hello大家好,我是小哥谈。反向残差注意力模块iRMB是一种用于图像分类和目标检测的深度学习模块。它结合了反向残差和注意力机制的优点,能够有效地提高模型的性能。在iRMB中,反向残差指的是将原始的残差块进行反转,即将卷积操作和批量归一化操作放在了后面。这样做的好…

第 5 篇 : 多节点Netty服务端(可扩展)

说明 前面消息互发以及广播都是单机就可以完成测试, 但实际场景中客户端的连接数量很大, 那就需要有一定数量的服务端去支撑, 所以准备虚拟机测试。 1. 虚拟机准备 1.1 准备1个1核1G的虚拟机(160), 配置java环境, 安装redis和minio 1.2 准备6个1核1G的空虚拟机(161到166), …

【opencv】图像拼接实验

实验环境&#xff1a;anaconda、jupyter notebook 实验用到的包&#xff1a;opencv、matplotlib、numpy 注&#xff1a;opencv在3.4.2之后sift就不是免费的了 我用的是3.4.1.15版本 实验使用到的图片 一、sift函数获取特征值 读入图片 book cv2.imread(book.png, cv2.IMRE…

Winform(c#)如何上传图片等资源文件

1、首先找到工程中properties&#xff0c;如下图双击其中的Resources.resx文件 2、进入下面界面&#xff0c;点击“添加资源”&#xff0c;选择要添加的图片资源 3、然后我们就可以使用了

OSPF工作过程

1.OSPF的数据包 hello包——周期性的发现&#xff0c;建立以及保活邻居关系 hello时间 --- 10S 死亡时间 --- 4倍的hello时间 --- 40S RID --- 1&#xff0c;全网唯一;2&#xff0c;格式统一---- 格式要求和IP地址一样&#xff0c;由32位二进制构成&#xff0c;使用点分十进制…

JavaEE 初阶篇-深入了解网络原理 TCP/IP 协议

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 TCP 协议概述 1.1 TCP 协议格式 2.0 TCP 协议的特性 2.1 确认应答 2.2 超时重传 2.2.1 超时的时间如何确定&#xff1f; 2.3 连接管理 2.3.1 三次握手 2.3.2 四次…

【C++】priority_queues(优先级队列)和反向迭代器适配器的实现

目录 一、 priority_queue1.priority_queue的介绍2.priority_queue的使用2.1、接口使用说明2.2、优先级队列的使用样例 3.priority_queue的底层实现3.1、库里面关于priority_queue的定义3.2、仿函数1.什么是仿函数&#xff1f;2.仿函数样例 3.3、实现优先级队列1. 1.0版本的实现…

DGC-GNN 配置运行

算法 DGC-GNN&#xff0c;这是一种全局到局部的图神经网络&#xff0c;用于提高图像中2D关键点与场景的稀疏3D点云的匹配精度。与依赖视觉描述符的方法相比&#xff0c;这种方法具有较低的内存需求&#xff0c;更好的隐私保护&#xff0c;并减少了对昂贵3D模型维护的需求。DGC-…

树莓派发送指令控制FPGA板子上的流水灯程序

文章目录 前言一、树莓派简介二、整体实现步骤三、树莓派设置四、树莓派串口代码五、Verilog代码5.1 串口接收模块5.2 流水灯模块 六、quartus引脚绑定七、 运行效果总结参考 前言 ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信&#xff0c;控制FPGA开发板上的小灯。实验…

LBSS84LT1G 130MA 50V P沟道小电流MOS管

LBSS84LT1G作为一款P沟道功率MOSFET&#xff0c;由于其低导通电阻和快速切换特性&#xff0c;在电机控制中有着广泛的应用。以下是几个典型的应用案例&#xff1a; 1. 直流电机驱动&#xff1a;在直流电机驱动电路中&#xff0c;LBSS84LT1G可用于控制电机的转速和方向。通过控…

WebSocket前后端建立以及使用

1、什么是WebSocket WebSocket 是一种在 Web 应用程序中实现双向通信的协议。它提供了一种持久化的连接&#xff0c;允许服务器主动向客户端推送数据&#xff0c;同时也允许客户端向服务器发送数据&#xff0c;实现了实时的双向通信。 这部分直接说你可能听不懂&#xff1b;我…

nestJs中跨库查询

app.module.ts中配置 模块的module中 注意实体类在写的时候和数据库中的表名一样 service中使用一下

【Cesium解读】Cesium中primitive/entity贴地

官方案例 Cesium Sandcastle Cesium Sandcastle 好文推荐&#xff1a;Cesium贴地设置_primitive贴地-CSDN博客 scene.globe.depthTestAgainstTerrain true; True if primitives such as billboards, polylines, labels, etc. should be depth-tested against the terrain…

【C++】内联函数、auto、范围for

文章目录 1.内联函数2.auto关键字2.1auto简介2.2auto的注意事项2.3auto不能推导的场景 3.基于范围的for循环(C11)4.指针空值nullptr(C11) 1.内联函数 概念&#xff1a; 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函…

CLIPDraw:通过语言-图像编码器探索文本到绘图合成

摘要 本工作介绍了 CLIPDraw&#xff0c;这是一种基于自然语言输入合成新颖绘画的算法。CLIPDraw 不需要任何训练&#xff1b;相反&#xff0c;它使用了一个预先训练好的 CLIP 语言-图像编码器作为衡量标准&#xff0c;以最大化给定描述与生成绘画之间的相似度。关键的是&…

使用XxlCrawler抓取全球航空公司ICAO三字码

目录 前言 一、数据源介绍 1、目标网站 2、页面渲染结构 二、XxlCrawler信息获取 1、创建XxlCrawler对象 2、定义PageVo对象 3、直接PageVO解析 4、自定义解析 总结 前言 长距离旅行或者出差&#xff0c;飞机一定是出行的必备方式。对于旅行达人或者出差人员而言&…