写点什么

linux 高性能服务器编程 -- 高性能服务器程序框架

用户头像
赖猫
关注
发布于: 2021 年 04 月 26 日

主要介绍服务器的三个主要模块: I/O 处理单元、逻辑单元、存储单元。另外服务器的模型有:C/S 模型和 P2P 模型。虽然服务器模型比较多,但是其核心框架都一样,只是在于逻辑处理方面。如下图简单的介绍一台服务器或服务器机群模型的基本框架:

1、I/O 模型

I/O 处理单元:I/O 处理单元是服务器管理客户连接的模块。主要是等待并受理新的客户连接接收客户数据,将服务器响应数据返回给客户端。

逻辑单元:就是一个个进程或者线程。用于处理客户数据,将结果传递给 I/O 处理单元或者直接发送给客户端。服务器中通常由多个逻辑单元,以实现多个客户端任务的并行处理。

网络存储单元:网络存储单元用于存储数据库、缓存以及文件。

请求队列:请求队列是各个单元之间的通信方式的抽象。请求队列通常被实现为池的一部分。请求队列是各台服务器之间预先建立的静态的永久的 TCP 连接。

I/O 模型 有阻塞 I/O 模型和 非阻塞 I/O 模型。因为 socket 在创建的时候默认是阻塞的,在创建 socket 的时候讲第二个参数设置为 SOCK_NONBLOCK 标志,或通过 fcntl 系统调用 F_SETFL 命令,将其设置为非阻塞的。对于阻塞 I/O 执行的系统调用会因为无法立即完成而被操作系统挂起,直到等待事情发生为止。比如:1)客户端通过 connect 向服务器端发起连接,connect 将发送同步报文段给服务器然后等待服务器返回确认报文段。2)如果服务器确认报文段没有立即到达客户端,则 connect 调用将被挂起,直到客户端收到确认报文段唤醒 connect 调用。在 socket 的基础 API 中,可能被阻塞的系统调用包括 accept send recv 和 connect.

对于非阻塞 I/O 总是需要和其他 I/O 通知机制一起使用,如果不和其他通知机制一起使用还是阻塞的。比如:I/O 复用 和 SIGIO 信号等 另行去处理 I/O,处理是异步的。 在非阻塞 I/O 执行系统调用总是立即返回一个(通知事件结果)。不管事件是否已经发生都会返回。所以对于非阻塞 I/O 就需要根据 errno 来区分成功还是失败的情况。事件返回的结果类型有(再来一次 EAGAIN) (期望阻塞 EWOULDBLOCK) (在处理中 EINPROGRESS) 。

I/O 复用是最常用的 I/O 通知机制。例如:应用程序通过 I/O 复用函数向内核注册一组事件,内核通过 I/O 复用函数把其中就绪的事件通知给应用程序。Linux 上常用的 I/O 复用函数有 select pull epoll_wait. 它们能提高程序效率的原因在于它们具有同时监听多个 I/O 事件能力。

理论上来说,阻塞 I/O 和 I/O 复用 以及信号驱动 I/O 都是属于同步 I/O 模型。因为 I/O 的读写操作都是在 I/O 事件发生之后由应用程序完成的。而异步 I/O 的读写都是立即返回的,不论是否阻塞,因为真正的读写操作已经由内核完成了。也就是说:同步 I/O 模型要求用户代码自行执行 I/O 操作,将数据从内核缓存区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区。而异步 I/O 机制则有内核来执行 I/O 操作,数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的。

可以总结 为同步 I/O 像应用程序通知的是 I/O 就绪事件、而异步 I/O 向应用程序通知的是 I/O 完成事件。

I/O 模型对比

1)阻塞 I/O : 程序阻塞于读写函数;

2)I/O 复用: 程序阻塞于 I/O 复用系统调用,但可同时监听多个 I/O 事件。对 I/O 本身的读写操作是非阻塞的。

3)SIGIO 信号: 信号触发读写就绪事件,用户陈谷执行读写操作。程序没有阻塞阶段。

4)异步 I/O: 内核执行读写操作并触发读写完成事件。程序没有阻塞阶段。  

2、Reactor 和 Proactor 事件处理模式

reactor的5种实现方式

事件处理的两种模式:Reactor 和 Proactor 通常服务器程序需要处理三类事件:I/O 事件、信号、定时事件。同步 I/O 模型通常使用 Reactor 模式,异步 I/O 模式用 Proactor 处理。也可以通过同步 I/O 模拟出 Proactor 模式;

Reactor 模式:她只要求主线程(i/o 处理单元)监听文件描述上是否有事件发生,有就立即通知通知工作线程(逻辑单元)处理任务。所有的读写处理数据都在线程上执行。使用同步 I/O 模型(epoll_wait 为例)实现 Reactor 模式工作流程是:

1)主线程往 epoll 内核事件表注册 socket 上的读就绪事件。

2)主线程调用 epoll_wait 等待 socket 上有数据可读。

3)当 socket 上有数据可读时,epoll_wait 通知主线程,主线程则将 socket 可读事件放入请求队列。

4)请求队列上的某一个线程将会被唤醒。它从 socket 读取数据,并处理客户请求,然后往 epoll 内核事件表中注册 socket 上的写就绪事件。

5)主线程调用 epoll_wait 等待 socket 可写。

6)当 socket 可写时,epoll_wait 通知主线程。主线程将 socket 可写事件放入请求队列。

7)请求队列上的某一个线程将会被唤醒。

Proactor 模式:她是将 I/O 操作都交给主线程和内核处理。工作线程仅负责业务逻辑。使用异步 I/O(aio_read 和 aio_write 为例) 实现 Proactor 模式。

1)主线程调用 aio_read 函数向内核注册 socket 上的读完成事件,并告诉内核用户读缓冲区的位置,以及读操作完成时如何通知应用程序。

2)主线程继续处理其他逻辑。

3)当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,已通知应用程序数据已可用。

4)应用程序预先定义好信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核用户写缓冲区的位置,以及写操作完成时如何通知应用程序。

5)主线程继续处理其他逻辑。

6)当用户缓冲区的数据被写入 socket 之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。

7)应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,决定是否关闭 socket.如下图:

需要 C/C++ Linux 服务器架构师学习资料加群 960994558 获取(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享

3、池

线程池如何做到最高效,线程的效率真的比进程高?

池有很多种,常见的有 内存池、进程池、线程池、连接池。池是在服务器启动时预先初始化创建好的长连接。已达到空间换时间的概念提高效率。当然逾期初始化好的数据对它的大小就难以把控了,当然也可以动态扩容。

C/C++Linux服务器开发/架构师

用户头像

赖猫

关注

C/C++Linux服务器开发学习群960994558 2020.11.28 加入

纸上得来终觉浅,绝知此事要躬行

评论

发布
暂无评论
linux高性能服务器编程--高性能服务器程序框架