颤抖开篇,从php角度谈谈IO模型(BIO)

news2024/10/5 13:12:16

颤抖开篇,从php角度谈谈IO模型(BIO)

IO 是什么?

在计算机系统中I/O就是输入(input)和输出(Output)的意思。针对不同的操作对象,可以划分为磁盘I/O模型,网络I/O模型,内存映射I/O,Direct I/O、数据库I/O等,只要具有输入输出类型的交互系统都可以认为是I/O系统,也可以说是整个操作系统数据交换与人机交互的通道,这个概念与选用的开发语言没有关系,是一个通用概念。

谈谈 (阻塞)

在学习 IO 中必须要搞懂的几个概念:(阻塞,非阻塞)与(同步,异步)

本篇文章只介绍阻塞,其余几个概念将在后面篇章中挨个介绍学习。

在了解阻塞IO前,我们先看看网络数据包接收流程,在这里我们可以将整个流程总结为两个阶段:

图片
数据接收阶段.png

  • 数据准备阶段:
    在这个阶段,网络数据包到达网卡,通过DMA的方式将数据包拷贝到内存中,然后经过硬中断,软中断,接着通过内核线程ksoftirqd经过内核协议栈的处理,最终将数据发送到内核Socket的接收缓冲区中。

  • 数据拷贝阶段: 当数据到达内核Socket的接收缓冲区中时,此时数据存在于内核空间中,需要将数据拷贝到用户空间中,才能够被应用程序读取。

阻塞

阻塞主要发生在第一阶段:数据准备阶段。

当应用程序发起系统调用 read 读操作时,线程从用户态转为内核态,读取内核Socket的接收缓冲区中的网络数据。

如果这时内核Socket的接收缓冲区没有数据,那么线程就会一直等待,直到Socket接收缓冲区有数据为止。随后将数据从内核空间拷贝到用户空间,系统调用 read 返回。

在这里插入图片描述

从图中我们可以看出:阻塞的特点是在第一阶段和第二阶段都会等待。

阻塞IO(BIO)

在这里插入图片描述

经过前一小节对阻塞这个概念的介绍,相信大家可以很容易理解阻塞IO的概念和过程。

既然这小节我们谈的是IO,那么下边我们来看下在阻塞IO模型下,网络数据的读写过程。

阻塞读

当用户线程发起read系统调用,用户线程从用户态切换到内核态,在内核中去查看Socket接收缓冲区是否有数据到来。

Socket接收缓冲区中有数据,则用户线程在内核态将内核空间中的数据拷贝到用户空间,系统IO调用返回。

Socket接收缓冲区中无数据,则用户线程让出CPU,进入阻塞状态。当数据到达Socket接收缓冲区后,内核唤醒阻塞状态中的用户线程进入就绪状态,随后经过CPU的调度获取到CPU quota进入运行状态,将内核空间的数据拷贝到用户空间,随后系统调用返回。

阻塞写

当用户线程发起send系统调用时,用户线程从用户态切换到内核态,将发送数据从用户空间拷贝到内核空间中的Socket发送缓冲区中。

当Socket发送缓冲区能够容纳下发送数据时,用户线程会将全部的发送数据写入Socket缓冲区,然后执行在网络包发送流程,然后返回。

当Socket发送缓冲区空间不够,无法容纳下全部发送数据时,用户线程让出CPU,进入阻塞状态,直到Socket发送缓冲区能够容纳下全部发送数据时,内核唤醒用户线程,执行后续发送流程。

阻塞IO模型下的写操作做事风格比较硬刚,非得要把全部的发送数据写入发送缓冲区才肯善罢甘休。

由于BIO 的阻塞特性,想让BIO 同时为多个客户端服务,每个请求都需要被一个独立的线程处理。一个线程在同一时刻只能与一个连接绑定。来一个请求,服务端就需要创建一个线程用来处理请求。

阻塞IO模型

在这里插入图片描述

在早期 Java 中 BIO 的实现就是一个客户端连接创建一个线程来处理请求,由于php 对多线程支持不是特别好,在php 中如何实现BIO呢? 咱们先来个例子

// 创建 ipv4 tcp socket
$sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
if (!is_resource($sockfd)) {
   fprintf(STDOUT, "socket create fail:%s\n", socket_strerror(socket_last_error($this->_sockfd)));

}
// 绑定地址 端口
socket_bind($sockfd,'0.0.0.0',6379);
// 监听 socket
socket_listen($sockfd,10);

while (1){
   // 第一个阻塞函数   获取客户端连接,没有客户端连接会一直阻塞,阻塞状态的进程是不会占据CPU的
   $connfd = socket_accept($sockfd);
   if(empty($connfd)){
       continue;
   }
   // 第二个阻塞函数 , 没有数据会一直阻塞,直到客户端发送数据过来,才往下执行
      $buff = socket_read($connfd,1024);
     fprintf(STDOUT,"%s",$buff);
   // 向客户端发送一个helloworld
   $msg = "helloworld\r\n";
   
   // 向客户端发送数据
   socket_write($connfd, $msg, strlen( $msg ) );
   
   echo time().' : a new client'.PHP_EOL;
   // 服务端 主动断开客户端连接
   socket_close($connfd);

}
// 关闭 监听 socket
socket_close($sockfd);

简单解析一下上述代码来说明一下tcp socket服务器的流程:

  • 首先,根据协议族(或地址族)、套接字类型以及具体的的某个协议来创建一个socket。
  • 第二,将上一步创建好的socket绑定(bind)到一个ip:port上。
  • 第三,开启监听linten。
  • 第四,使服务器代码进入无限循环不退出,当没有客户端连接时,程序阻塞在accept
    上,有连接进来时才会往下执行,接着阻塞在read 上,客户端发送数据才会往下执行 ,然后再次循环下去,为客户端提供持久服务。

上面这个案例中,有两个很大的缺陷:

  1. 一次只可以为一个客户端提供服务,如果第一个客户端连接没有发送数据,导致一直阻塞在 read 上,这时有第二个客户端来连接,那么第二个客户端就必须要等待第一个连接发送数据才行。
  2. 很容易受到攻击,造成拒绝服务。

分析了上述问题后,又联想到了前面说的多进程,那我们可以在accpet到一个请求后就fork一个子进程来处理这个客户端的请求,这样当accept了第二个客户端后再fork一个子进程来处理第二个客户端的请求,这样问题不就解决了吗?

// 创建 ipv4 tcp socket
$sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
if (!is_resource($sockfd)) {
   fprintf(STDOUT, "socket create fail:%s\n", socket_strerror(socket_last_error($this->_sockfd)));

}
// 绑定地址 端口
socket_bind($sockfd,'0.0.0.0',6379);
// 监听 socket
socket_listen($sockfd,10);

while (1){
   // 第一个阻塞函数   获取客户端连接,没有客户端连接会一直阻塞,阻塞状态的进程是不会占据CPU的
   $connfd = socket_accept($sockfd);
   if(empty($connfd)){
       continue;
   }

    $pid = pcntl_fork();
   if($pid == 0){
       // 第二个阻塞函数 , 没有数据会一直阻塞,直到客户端发送数据过来,才往下执行
      $buff = socket_read($connfd,1024);
     fprintf(STDOUT,"%s",$buff);
       // 向客户端发送一个helloworld
       $msg = "helloworld\r\n";
       socket_write($connfd, $msg, strlen( $msg ) );
       // 休眠5秒钟,可以用来观察时候可以同时为多个客户端提供服务
       echo time().' : a new client'.PHP_EOL;
       socket_close($connfd);
   }

}
// 关闭 监听 socket
socket_close($sockfd);

通过 fork 多进程的确可以同时服务多个客户端,但当客户端请求的并发量突然增大时,服务端在一瞬间就会创建出大量的进程,而创建进程是需要系统资源开销的,这样一来就会一瞬间占用大量的系统资源。

如果客户端创建好连接后,但是一直不发数据,通常大部分情况下,网络连接也并不总是有数据可读,那么在空闲的这段时间内,服务端进程就会一直处于阻塞状态,无法干其他的事情。CPU也无法得到充分的发挥,同时还会导致大量进程切换的开销。

编写一个例子模拟大量客户端连接

for ($i = 10000; $i < 65000; $i++){
	// 创建 socket 
    $_sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
    // 绑定客户端 ip地址 端口
    socket_bind($_sockfd,'192.168.0.102',$i);
    // 连接服务端
    if(socket_connect($_sockfd,'111.230.247.213',6379)){
        fprintf(STDOUT,"客户端连接成功 ip=%s\n",'192.168.0.102:'.$i);
    }
}

执行客户端模拟连接脚本
在这里插入图片描述
服务端的确可以同时处理很多请求,但是也创建了大量进程,消耗大量系统资源与同时还会导致大量进程切换的开销。
在这里插入图片描述
在这里插入图片描述

所以,我们就再次提出增进型解决方案。我们可以预估一下业务量,然后在服务启动的时候就fork出固定数量的子进程,每个子进程处于无限循环中并阻塞在 accept 上,当有客户端连接挤进来就处理客户请求,当处理完成后仅仅关闭连接但本身并不销毁,而是继续等待下一个客户端的请求。这样,不仅避免了进程反复fork销毁巨大资源浪费,而且通过固定数量的子进程来保护系统不会因无限fork而崩溃,其实这就是资源池化解决方案。

$sockfd = socket_create(AF_INET, SOCK_STREAM, 0);
if (!is_resource($sockfd)) {
    fprintf(STDOUT, "socket create fail:%s\n", socket_strerror(socket_last_error($this->_sockfd)));

}
socket_bind($sockfd,'0.0.0.0',6379);
socket_listen($sockfd,10);


// 按照数量fork出固定个数子进程
for( $i = 1; $i <= 10; $i++ ){
    $pid = pcntl_fork();
    if( 0 == $pid ){
        cli_set_process_title('phpserver worker process');
        while( true ){
            $conn_socket = socket_accept( $sockfd );
            $msg = "helloworld\r\n";
            socket_write($conn_socket, $msg, strlen( $msg ) );
            socket_close($conn_socket);
        }
    }
}
// 父进程回收子进程退出,回收资源
while( true ){
    $pid = pcntl_wait($status);
    if($pid > 0){
        fprintf(STDOUT,"PID=%d 子进程退出了",$pid);
    }
}
socket_close($sockfd );

启动php BIO 服务端 ,通过 ps -ef|grep phpserver 命令查看阻塞在 socket_accept 等待处理客户端连接的 10 个子进程

在这里插入图片描述

预先创建10个子进程 处于等待服务状态,再同一个时刻可以同时为10个客户端提供服务。

适用场景

基于以上阻塞IO模型的特点,该模型只适用于连接数少,并发度低的业务场景。

比如公司内部的一些管理系统,通常请求数在100个左右,使用阻塞IO模型还是非常适合的。而且性能也不错。

文章部分内容参考文献

  1. https://mp.weixin.qq.com/s/zAh1yD5IfwuoYdrZ1tGf5Q

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

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

相关文章

开发神器VSCode配置C/C++编译环境

hi&#xff0c;小伙伴们大家好&#xff0c;今天给大家介绍一款程序员常用的开发神器VSCode&#xff0c;想必大家肯定有所了解&#xff0c;也有很多小伙伴在日常工作中经常使用。当木荣君初次见到VSCode时&#xff0c;真正的被它惊艳到了&#xff0c;可以说是一见钟情。从此就爱…

13.6-14.8读书笔记

13.6 对象移动 13.6.1 右值引用 概念: 为了支持移动操作,新标准引入了的一种新的引用类型.所谓右值引用就是必须绑定到右值的引用. 通过&&来获得右值引用 int i 42;int &r i;int &&rr i; // 错误,不能将一个右值引用绑定到一个左值上int &r3 …

【python基础_05】面向对象

文章目录1. 类和对象1.1 使用对象组织数据的模版1.2 成员变量和成员方法1.3 实现代码2. 内置方法&#xff08;魔术方法&#xff09;2.1 构造方法&#xff1a;__init__&#xff08;&#xff09;1. 类和对象 1.1 使用对象组织数据的模版 1.2 成员变量和成员方法 1.3 实现代码 1…

jupyter notebook无法启动内核

jupyter notebook无法启动内核问题概述方法一使用Window PowerShell方法二更改文件路径重新启动内核参考问题概述 遇到的问题是在使用jupyter的时候无法正常运行,所以在这里尝试一些办法,在这里进行记录,希望能够帮助到大家 方法一 使用Window PowerShell 首先第一个方法就…

Java IO流 - 释放资源的方式

资源释放的方式 书接上文, 在上一篇文章我们做过一个文件拷贝的练习, 但是在联系中是有释放资源隐患的的, 例如在下面代码中, 在文件释放之前有许多行的逻辑代码; 如果这许多行的逻辑代码有报错, 导致程序不运行, 那么资源就得不到释放 public static void main(String[] args)…

Crack:ActiveReportsJS 3.2.2 EN:ActiveReportsJS

ActiveReportsJS - 高级 JavaScript 报告解决方案 ActiveReportsJS 是一种用于在前端应用程序中可视化数据的报告解决方案。Ω578867473自定义报告布局并将我们的报告设计器和查看器组件集成到 Web 应用程序中&#xff0c;以便在任何平台上预览、导出或打印报告。 使用我们的跨…

WPF+ASP.NET SignalR实现动态折线图

在实际业务中&#xff0c;当后台数据发生变化&#xff0c;客户端能够实时的收到通知&#xff0c;而不是由用户主动的进行页面刷新才能查看&#xff0c;这将是一个非常人性化的设计。有没有那么一种场景&#xff0c;后台数据明明已经发生变化了&#xff0c;前台却因为没有及时刷…

ElementUI——案例2用户管理(基于SpringBoot实现增删改)

1.ElementUI整合SpringBoot前后端分离实现用户增删改查 效果展示 2.前端核心代码 项目目录 main.js引入 import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; import router from ./router import axios from axiosVue.prototype.$http ax…

SQL 存储过程

文章目录存储过程简介存储过程的创建及调用存储过程的删除 如何删除存储过程存储过程的优缺点现需要向学生表中插入新的学生数据。但在插入学生数据的时&#xff0c;需要同 时检查老师表里的数据。如果插入学生的老师不在老师表里&#xff0c;则先向老师表中插入一条老师数据&a…

高级IO-多路转接

高级IO 以前的都是拷贝接口。write什么的就是将字符串拷贝到发送缓冲区中。 应用层等待接收缓冲区填写数据的过程算是IO吗&#xff1f;算 IO等待拷贝数据&#xff1b; 真正的IO的过程就是拷贝的过程。比如等待鱼上钩的时候也算是钓鱼(adj)&#xff0c;当把鱼拿上来的时候也…

【Spring篇】代理模式

&#x1f353;个人主页&#xff1a;个人主页 &#x1f352;系列专栏&#xff1a;SSM框架 目录 一、场景模拟 二、提出问题 三、代理模式 1.静态代理 2.动态代理 一、场景模拟 ①声明接口 声明计算器接口Calculator&#xff0c;包含加减乘除的抽象方法 public interface…

hadoop基础搭建(hadoop+hive+hbase+zk)(一)

文章目录一、基础环境&插件安装&#xff08;root&#xff09;二、创建启动脚本&#xff0c;后续使用三、安装JDK&#xff08;root&#xff09;四、安装Hadoop五、安装 rsync&#xff08;root&#xff09;六、网络配置&#xff08;4台服务器&#xff0c;root&#xff09;七、…

使用html2canvas,将页面转换成图片的采坑记录(Web/Taro h5)

使用html2canvas将页面转换成图片的采坑记录 "html2canvas": "^1.4.1","tarojs/taro": "3.4.0-beta.0"问题: 1. 生成的图片很模糊 2. 生成的图片是空白 3. 生成的图片不完整 截图前是这样 截图后这样 截图后的图片图片缺省了一部分…

【消息中间件】1小时快速上手RabbitMQ

前 言 &#x1f349; 作者简介&#xff1a;半旧518&#xff0c;长跑型选手&#xff0c;立志坚持写10年博客&#xff0c;专注于java后端 ☕专栏简介&#xff1a;深入、全面、系统的介绍消息中间件 &#x1f330; 文章简介&#xff1a;本文将介绍RabbitMQ&#xff0c;一小时快速上…

Good Bye 2022: 2023 is NEAR D. Koxia and Game

原题链接&#xff1a;Problem - D - Codeforces 题面&#xff1a; 大概意思就是给你一个数组a和数组b&#xff0c;你自己设计一个数组c&#xff0c;Koxia可以从a[i]、b[i]和c[i]中选一个&#xff0c;而Mahiru只能从另外两个里选一个&#xff0c;问你有多少个数组c一定能使Mah…

给GitHub装扮个性化首页

我的主页 如何配置 需要创建一个仓库&#xff0c;仓库名要跟GitHub的用户名一样,我的已经创建过了 喜欢我的主页可以直接fork然后在自己的仓库修改&#xff0c;如果想自己装扮直接看下一步 地址&#xff1a;https://github.com/linweiqian 开始装扮 下面展示仓库状态统计的设…

工业控制系统安全控制应用缩略语汇总

声明 本文是学习GB-T 32919-2016 信息安全技术 工业控制系统安全控制应用指南. 下载地址 http://github5.com/view/585而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 工业控制系统安全控制应用缩略语 ICS 工业控制系统&#xff08;Industrial Contro…

线程的高效利用——线程池

文章目录线程的开销线程池的工作方式ThreadPoolExecutor基础线程池结果的处理线程的开销 线程作为一种昂贵的资源&#xff0c;开销包括如下几点&#xff1a; 1、线程的创建与启动的开销。 2、线程的销毁的开销。 3、线程调度的开销。线程的调度会产生上下文切换&#xff0c;从…

skywalking解析-入门

前几天从github上看最近比较火的项目&#xff0c;发现了skywalking&#xff0c;就进行了些了解&#xff0c;发现这个领域自己目前知之甚少&#xff0c;打算通过对源码的分析深入了解一下分布式追踪。首先从对skywalking介绍开始。 目录一、简介二、整体架构三、源码下载四、系统…

spark-RDD学习笔记

本文是19年学生时学习林子雨老师课程中的一些学习笔记&#xff0c;主要内容包括RDD的概念和运行原理,rdd相关编程api接口以及对应的实例。关于RDD的内容&#xff0c;这个笔记描述的2019年年底之前的pyspark版本&#xff0c;2023年年初时在pyspark的实际工作中rdd已经很少用或者…