服务端处理网络请求的流程图
主要步骤包括
①获取请求数据: 客户端与服务器建立连接发出请求,服务器接受请求(1-3)
②构建响应:在用户空间处理客户端请求,直到构建响应完成(4)
③返回数据:将构建好的响应再通过内核空间的网络I/O发还给客户端
所以,服务端有两个关键点
①如何管理连接,获取输入数据
②服务器如何处理请求
阻塞与非阻塞
阻塞:被调用方在收到请求到返回结果之前的这段时间,调用方将一直等待。
非阻塞:调用方可以去忙别的事
所以,阻塞与非阻塞,主要说的是调用方
同步处理与异步处理
同步:被调用方得到最终结果,才返回。
异步:被调用方先返回应答,然后在计算结果,计算完最终结果再通知并返回。
所以,同步与异步,主要说的是被调用方
优点:在阻塞等待数据期间,进程/线程挂起,基本不会占用CPU资源
缺点:每个连接需要单独的线程/进程单独处理,并发大时线程切换开销大
优点:不会阻塞在内核等待数据,每次可以立马返回,实时性较好
缺点:会占用大量的CPU时间,系统资源利用率低
之前我们的I/O阻塞模型不是会创建大量连接?那现在我们就先注册到select上,让select有数据了再通知我们。
优点:可以基于一个阻塞对象,同时在多个描述符上进行等待就绪,而不是使用多个线程。
缺点:连接数较少时效率相比多线程+阻塞I/O模型,效率较低,延时会更大。因为单个连接处理要经过2次系统调用,占用时间会增加
线程模型
Reactor模型
针对阻塞I/O模型进行了改进
①基于I/O复用模型,多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需等待所有连接。
②基于线程池复用线程资源,不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。
Reactor模式也叫Dispatcher模式,即I/O多了复用同一监听事件,收到事件后进行分发
Reactor模式中的两个关键组成
①Reactor,在单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对I/O事件做出反应。
②Handlers,处理程序执行I/O事件要完成的实际事件。
根据Reactor的数量和处理资源池线程的数量不同。
有3中典型实现:
①单Reactor单线程
②单Reactor多线程
③主从Reactor多线程
单Reactor单线程
说明:
①Reactor对象通过select监听客户端请求时间,收到事件后通过dispatch进行分发
②建立连接请求事件,则由Acceptor通过accept处理连接请求,然后创建Handler对象处理连接完成后的后续业务处理
③如果不是连接事件,则Reactor会分发调用连接对应的Handler来相应
④Handler会完成read->业务处理->send的完整业务流程
优点:简单明了,没有多线程竞争
缺点:
性能问题:只有一个线程,无法发挥多线程的优势
可靠性问题:线程死了,会导致整个通信模块不可用。
使用场景:客户端数量有限,业务处理非常快速,如Redis
单Reactor多线程
①Reactor对象通过select监听客户端请求时间,收到事件后通过dispatch进行分发
②建立连接请求事件,则由Acceptor通过accept处理连接请求,然后创建Handler对象处理连接完成后的后续业务处理
③如果不是连接事件,则Reactor会分发调用连接对应的Handler来相应
④Handler只负责响应事件,不做业务处理,通过read读取数据后,分发给后面的Worker线程池进行业务处理
⑤Worker线程池分配独立的线程完成真正的业务处理
⑥Handler收到响应结果后通过send将结果返回给client
优点:可以充分利用多核CPU
缺点:Reactor在高并发下容易成为性能瓶颈
主从Reactor多线程
①Reactor主线程MainReactor对象通过select监听客户端请求时间,收到事件后由Acceptor通过accept处理连接请求,建立连接请求事件
②Acceptor处理建立连接事件后,MainReactor将连接分配给SubReactor进行处理
③SubReactor将连接加入到连接队列进行监听,并创建一个Handler用于处理连接事件
④当新事件发生时,SubReactor会调用连接对应的Handler进行响应。
⑤Handler只负责响应事件,不做业务处理,通过read读取数据后,分发给后面的Worker线程池进行业务处理
⑥Worker线程池分配独立的线程完成真正的业务处理
⑦Handler收到响应结果后通过send将结果返回给client
优点:父线程与子线程数据交互简单,职责明确,父线程只需要接收新连接,子线程完成后续的业务处理
使用场景:Ngnix主从多进程模型,Netty主从多线程模型