一个线程如何处理多个连接?(非阻塞IO)

news2024/11/17 4:53:53

从BIO到NIO的转变

    • 五种IO模型
    • BIO的缺陷
    • 非阻塞
    • 非阻塞IO(NIO)
      • 非阻塞读
      • 非阻塞写
    • 非阻塞IO模型
    • php NIO 实现
    • 适用场景
    • 什么是C10k问题?
      • C10K问题的由来

五种IO模型

《UNIX 网络编程》一书中介绍了五种IO模型: 分别是 BIO,NIO,IO多路复用器,信号驱动式IO,AIO(异步IO 模型)

BIO的缺陷

从BIO 到 NIO 在到 多路复用器等 后续的IO模型为什么出现? 是直接就出现了还是说他们之间有因果关系。

技术都有一个发展的过程,促使一个新IO模型的出现一定是老的IO模型存在弊端,新IO模型是为了解决弊端才会出现。

所以这个因果关系就是,BIO(Blocking I/O) 有没有弊端?才促使NIO的出现!

BIO 这种模式要服务多客户端,必须每个客户端连接都需要服务端创建一个线程去处理连接,每个线程又需要调用 accept() ,recv(),clone() 等一系列 系统调用函数,系统调用会造成中断,导致用户态到内核态的切换,加上线程间的切换导致 cpu 一部分时间是没有跑程序而是再跑内核调度的过程,最终得出一个比较浪费cpu资源的结论,为什么会这样?其实是因为进程或者是线程 太多了。

大家有没有想过BIO为什么要创建这么多进程或者线程,想明白这点,你才能找到BIO产生上面说的那些弊端最底层原因。

没错 Blocking(阻塞) 这才是BIO模型最底的弊端,内核给的系统调用 accept() ,recv() 一定是会有一个阻塞发生,就是因为阻塞才导致我们的程序必须不停的创建线程或者进程去处理客户端连接,如果都放在一个进程或者线程中执行就无法服务多个客户端连接
在这里插入图片描述
怎么解决 Blocking(阻塞) 这个弊端?

程序员写程序能解决这个问题吗?,你可以试一试不创建多个线程或者进程能不能处理多客户端连接,accept() ,recv()系统调用函数是内核提供的,调用就是有阻塞,所有我们程序员是无能为力了,只能是内核做出改变。

基于内核的改变,才有NIO(nonblock非阻塞IO模型) 的产生,每一种IO模型的出现都是对前一种的升级优化。

非阻塞

阻塞和非阻塞主要的区分是在第一阶段:数据准备阶段。

在第一阶段,当Socket的接收缓冲区中没有数据的时候,阻塞模式下应用线程会一直等待。非阻塞模式下应用线程不会等待,系统调用直接返回错误标志EWOULDBLOCK

当Socket的接收缓冲区中有数据的时候,阻塞和非阻塞的表现是一样的,都会进入第二阶段等待数据从内核空间拷贝到用户空间,然后系统调用返回。

在这里插入图片描述
从上图中,我们可以看出:非阻塞的特点是第一阶段不会等待,但是在第二阶段还是会等待。

非阻塞IO(NIO)

阻塞IO模型最大的问题就是一个线程只能处理一个连接,如果这个连接上没有数据的话,那么这个线程就只能阻塞在系统IO调用上,不能干其他的事情。这对系统资源来说,是一种极大的浪费。同时大量的线程上下文切换,也是一个巨大的系统开销。

所以为了解决这个问题,我们就需要用尽可能少的线程去处理更多的连接。,网络IO模型的演变也是根据这个需求来一步一步演进的。

基于这个需求,第一种解决方案非阻塞IO就出现了。我们在上一小节中介绍了非阻塞的概念,现在我们来看下网络读写操作在非阻塞IO下的特点:

在这里插入图片描述

非阻塞读

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

  • Socket接收缓冲区中无数据,系统调用立马返回,并带有一个 EWOULDBLOCK
    EAGAIN 错误,这个阶段用户线程不会阻塞,也不会让出CPU,而是会继续轮训直到Socket接收缓冲区中有数据为止。
  • Socket接收缓冲区中有数据,用户线程在内核态会将内核空间中的数据拷贝到用户空间,注意这个数据拷贝阶段,应用程序是阻塞的,当数据拷贝完成,系统调用返回。

非阻塞写

前边我们在介绍阻塞写的时候提到阻塞写的风格特别的硬朗,头比较铁非要把全部发送数据一次性都写到Socket的发送缓冲区中才返回,如果发送缓冲区中没有足够的空间容纳,那么就一直阻塞死等,特别的刚。

相比较而言非阻塞写的特点就比较佛系,当发送缓冲区中没有足够的空间容纳全部发送数据时,非阻塞写的特点是能写多少写多少,写不下了,就立即返回。并将写入到发送缓冲区的字节数返回给应用程序,方便用户线程不断的轮训尝试将剩下的数据写入发送缓冲区中。

非阻塞IO模型

非阻塞IO模型

基于以上非阻塞IO的特点,我们就不必像阻塞IO那样为每个请求分配一个线程去处理连接上的读写了。

我们可以利用一个线程或者很少的线程,去不断地轮询每个Socket的接收缓冲区是否有数据到达,如果没有数据,不必阻塞线程,而是接着去轮询下一个Socket接收缓冲区,直到轮询到数据后,处理连接上的读写,或者交给业务线程池去处理,轮询线程则继续轮询其他的Socket接收缓冲区。

这样一个非阻塞IO模型就实现了我们需要用尽可能少的线程去处理更多的连接

php NIO 实现

// 创建 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);

// 存放客户端 socket
$client = [];
while (1){
	// 设置监听 socket 为非阻塞
    socket_set_nonblock($sockfd);
    // socket_accept() 设置为非阻塞没有客户端连接将直接返回 false。
    $connfd = socket_accept($sockfd);
    if(empty($connfd)){
        if($client){
        	// 遍历所有客户端socket 连接,挨个读取看看客户端是否有发送数据到服务端
            foreach ($client as $c_fd){
            	// 读取 socket 上发来的数据,如果未发来数据,立即返回 false ,不会阻塞
                $buff = socket_read($c_fd,1024);

                if(empty($buff)){
                    continue;
                }
                fprintf(STDOUT,"client data: %s\n",$buff);
                $biff = "server ok!\n";
                socket_write($c_fd,$biff,strlen($biff));
            }
        }
    }else {
    	// 设置客户端 socket 为非阻塞,当调用socket_read()读取socket发送过来数据,如果为发送数据不产生阻塞,直接返回 false
        socket_set_nonblock($connfd); 
        $client[] = $connfd;
        socket_getpeername($connfd,$address,$port);
        fprintf(STDOUT, "有客户端连接 ip:%s,port:%d\n",$address,$port);
    }
    if($client){
        foreach ($client as $c_fd){
        // 读取 socket 上发来的数据,如果未发来数据,立即返回 false ,不会阻塞
            $buff = socket_read($c_fd,1024);

            if(empty($buff)){
                continue;
            }
            fprintf(STDOUT,"client data: %s\n",$buff);
            $biff = "server ok!\n";
            socket_write($c_fd,$biff,strlen($biff));
        }
    }
}

适用场景

虽然非阻塞IO模型与阻塞IO模型相比,减少了很大一部分的资源消耗和系统开销。

但是它仍然有很大的性能问题,因为在非阻塞IO模型下,需要用户线程或进程去不断地发起系统调用去轮训Socket接收缓冲区,这就需要用户线程不断地从用户态切换到内核态内核态切换到用户态。随着并发量的增大,这个上下文切换的开销也是巨大的。

所以单纯的非阻塞IO模型还是无法适用于高并发的场景。只能适用于C10K以下的场景。

什么是C10k问题?

著名的C10K并发连接问题(即单机1万个并发连接问题),“C10K”概念最早由Dan Kegel发布于其个人站点,即出自其经典的《The C10K problem 》一文。

C10K问题的由来

大家都知道互联网的基础就是网络通信,早期的互联网可以说是一个小群体的集合。互联网还不够普及,用户也不多,一台服务器同时在线100个用户估计在当时已经算是大型应用了,所以并不存在什么 C10K 的难题。互联网的爆发期应该是在www网站,浏览器,雅虎出现后。最早的互联网称之为Web1.0,互联网大部分的使用场景是下载一个HTML页面,用户在浏览器中查看网页上的信息,这个时期也不存在C10K问题。

Web2.0时代到来后就不同了,一方面是普及率大大提高了,用户群体几何倍增长。另一方面是互联网不再是单纯的浏览万维网网页,逐渐开始进行交互,而且应用程序的逻辑也变的更复杂,从简单的表单提交,到即时通信和在线实时互动,C10K的问题才体现出来了。因为每一个用户都必须与服务器保持TCP连接才能进行实时的数据交互,诸如Facebook这样的网站同一时间的并发TCP连接很可能已经过亿。

这时候问题就来了,最初的服务器都是基于进程/线程模型的,新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么单机而言操作系统是无法承受的(往往出现效率低下甚至完全瘫痪)。如果是采用分布式系统,维持1亿用户在线需要1万台服务器,成本巨大,也只有Facebook、Google、雅虎等巨头才有财力购买如此多的服务器。

基于上述考虑,如何突破单机性能局限,是高性能网络编程所必须要直面的问题。这些局限和问题最早被Dan Kegel 进行了归纳和总结,并首次成系统地分析和提出解决方案,后来这种普遍的网络现象和技术局限都被大家称为 C10K 问题。

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

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

相关文章

无线电基础电路 > RLC阻尼系数计算仿真

随机搭建电路如下图所示&#xff1a; 阻尼系数的希腊字母符号“ ζ ”读作泽塔。 阻尼系数ζ (R/2) * √C/L 1000/2 * √0.00001 1.58 包括三种情况&#xff1a; ζ>1&#xff1a;过阻尼&#xff0c;频响不利落&#xff0c;需要较长时间才能消失。 ζ<1&#xff…

MinIO基本使用(实现上传、下载功能)

MinIO基本使用&#xff08;实现上传、下载功能&#xff09;1.简介2.下载和安装3.启动服务端4.创建User和Bucket4.1 创建User4.1.1 生成accessKey和secretKey4.2 创建Bucket5.在SpringBoot中使用MinIO5.1 引入依赖5.2 配置文件定义5.3 定义实体类5.4 定义业务类5.5 定义测试类5.…

vivado中block design遇到的error总结

Error1.[BD 41-1356] Address block </processing_system7_0/S_AXI_HP0/HP0_DDR_LOWOCM> is not mapped into </axi_vdma_0/Data_MM2S>. Please use Address Editor to either map or exclude it. 修改方法. a、点击Address Editor. b、在Address Editor页面右击失…

【Ajax】了解Ajax与jQuery中的Ajax

一、了解Ajax什么是AjaxAjax 的全称是 Asynchronous Javascript And XML&#xff08;异步 JavaScript 和 XML&#xff09;。通俗的理解&#xff1a;在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式&#xff0c;就是Ajax。2. 为什么要学Ajax之前所学的技术&#xf…

使用MQTT fx测试云服务器的 mosquitto 通讯

文章目录一.MQTT.fx介绍二.MQTT.fx安装教程三.使用MQTT.fx测试云服务器的 mosquitto 通讯一.MQTT.fx介绍 MQTT.fx是一款基于Eclipse Paho&#xff0c;使用Java语言编写的MQTT客户端工具。支持通过Topic订阅和发布消息&#xff0c;用来前期和物理云平台调试非常方便。 二.MQTT…

【数据结构——顺序表的实现】

前言&#xff1a; 在之前我们已经对复杂度进行的相关了解&#xff0c;因此现在我们将直接进入数据结构的顺序表的相关知识的学习。 目录1.线性表2.顺序表2.1概念及结构2.2 接口实现2.2.1.打印顺序表2.2.2初始化顺序表2.2.3.容量的检查2.2.4.销毁顺序表2.2.5.尾插操作2.2.6.尾删…

Ubuntu下的LGT8F328P MiniEVB Arduino开发和烧录环境

基于 LGT8F328P LQFP32 的 Arduino MiniEVB, 这个板型资料较少, 记录一下开发环境和烧录过程以及当中遇到的问题. 关于 LGT8F328P 芯片参数 8位RISC内核32K字节 Flash, 2K字节 SRAM最大支持32MHz工作频率 集成32MHz RC振荡器集成32KHz RC振荡器 SWD片上调试器工作电压: 1.8V…

C语言文件操作(3)

TIPS 1. 文件是不是二进制文件&#xff0c;不是后缀说了算&#xff0c;而是内容说了算 2. 文件的随机读写 文件的随机读写也就是说我指哪打哪 fseek() 人为调整指针指向的位置 1. 根据文件指针FILE*的当前位置和你给出的偏移量来让它这个文件指针呢定位到你想要的位置上…

Flutter 这一年:2022 亮点时刻

回看 2022&#xff0c;展望 Flutter Forward 2022 年&#xff0c;我们非常兴奋的看到 Flutter 社区持续发展壮大&#xff0c;也因此让更多人体验到了令人难以置信的体验。每天有超过 1000 款使用 Flutter 的新移动应用发布到 App Store 和 Google Play&#xff0c;Web 平台和桌…

实战打靶集锦-002-SolidState

**写在前面&#xff1a;**谨以此文纪念不完美的一次打靶经历。 目录1. 锁定主机与端口2. 服务枚举3. 服务探查3.1 Apache探查3.1.1 浏览器手工探查3.1.2 目录枚举3.2 JAMES探查3.2.1 搜索公共EXP3.2.2 EXP利用3.2.2.1 构建payload3.2.2.2 netcat构建反弹shell3.2.3 探查JAMES控…

三十一、Kubernetes中Service详解、实例第一篇

1、概述 在kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;我们可以通过pod的ip来访问应用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;这也就意味着不方便直接采用pod的ip对服务进行访问。 为了解决这个问题&#xff0c;kubernetes提供了Service资源&…

NX二开ufun函数UF_MODL_ask_curve_points(获取曲线信息)

根据曲线tag&#xff0c;返回曲线相关信息&#xff1a;弦宽容、弧度、最大步长、点数组的点。 实例返回结果截图如下&#xff1a; 实例创建曲线截图如下&#xff1a; 1、函数结构 int UF_MODL_ask_curve_points &#xff08;tag_t curve_id&#xff0c; double ctol&#xf…

【SpringCloud19】SpringCloud Alibaba Sentinel实现熔断与限流

1.概述 官网 中文文档 1.1 是什么 一句话解释&#xff0c;之前我们讲解过的Hystrix 1.2 怎么下 下载网址 1.3 作用 1.4 如何使用 官网学习 服务使用中的各种问题&#xff1a; 服务雪崩服务降级服务熔断服务限流 2.安装Sentinel控制台 2.1 组成部分 核心库&#x…

Golang之实战篇(1)

"千篇一律&#xff0c;高手寂寞。几十不惑&#xff0c;全都白扯"上篇介绍了golang这门新的语言的一些语法。那么我们能用golang简单地写些什么代码出来呢&#xff1f;一、猜数字这个游戏的逻辑很简单。系统随机给你生成一个数&#xff0c;然后读取你猜的数字&#xf…

老杨说运维 | AIOps如何助力实现全面可观测性(上)

前言&#xff1a; 嗨&#xff0c;今天是大年三十&#xff0c;大家是不是已经在家坐享团圆之乐了&#xff1f;还是说在奔向团圆的路上呢&#xff1f;不论如何&#xff0c;小编先祝大家新年如意安康&#xff0c;平安顺遂~ 熟悉我们的朋友肯定都知道&#xff0c;关于《老杨说运维…

30.字符串处理函数

文章目录1.测字符串长度函数2.字符串拷贝函数1.strcpy函数2.strncpy函数3.字符串追加函数1.strcat函数2.strncat函数4.字符串比较函数1.strcmp函数2.strncmp函数5.字符查找函数1.strchr函数2.strrchr函数6.字符串匹配函数7.空间设定函数8.字符串转换数值9.字符串切割函数strtok…

【Java开发】Spring Cloud 04 :服务治理Nacos

本章节正式进入 Spring Cloud 环节了&#xff0c;首先介绍微服务架构中一个最重要的原理概念&#xff1a;服务治理&#xff0c;在概念讲解之后&#xff0c;讲解介绍 Nacos 服务注册中心的体系结构。1 服务治理1.1 服务治理介绍首先通过一个例子告诉你服务治理解决了什么问题。比…

GD32F4——外部中断

一、NVIC中断系统 Cortex-M4集成了嵌套式矢量型中断控制器&#xff08;Nested Vectored Interrupt Controller&#xff0c;NVIC&#xff09;来实现高效的异常和中断处理。 中断系统包含外部中断、定时器中断、DMA中断和串口中断等。 二、EXTI外部中断 EXTI&#xff08;中断…

go的基本语法介绍之变量的声明与初始化

1.常见基本数据类型 uint8&#xff1a;无符号8位整形&#xff0c;取值范围&#xff1a;0-255 uint16&#xff1a;无符号16位整形&#xff0c;取值范围&#xff1a;0-65535 uint32&#xff1a;无符号32位整形&#xff0c;取值范围&#xff1a;0-4294967295 uint64&#xff1…

opencv arm交叉编译与仿真验证详细流程

【关键内容】 1.将opencv编译为能在arm上运行的库 2.在没有板子的情况下&#xff0c;仿真验证opencv库 1.将opencv编译为能在arm上运行的库 1.在下方链接中选择某个版本 Releases - OpenCVhttps://opencv.org/releases/点击“Sources”即可开始下载&#xff0c;得到opencv-…