服务端高性能网络IO编程模型简析
服务端高性能网络IO编程模型简析
一、客户端与服务器端
多数网络应用可以分为客户端(client)和服务器端(server)模型,然后中间通过各种定义的协议来进行两端的通信。

比如常用的 Nginx 软件。可以把它当做一个 web 服务器。我们可以在 web 浏览器(客户端)与 Nginx 进行通信。
我们常说 Nginx 的并发服务很高,指的就是通过客户端访问服务端的用户很多,并发用户多,Nginx 服务器还能很好的进行服务。
客户端与服务端的通信涉及各种通信协议。大家最熟悉的通信协议就是 TCP/IP。根据 TCP/IP 通信服务模型如下:

上面图中的应用层协议有很多,比如最常见的就是 http 协议。
二、TCP/IP和Socket
TCP/IP 协议是一个很复杂的协议,前面文章有讲这个tcp协议。这里主要不是讲tcp协议内容,而是讲 TCP 套接字接口编程-socket。

应用层:就是接收网络数据,处理网络数据,然后返回数据给用户。比如 Nginx web 服务器就是在这一层。
数据怎么组织客户端才能识别出来呢?这就要用到协议了,这一层最有名的协议就是 HTTP 应用协议。
TCP/IP 协议是一个很复杂的协议,那在编程中怎么简化它来进行网络编程呢?
套接字 socket 网络编程 API 是起源于 BSD 操作系统。
BSD 操作系统为应用程序提供的套接字(socket) API:

建立一个TCP连接的3次握手:
1.服务器准备好接收外来连接,通过调用 socket,bind,listen 函数来完成。
2.客户端可以通过connect进行主动打开连接。客户端发起一个 tcp 的 SYN(SYN J)。
3.服务器确认客户的的 SYN,同时服务器也发送一个 SYN(SYN K) 和对客户 SYN 的 确认 ACK(ACK J + 1)。
4.客户端发送一个 ACK(ACK K + 1) 确认服务端的 SYN。

三、服务端数据处理流程
网络数据处理流程简图:

- 获取用户请求的数据:比如用户进程是Nginx服务器,那么它就要获取用户请求的数据
- 处理用户数据:服务器获取用户请求数据,然后进行业务逻辑处理,构建响应用户的数据
- 最后返回数据给用户
上面是从数据的流动方向来看待数据处理过程。
如果从用户空间的数据到内核空间的数据,IO模型来看,数据传递方式有 5 种IO模型,见这篇文章:5种IO模型。
上面图片显示的是一个用户访问 web 应用服务器的数据流方向。如果是多个用户访问,成百上千的用户访问,服务端怎么处理这么多用户呢?
web 服务器会不会崩溃?
下面就来看看多用户的服务器端处理模型。
四、高性能IO服务模型
上面说到成百上千的用户访问 web 服务器,从服务端编程角度来说,就涉及到了服务端并发编程,服务器端怎么处理这么多用户访问呢?
一种方法就是为每个客户端用户创建一个独立的处理流程,这就是并发程序。常见的并发编程有三种,多进程,多线程,最后一个 I/O 多路复用。
- 多线程/多进程

客户端的每一个用户访问服务端产生的连接,就起一个线程/进程来进行服务处理(如上图)。
如果用户数过多,并发数就会很大,那么所创建的线程/进程也会很多,服务端系统资源可能会耗光。
还有服务端的read,accept等函数基本用法都是阻塞的,从5种IO模型也可以看出来。
- I/O多路复用
从上面第二小节可以得知,一般的服务端网络编程步骤,socket,bind,listen,accept,read,write,close,这几个
步骤。首先,有这么多步骤,可不可以分步骤来处理,当然是可以的。再次,换一种理解方法,把这些步骤看成是一次处理事件(Event),
accept 接收socket描述符事件,read、write 分别是读事件、写事件。如果我们把 accept 接收的socket描述符存放在一个列表中,
只要知道在列表中的socket描述符准备好了读、写事件,那么我们就可以去进行读和写操作了。这就是IO多路复用最基本的由来。
操作系统准备了3个IO多路复用的函数:select,poll,epoll。最常用的是 epoll。
epoll 是对 select 和poll 的改进:
- 不需要每次向内核传入文件socket文件描述符,内核自己保存了一份
- 不像poll通过轮询的方式来找出就绪的文件描述符,而是通过异步IO事件来通知用户
- 内核会通过 IO 事件告诉用户就绪的文件描述符
有人把这种模式叫做 Reactor 模式,也叫事件分发(event dispatch)模式。

五、Doug Lea 介绍的可伸缩高性能IO模型
一般网络服务处理流程
java.util.concurrent 包的作者 Doug Lea 有一篇分析与构建可伸缩的高性能 IO 服务的一篇经典文章:《Scalable IO in Java》。虽然是 java 描述,但是编程思想是相通的。
这篇文章中总结了一般的网络服务或分布式服务等应用程序中,大都具备的一些相同处理流程:
Read Request,读取请求数据
Decode request,解码请求数据
Process service,对数据进行加工处理
Encode reply,对返回数据进行编码
Send reply,发送返回数据
在实际应用中,每一步的运行效率都是不同的。
传统服务器IO模型
一般的网络服务中会为每一个连接开启一个新的线程、进程,如下图:

(来自《Scalable IO in Java》作者:Doug Lea)
上面这种模式有个缺点就是如果用户成百上千,那么系统资源会逐渐耗尽。那怎么才能构建高性能可伸缩的IO服务。作者列了几点希望达成的目标:
在海量负载连接情况下能够优雅降级
能够随着硬件资源的增加,应用性能能随着持续改进
具备低延时、高吞吐量、可调节的服务治理
基于事件驱动模式的设计
基于事件驱动的架构设计通常比其他架构设计模型更加的有效率,可以节省一定的资源,事件驱动模式不需要为每一个客户端建立一个线程,这意味着更少的资源开销、上下文切换等。但事件任务调度可能会慢一些,通常实现复杂度也会增加。相关功能可以分解成非阻塞操作,但不可能把所有阻塞消除。
由于是时间驱动,所以需要跟踪事件任务相关的状态,知道在什么时候会发生事件,就可以进行相应的处理。
Reactor模型(分发模式)
分发模式几个机制:
- 将一个完整的处理过程分解为一个一个小的任务或事件来处理
- 每个任务执行相关的动作而且不产生阻塞
- 在任务执行状态触发时才会执行,比如只有有数据时才会触发读操作
在服务器网络开发中,多数处理的是 IO 事件,当某个 IO 事件准备就绪时,然后触发某时间的操作。

(来自《Scalable IO in Java》作者:Doug Lea)
Reactor 也可以称为反应器模式:
1.Reactor模式中会通过分配适当的handler(处理程序)来响应IO事件
2.每个handler执行非阻塞的操作
3.通过将handler绑定到事件进行管理
1. 单线程模式
下图展示的就是单线程下基本的Reactor设计模式

单 Reactor 单线程模式 (来自《Scalable IO in Java》作者:Doug Lea)
2. 多线程模式
在多处理器常见下,为了实现服务器更高性能可以采用多线程模式:
- 增加 worker 线程,专门处理非 IO 操作
- 拆分并增加反应器 Reactor 线程,一方面在压力较大时可以饱和处理 IO 操作,提高处理能力。另外多个 Reactor 线程也可以做负载均衡使用。线程的数量可以根据程序本身是 CPU 密集型还是 IO 密集型操作来进行合理的分配

单 Reactor 多线程模式 (来自《Scalable IO in Java》作者:Doug Lea)
3. 主从 Reactor 多线程模式

Proactor 模式
当然还有一个 Proactor 模式,它是异步 IO 相关。
前面文章 5种IO模型,异步 IO 模型有关。
Proactor 在时间发生时基于异步 IO 完成读写操作(由内核完成),等待 IO 完成后才返回到应用程序进行逻辑的处理。而 Reactor 通知读写事件是在应用程序中完成的。
参考
- 《Unix网络编程第1卷》 作者: [Richard Stevens, Bill Fenner,Andrew M. Rudoff
- 《Scalable IO in Java》 Doug Lea 关于分析与构建可伸缩的高性能IO服务一篇经典文章
服务端高性能网络IO编程模型简析的更多相关文章
- 金蝶 K/3 Cloud 服务端控件编程模型
如下图是服务端已有的控件编程模型
- TCP和UDP的区别以及使用python服务端客户端简单编程
一.TCP.UDP区别总结 1.TCP面向连接(如打电话要先拨号建立连接):UDP是无连接的,即发送数据之前不需要建立连接 2.TCP提供可靠的服务,也就是说,通过TCP连接传送的数据,无差错,不丢失 ...
- C#开发BIMFACE系列17 服务端API之获取模型数据2:获取构件材质列表
系列目录 [已更新最新开发文章,点击查看详细] 在上一篇<C#开发BIMFACE系列16 服务端API之获取模型数据1:查询满足条件的构件ID列表>中介绍了获取单文件(模型)的所有 ...
- C#开发BIMFACE系列18 服务端API之获取模型数据3:获取构件属性
系列目录 [已更新最新开发文章,点击查看详细] 本篇主要介绍如何获取单文件/模型下单个构建的属性信息. 请求地址:GET https://api.bimface.com/data/v2/fil ...
- C#开发BIMFACE系列19 服务端API之获取模型数据4:获取多个构件的共同属性
系列目录 [已更新最新开发文章,点击查看详细] 在前几篇博客中介绍了一个三维文件/模型包含多个构建,每个构建又是由多种材质组成,每个构建都有很多属性.不同的构建也有可能包含相同的属性. 上图中 ...
- C#开发BIMFACE系列21 服务端API之获取模型数据6:获取单模型的楼层信息
系列目录 [已更新最新开发文章,点击查看详细] 一个文件/模型中可能包含多个楼层信息,获取楼层信息对于前端页面的动态展示非常有帮助.本篇介绍获取一个文件/模型中可能包含多个楼层信息的详细方法. ...
- C#开发BIMFACE系列24 服务端API之获取模型数据9:获取单个房间信息
系列目录 [已更新最新开发文章,点击查看详细] 大厦建筑模型中,基本上包含多个楼层,每个楼层包含多个房间等信息.在<C#开发BIMFACE系列21 服务端API之获取模型数据6:获取单模 ...
- C#开发BIMFACE系列25 服务端API之获取模型数据10:获取楼层对应面积分区列表
系列目录 [已更新最新开发文章,点击查看详细] 在<C#开发BIMFACE系列22 服务端API之获取模型数据7:获取多个模型的楼层信息>中,返回的楼层信息结果中包含了楼层的具体信 ...
- C#开发BIMFACE系列22 服务端API之获取模型数据7:获取多个模型的楼层信息
系列目录 [已更新最新开发文章,点击查看详细] 在<C#开发BIMFACE系列21 服务端API之获取模型数据6:获取单模型的楼层信息>中介绍获取单个模型的所有楼层信息.某些场景下 ...
- C#开发BIMFACE系列26 服务端API之获取模型数据11:获取单个面积分区信息
系列目录 [已更新最新开发文章,点击查看详细] 在<C#开发BIMFACE系列25 服务端API之获取模型数据9:获取楼层对应面积分区列表>一文中介绍了如何获取单个模型中单个楼层包 ...
随机推荐
- [转帖]Shell脚本数组(实现冒泡排序,直接选择排序,反转排序)
目录 数组 数组定义方法 数组包括的数据类型 获取数组长度 读取某下标赋值 数组遍历 数组切片 数组替换 删除数组 追加数组中的元素 从函数返回数组 加法传参运算 乘法传参运算 数组排序算法 冒泡排序 ...
- [转帖]TiDB BR 备份至 MinIO S3 实战
https://tidb.net/blog/3a31d41d#3.%E9%83%A8%E7%BD%B2%20MinIO%20S3%20%E5%8F%8A%E5%A4%87%E4%BB%BD%E6%81 ...
- 【转帖】ESXi 6.x 安装storcli监控raid卡状态
https://b2b.baidu.com/land?id=744541c6188f7937d6dc97d6fb9142ff10 脚本宝典收集整理的这篇文章主要介绍了ESXi 6.x 安装storcl ...
- [转帖] Linux命令拾遗-使用blktrace分析io情况
https://www.cnblogs.com/codelogs/p/16060775.html 原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 一般来说,想检 ...
- [转帖]VCSA证书过期问题处理
1. 故障现象 2022年10月25日,登陆VC报错. 按照报错信息,结合官方文档,判断为STS证书过期导致. vCenter Server Appliance (VCSA) 6.5.x, 6.7. ...
- [转帖]JAVA之G1垃圾回收器
https://www.cnblogs.com/boanxin/p/12292331.html 概述 G1 GC,全称Garbage-First Garbage Collector,通过-XX:+Us ...
- Sysbench简单测试数据库性能
摘要 先进行了一个PG数据库的测试. Mysql数据库的测试稍后跟上. 紧接着上一篇的安装, 部分文件可能需要特定路径才可以. sysbench 测试的说明 一个参数 这里稍微说一下参数的问题 sys ...
- 让你彻底理解TypeScript中的readonly
1.readonly的讲解 readonly修饰符,首先是一个关键字 对类中的属性成员进行修饰修饰之后,该属性成员就不能修改了.只能够进行访问 在构造函数中是可以对只读属性(readonly)进行修改 ...
- miniIO系列文章03---abpvext中集成
在Abp商业版本中已经提供了文件管理模块的,免费版本是没有的,本文将介绍如何使用Minio打造一个自己的文件管理模块. 在项目开始之前,需要先安装一个Minio服务,可以在本地pc或云主机中安装,具体 ...
- Python 实现指定窗口置顶激活
通过Python实现对特定窗口的置顶操作以及对特定窗体发送按键,这里需要安装一个第三方pip包,执行命令pywin32安装好以后,我们运行试试. 第一个案例,遍历所有句柄,然后对特定窗口进行最大化或最 ...