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) { // disconnectprintf("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复用

缺点: 参数太多,麻烦

 
#elsefd_set rfds, rset; //rfds返回数据dataset, 是应用层的,用户设置的// rset是复制rfds的, 用于被复制到内核空间,用于判断的FD_ZERO(&rfds); //先清空FD_SET(sockfd, &rfds); // 再把sockfd 设置在可读rfds里,置1int 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位是1if(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断连了,会被回收,所以要判断一下}// recvint i = 0;for(i = sockfd +1; i<= maxfd; i++){ //i就是fdif (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) { // disconnectprintf("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断连了,会被回收,所以要判断一下}// 抄上面recvint 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) { // disconnectprintf("client disconnect: %d\n", i);close(i);// FD_CLR(i, &rfds); //FD_CLR是fdset里的一个宏,select有;poll没有fds[i].fd= -1; //因为从0开始,置-1fds[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不用

#elseint 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); //controlwhile(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) { // disconnectprintf("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同步自己代码搞定!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux系统修改网卡名称

说明&#xff1a; 因操作过程需要停用网卡&#xff0c;导致ssh远程连接不上&#xff0c;需要控制台登录操作。 测试环境&#xff1a; CentOS7.9、8.2虚拟机 Suse15 SP4虚拟机 操作步骤&#xff1a; 方法一&#xff1a; 1、 查看网卡当前名称及状态 ip a2、 将网卡状态从启用…

记一次苹果appstore提审拒审问题1.2

有关苹果appstore审核1.2问题的处理方案 2023.8.6苹果回复 Bug Fix Submissions The issues weve identified below are eligible to be resolved on your next update. If this submission includes bug fixes and youd like to have it approved at this time, reply to thi…

Flutter 中的 CupertinoActionSheet 小部件:全面指南

Flutter 中的 CupertinoActionSheet 小部件&#xff1a;全面指南 在Flutter中&#xff0c;CupertinoActionSheet是用于在iOS风格的应用中显示动作面板的组件。它提供了一个简洁的界面&#xff0c;让用户可以快速从一组选项中做出选择。CupertinoActionSheet通常伴随着一个或多…

RK3566(泰山派):GP7101背光驱动

RK3566&#xff08;泰山派&#xff09;&#xff1a;GP7101背光驱动 文章目录 RK3566&#xff08;泰山派&#xff09;&#xff1a;GP7101背光驱动GP7101背光驱动电路配置i2c1设备树创建驱动编写Makefilegp7101_bl.c驱动触摸I2C驱动框架。驱动中的结构体probe函数devm_backlight_…

过滤器Filter和拦截器Interceptor实现登录校验

一.过滤器 Filter过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些登录验证的功能 1.Filter的快速入门 1.定义Filter:定义一个类&#xff0c;实现Filter接口&#xff0c;并重写其所有方法。2.配置 public class dofilter implements Filter {Override //初始化只…

【JAVA】数组的定义与使用

前一篇我们讲述了方法的使用和递归&#xff0c;这一讲 我们来叙述一下数组相关知识点。最近更新较快&#xff0c;大家紧跟步伐哦~~ 1. 数组的基本概念 1.1 为什么要使用数组 假设现在要存5个学生的javaSE考试成绩&#xff0c;并对其进行输出&#xff0c;按照之前掌握的知识点&…

obsidian 外观设置解毒

前言 一入obsidian深似海&#xff0c;外观设置也是五花八门&#xff0c;仿佛回到读书时期折腾桌面一样。 我对比了AnuPpuccin、minimal和其他的一些外观主题&#xff0c;设置都太复杂了&#xff0c;尤其是需要调整CSS文件&#xff0c;最后发现一款&#xff0c;非常好用&#…

数据传送指令

数据传送&#xff1a;负责把数据、地址或立即数传送到寄存器或存储单元中 数据传送指令可以分为四个类型&#xff1a;通用数据传送指令、地址传送指令、标志位传送指令、专用累加器传送指令(输入、输出指令) 一、通用数据传送指令 &#xff08;一&#xff09;传送字或字节指…

前端框架-echarts

Echarts 项目中要使用到echarts框架&#xff0c;从零开始在csdn上记笔记。 这是一个基础的代码&#xff0c;小白入门看一下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" co…

刷题之找到字符串所有字母异位词

找到字符串所有字母异位词 滑动窗口。滑动窗口大小为待比较数组的大小。 class Solution { public:vector<int> findAnagrams(string s, string p) {//滑动窗口vector<int>result;if(s.size()<p.size())return result;vector<int>pnum(26,0);//记录p的字…

链表----带环链表快慢指针进阶版

1.带环链表及其拓展 &#xff08;1&#xff09;这个题目组要就是进行判断这个链表是否带环&#xff0c;使用的是布尔类型作为返回值&#xff1b; &#xff08;2&#xff09;我们这里的思路是使用的快慢指针&#xff0c;快指针一次走2步&#xff0c;慢指针一次走1步&#xff0c…

iOS Xcode 升级Xcode15报错: SDK does not contain ‘libarclite

一 iOS Xcode 升级Xcode15报错: SDK does not contain libarclite 1.1 报错信息 SDK does not contain libarclite at the path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/ lib/arc/libarclite_iphonesimulator.a; try increasin…

Sass(Syntactically Awesome Stylesheets)是一种强大的CSS预处理器

Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;是一种强大的CSS预处理器&#xff0c;它旨在简化CSS的编写&#xff0c;并增强其功能。它让开发者能够使用变量、嵌套规则、混入&#xff08;mixins&#xff09;、继承等高级功能&#xff0c;从而编写更加结构化和…

Linux基础知识面试题

1. 请描述Linux操作系统的安装过程&#xff0c;并说明其中的关键步骤。 Linux操作系统的安装过程通常涉及以下几个关键步骤&#xff1a; 准备安装介质&#xff1a;需要从官网或者其他可靠来源下载Linux发行版的ISO镜像文件&#xff0c;并制作一个启动U盘或者烧录到DVD中。现在…

基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (九)

LlaMA 3 系列博客 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (一) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (二) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (三) 基于 LlaMA 3 + LangGraph 在windows本地部署大模型 (四) 基于 LlaMA…

Java面向对象——多态

即同一个方法可以根据发送对象的不同而采用多种不同的行为方式。 一个对象的实际类型是确定的&#xff0c;但可以指向对象的引用的类型有很多&#xff08;父类&#xff0c;有关系的类&#xff09;。 多态存在的条件&#xff1a; 1. 有继承关系&#xff1b; 2. 子类重写父类…

linux 卸载Nginx

在Linux中卸载Nginx&#xff0c;通常涉及几个关键步骤&#xff0c;包括停止Nginx服务、卸载依赖库、删除文件和清理安装包。具体的命令可能因Linux发行版的不同而略有差异。以下是一个通用的卸载流程&#xff1a; 1、停止Nginx服务 在卸载Nginx之前&#xff0c;需要先停止Ngi…

思维导图软件哪个好?5个软件教你自己快速制作思维导图

思维导图软件哪个好&#xff1f;5个软件教你自己快速制作思维导图 思维导图软件在现代生活和工作中扮演着重要的角色&#xff0c;它们可以帮助人们整理思维、规划项目、记录笔记等。以下是五款值得推荐的思维导图软件&#xff0c;它们各有特色&#xff0c;可以帮助您快速制作思…

嵌入式Linux:编译和使用Protobuf库

目录 1、开发环境和工具 2、安装和编译Protobuf、Protobuf-C库 3、编写和编译proto文件 4、修改makefile文件 5、测试示例 6、参考资料 Protobuf&#xff08;Protocol Buffers&#xff09;是由 Google 开发的一种轻量级、高效的结构化数据序列化方式&#xff0c;用于在不同应用…

Java红黑树详解及示例

红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;它确保了在最坏情况下基本操作&#xff08;比如插入、删除、查找&#xff09;都能在O(log n)时间内完成。红黑树的关键在于它在每个节点上存储了…