欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > 【C++高并发服务器WebServer】-17:阻塞/非阻塞和同步/异步、五种IO模型、Web服务器

【C++高并发服务器WebServer】-17:阻塞/非阻塞和同步/异步、五种IO模型、Web服务器

2025/2/11 5:15:14 来源:https://blog.csdn.net/theaipower/article/details/145537458  浏览:    关键词:【C++高并发服务器WebServer】-17:阻塞/非阻塞和同步/异步、五种IO模型、Web服务器

在这里插入图片描述

本文目录

  • 一、阻塞/非阻塞、同步/异步
    • 1.1 辨析
    • 1.2 异步io接口
  • 二、五种IO模型
    • 2.1 阻塞 blocking 模型
    • 2.2 非阻塞 NIO 模型
    • 2.3 IO多路复用
    • 2.4 信号驱动Signal-driven
    • 2.5 异步
  • 三、Web Sever 网页服务器
    • 3.1 HTTP的请求响应步骤
    • 3.2 HTTP请求与响应报文格式
    • 3.3 HTTP请求方法
    • 3.4 HTTP状态码
    • 3.4 服务器编程基本框架

一、阻塞/非阻塞、同步/异步

1.1 辨析

本节的阻塞与非阻塞、同步与异步均指网络I/O

首先我们明确,典型的一次IO的两个阶段是 数据就绪数据读写

数据就绪是指根据系统IO操作的就绪状态,从而有阻塞和非阻塞的说法。
数据读写是指根据应用程序和内核的交互方式,从而有同步和异步的说法。

我们可以以下面这个图进行对应的说明。

在这里插入图片描述
阻塞:当调用IO的函数,当数据还没有准备好(对方可能还没有发送过来),那么就会处于阻塞,这个时候线程会处于一个挂起的状态。线程挂起就不会再占用cpu的资源了。

非阻塞:当调用IO的函数,当数据还没有准备好(对方可能还没有发送过来),程序并不会挂起,而是会立即返回,并且继续往下执行,这个时候需要通过返回值进行一个判断(判断数据是否发过来了)。

比如之前接触过的ssize_t recv(int sockfd,void * buf,size_t len,int flags)这个函数(默认是阻塞的属性)。

sockfd就对应了操作系统在内核的接收缓冲区,假设recv设置了阻塞属性,那么如果对方已经把数据发过来,就不会处于阻塞,但是如果数据没有到达,就会把线程挂起。

假设如果recv函数设置了非阻塞的属性,当没有数据到达,那么函数会立即返回一个数值,我们需要判断这个数值,判断一些情况。判断的情况现在进行对应的说明(例如当返回-1时,需要对EINTR、EAGAIN、EWOULDBLOCK等进行特判,这几个情况并不是错误)。

int size = recv(sockfd,buf,1024,0) ,如果返回size=-1,就代表出错了,但是会设置对应的错误的errno

但是当是某些情况的时候,需要我们进行特殊处理。比如EINTR,当信号产生后会中断程序,中断后再回来之后会返回-1,并且标记错误为EINTR)。还比如EAGAIN / EWOULDBLOCK,表示没有得到数据,需要再读一次。

如果返回size=0,代表读取到数据的末尾,对方连接关闭。

如果返回size>0,代表读取到了多少的数据。

在这里插入图片描述
接下来来讲讲同步异步。

recv是一个“同步”函数,因为是需要我们自己去调用和读取到buf中(个人见解),需要等sockfd中数据复制到buf中后才会返回,并且在没有完成之前,并不会返回,此时程序并不会往下继续执行。

简单来说就是需要我们应用层自己去调用这个recv完成相关的读写。

异步一般需要操作系统给我们提供异步IO接口,一般需要把sockfd、buf、通知方式给操作系统,接下来应用程序可以继续往下执行其他操作,当内环缓冲区有数据之后,操作系统会自动帮我们把这个数据放到buf当中,然后再通知我们(通过sigio信号,告知我们buf的数据已经准备好了)。

也就是说,在同步I/O操作中,应用程序负责主动调用I/O函数来请求数据传输。以recv函数为例,应用程序调用recv后,如果数据尚未准备好,recv会阻塞当前线程,直到数据从套接字缓冲区复制到用户提供的缓冲区中。这种阻塞行为意味着应用程序在等待I/O操作完成期间不能执行其他任务。同步I/O通常适用于数据传输量不大、实时性要求不高的场景。

与同步I/O不同,异步I/O允许应用程序在发起I/O请求后立即返回,继续执行其他任务,而不必等待I/O操作完成。操作系统会在数据准备好后,通过某种机制(如信号、回调函数或事件通知)告知应用程序。这种机制使得应用程序可以在数据到达时得到通知,而不必不断轮询检查。

异步I/O通常需要操作系统提供专门的异步I/O接口,如aio_read、aio_write等。这些接口允许应用程序指定I/O操作的参数,包括缓冲区地址、数据长度等,并将控制权返回给应用程序。当I/O操作完成时,操作系统会通过注册的回调函数或信号处理程序通知应用程序。

异步I/O通常能更好地利用系统资源,因为它允许应用程序在等待I/O操作完成时执行其他任务,从而提高程序的并发性和响应性。但异步I/O的编程模型通常比同步I/O更复杂,因为它需要处理回调函数、信号处理等机制,这可能会增加代码的复杂性和调试难度。

同步I/O:适用于I/O操作不频繁、数据量不大、实时性要求不高的场景。例如,简单的文件读写、少量的数据传输等。

异步I/O:适用于需要处理大量并发连接、数据量大、实时性要求高的场景。例如,高性能的网络服务器、实时数据处理系统等。

陈硕大神说过一句话:处理IO的时候,阻塞和非阻塞都是同步IO(都需要我们用户自己去操作数据),只有使用了特殊的API才是异步IO(比如linux中的aio_read接口)

epoll并不是异步的,这个需要弄清楚。明确这个数据是不是我们应用程序去主动操作就行,如果是,那就是同步,如果是操作系统帮我们直接搬运好到buf中,那就是异步。(IO多路复用都是同步的。)

在这里插入图片描述
一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪” 和 “数据读写”,数据就绪阶段分为阻塞和非阻塞,表现得结果就是,阻塞当前线程或是直接返回。

同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞);异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。

1.2 异步io接口

man aio_read来查看linux系统文档中的异步io说明。

可以看到其函数的参数是一个类型为aiocb *aiocbp的一个结构体。

在这里插入图片描述

结构体定义如下,是一个异步IO的控制块。
(异步一般是跟非阻塞搭配使用的。)

在这里插入图片描述

二、五种IO模型

2.1 阻塞 blocking 模型

很简单来阐述,就是必须等待函数返回,才能往下执行。
在这里插入图片描述

2.2 非阻塞 NIO 模型

非阻塞等待,每隔一段时间就去检测I0事件是否就绪。没有就绪就可以做其他事。非阻塞I/0执行系统调用总是立即返回,不管事件是否已经发生,若事件没有发生,则返回-1,此时可以根据 errno 区分这两种情况,对于accept,recv和send,事件未发生时,errno通常被设置成 EAGAIN。

在这里插入图片描述

2.3 IO多路复用

Linux 用 selectpoll/epoll 函数实现 IO复用模型,这些函数也会使进程阻塞,但是和阻塞IO所不同的是这些函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的!0函数进行检测。直到有数据可读或可写时,才真正调用I0操作函数。

在这里插入图片描述

2.4 信号驱动Signal-driven

Linux 用套接口进行信号驱动 IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进程收到SIGIO 信号,然后处理 IO 事件。

内核在第一个阶段是异步,在第二个阶段是同步;与非阻塞IO的区别在于它提供了消息通知机制,不需要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。

在这里插入图片描述

2.5 异步

Linux中,可以调用 aio_read 函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。
在这里插入图片描述

三、Web Sever 网页服务器

WebServer就是一个服务器软件程序,或者运行这个服务器软件的硬件(计算)。主要是通过HTTP协议与客户端进行通信,接收请求,然后做出HTTP响应。

通常用户使用 Web 浏览器与相应服务器进行通信。在浏览器中键入“域名"或"IP地址:端口号”,浏览器则先将你的域名解析成相应的 IP 地址或者直接根据你的IP地址向对应的 Web 服务器发送一个 HTTP 请求。这一过程首先要通过 TCP 协议的三次握手建立与目标 Web 服务器的连接,然后 HTTP 协议生成针对目标 Web 服务器的 HTTP 请求报文,通过 TCP、IP 等协议发送到目标 Web 服务器上。

HTTP假定其下层协议必须提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用,因此也就是其在TCP/IP协议族使用TCP作为其传输层。

3.1 HTTP的请求响应步骤

在这里插入图片描述

3.2 HTTP请求与响应报文格式

在这里插入图片描述
在这里插入图片描述

3.3 HTTP请求方法

GET:用于请求指定资源的数据。GET 请求应该只被用于获取数据,并且不会对服务器上的数据产生任何影响。

POST:用于向指定资源提交数据,通常导致服务器上的数据被创建或修改。POST 请求通常用于表单提交。

PUT:用于将请求体中包含的数据替换指定资源的全部内容。如果该资源不存在,服务器可能会根据请求创建新资源。

DELETE:用于删除指定的资源。

HEAD:类似于 GET 请求,但不返回响应体。HEAD 请求用于获取资源的元数据,如 HTTP 头信息。

OPTIONS:用于获取目标资源所支持的通信选项。这个方法经常用于跨源资源共享(CORS)请求,以确定服务器支持哪些 HTTP 方法。

PATCH:用于对已知资源进行部分修改。与 PUT 不同,PATCH 通常用于对资源进行小的、局部的修改。

CONNECT:将连接改为管道方式的代理请求。CONNECT 方法主要用于创建隧道,例如,在代理服务器上建立 SSL 加密通道。

3.4 HTTP状态码

在这里插入图片描述

3.4 服务器编程基本框架

I/O处理单元是负责客户端连接,读写网络数据。逻辑单元是业务进程或者线程。网络存储单元是指数据库、文件、缓存等。请求队列则是指各单元之间的通信方式。

IO处理单元是服务器管理客户连接的模块。它通常要完成以下工作:等待并接受新的客户连接,接收客户数据,将服务器响应数据返回给客户端。但是数据的收发不一定在 1/0 处理单元中执行,也可能在逻辑单元中执行,具体在何处执行取决于事件处理模式。

一个逻辑单元通常是一个进程或线程。它分析并处理客户数据,然后将结果传递给 IO 处理单元或者直接发送给客户端(具体使用哪种方式取决于事件处理模式)。服务器通常拥有多个逻辑单元,以实现对多个客户任务的并发处理。

网络存储单元可以是数据库、缓存和文件,但不是必须的。

请求队列是各单元之间的通信方式的抽象。I/0 处理单元接收到客户请求时,需要以某种方式通知一个逻辑单元来处理该请求。同样,多个逻辑单元同时访问一个存储单元时,也需要采用某种机制来协调处理竞态条件。请求队列通常被实现为池的一部分。
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com