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.hqwc.cn/news/700551.html

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

相关文章

Git 基础使用(1) 入门指令

文章目录 Git 作用Git 安装Git 使用Git 仓库配置Git 工作原理Git 修改添加Git 查看日志Git 修改查询Git 版本回退 概念补充 Git 作用 Git 是一种分布式版本控制系统&#xff0c;它旨在追踪文件和文件夹的更改&#xff0c;并协助多人协作开发项目。 Git 安装 &#xff08;Lin…

Pyqt中QThread传递自己定义的参数、类、函数

Pyqt中QThread传递自己定义的参数、类、函数 1 pyqt中Qthread传递自己定义的参数2 pyqt中Qthread传递自己定义的类3 pyqt中Qthread传递自己定义的函数4 pyqt中Qthread内部定义自己的函数5 pyqt中Qthread传递参数到内部定义自己的函数 1 pyqt中Qthread传递自己定义的参数 在PyQ…

bugfix:遇见“隐形字符”:ⅰ与i的编码迷局

前言 在软件开发的世界里&#xff0c;遇到各种奇奇怪怪的bug是在所难免的。今天&#xff0c;我就遭遇了一个看似简单实则棘手的问题——用户反馈账号无法登录&#xff0c;系统一直提示“账号不存在”。一番抽丝剥茧后&#xff0c;我发现问题竟然出在一个不起眼的字符上&#x…

589.N叉树的前序遍历

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

深度剖析进程概念与进程状态

文章目录 1. 前言2. 什么是进程2.1 进程概念2.2 进程描述——PCB 3. 进程的一些基本操作3.1 查看进程3.2 结束进程3.3 通过系统调用获取进程标示符3.4 通过系统调用创建子进程 4. 进程状态4.1 普适的操作系统层面4.2 具体Linux操作系统层面 5. 两种特殊的进程5.1 僵尸进程5.2 孤…

[笔试训练](二十二)064:添加字符065:数组变换066:装箱问题

目录 064:添加字符 065:数组变换 066:装箱问题 064:添加字符 添加字符_牛客笔试题_牛客网 (nowcoder.com) 题目&#xff1a; 题解&#xff1a; 枚举所有A&#xff0c;B字符串可能的对应位置&#xff0c;得出对应位置不同字符数量的最小情况 两字符串的字符数量差n-m&…

【时隙ALOHA,CSMA(载波侦听多路访问)carrier sense mltiple access,无线局域网: CSMA/CA】

文章目录 时隙ALOHA时隙ALOHA的效率( Efficiency )纯ALOHA(非时隙)----效率低CSMA(载波侦听多路访问)carrier sense mltiple accessCSMA冲突CSMA/CD(冲突检测)边说边听&#xff08;提高了信道利用率&#xff09;以太网就是用的这个无线局域网: CSMA/CA无线局域网中的 MAC&#…

HTML常用标签-表单标签

表单标签 1 表单标签2 表单项标签2.1 单行文本框2.2 密码框2.3 单选框2.4 复选框2.5 下拉框2.6 按钮2.7 隐藏域2.8 多行文本框2.9 文件标签 1 表单标签 表单标签,可以实现让用户在界面上输入各种信息并提交的一种标签. 是向服务端发送数据主要的方式之一 form标签,表单标签,其内…

Docker 使用 Fedora 镜像

Fedora 在 Docker 中的使用也非常简单&#xff0c;直接使用命令 docker run -it fedora:latest bash 就可以 pull 到本地的容器中并且运行。 C:\Users\yhu>docker run -it fedora:latest bash Unable to find image fedora:latest locally latest: Pulling from library/fed…

C# OpenCvSharp DNN 黑白老照片上色

C# OpenCvSharp DNN 黑白老照片上色 目录 效果 项目 代码 下载 参考 效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropS…

Kali Linux 安装 + 获取 root 权限 + 远程访问!保姆级教程!

kali是linux其中一个发行版&#xff0c;基于Debian&#xff0c;前身是BackTrack&#xff08;简称BT系统&#xff09;。kali系统内置大量渗透测试软件&#xff0c;可以说是巨大的渗透系统&#xff0c;涵盖了多个领域&#xff0c;如无线网络、数字取证、服务器、密码、系统漏洞等…

Context Pattern上下文模式

使用情景 全局使用的配置&#xff0c;数据库的连接。MVC中的跨层数据传输携带请求ID&#xff0c;用户信息等用户权限信息线程上下文 跨层数据共享 统一调用参数 携带多个事务需要处理的对象 携带用户信息 使用ThreadLocal