这里是Themberfue
在学习完简单的网络编程后,我们将更加深入网络的学习------HTTP协议、TCP协议、UDP协议、IP协议...........
IO多路复用
✨在上一节基于 TCP 协议 编写应用层代码时,我们通过一个线程处理连接的申请,随后通过多线程或者线程池来处理对每个客户端的请求;这样,一个客户端就对应一个线程。
但随着客户端的数量不断增加,反复的创建和销毁线程,其开销都是很大的,哪怕是通过线程池处理,开销也是不容小觑的。
✨所以,为了解决这一问题,IO多路复用便诞生了:传统的IO操作是 阻塞式 的,当程序等待一个IO操作时,其他的IO操作会挂起,而 IO多路复用 允许程序同时监视多个 IO通道,从而不用为每个IO通道都单独地创建线程或进程。
简单来说,就是一个线程就可以同时处理多个客户端的请求。
✨举个例子:在大学生活中,给室友带饭是非常常见的的现象,假如你现在给两个室友带饭(总共买三份,你自己也得吃啊),你就要出去买三份晚餐,假设这三份晚餐都是在不一样的小吃摊贩卖的。
这里的视角是,你是服务端,小吃摊老板是客户端,小吃摊老板做完后给你相当于给你发送请求,代入这个视角更好理解~~~
**第一种购买方案:**你到了小吃摊A跟老板A说要一份晚餐,你等待老板A做完后,立刻去小吃摊B跟老板B说要一份晚餐,你等待老板B做完后,立刻去小吃摊C跟老板C说要一份晚餐,你等待老板C做完后;OK,此时全部请求处理完了。这里就相当于是单个线程处理完一个请求后立马处理下一个请求。
第二种购买方案: 你叫上其他两个认识的人(这里以甲乙丙代称),和你一起去买,甲 到小吃摊A小吃摊A跟老板A说要一份晚餐,乙 到小吃摊B跟老板B说要一份晚餐,丙到小吃摊C跟老板C说要一份晚餐;等到三份晚餐都做完后,全部请求也就处理完了。这里就相当于是创建了多个线程同时处理多个请求。
第三种购买方案: 依旧是你一个人买三份晚餐,但与第一种方案不同的是,你到了小吃摊A跟老板A说要一份晚餐,此时你立刻去小吃摊B跟老板B说要一份晚餐,随后又去吃摊C跟老板C说要一份晚餐;然后你就开始等他们做了,小吃摊A老板A做完后,你去拿,小吃摊B老板B做完后,你去拿,小吃摊C老板C做完后,你去拿;拿完全部之后,全部请求也就处理完了。这里就只用了一个线程就同时处理了多个请求。这便是 IO多路复用 的形象比喻。
✨一般来说,绝大部分客户端,大部分时间都是沉默的,虽然连接了,但不会一直发送请求,此时工作线程等待就行了,等到客户端发来请求后,工作线程处理就OK了;很小的概率会说多个客户端同时发送请求的,这时候就灵活切换到线程池就可以了~~~
✨目前,IO多路复用 是服务器开发中主流的核心技术;不过这并不需要程序员自己实现,这是操作系统内置的,只需要调用API即可 ;Java通过 NIO 封装了 IO多路复用。
❤️以下是GPT-4o对 IO多路复用 的简单讲解:
IO 多路复用(I/O Multiplexing)
IO 多路复用是一种高效管理多个 IO 通信通道的技术,常用于需要同时处理多个网络连接或文件描述符的场景,比如高并发服务器的设计。它通过一种机制,让程序可以同时监听多个 IO 事件(如读、写、错误),并根据事件的发生进行相应处理。
1. 什么是 IO 多路复用?
传统的 IO 操作是 阻塞式 的,当程序等待一个 IO 操作完成时,其他的 IO 操作会被挂起。IO 多路复用允许程序同时监视多个 IO 通道,而不用为每个通道都创建独立的线程或进程。
2. 核心思想
IO 多路复用通过一个单独的线程或进程,利用系统调用 轮询 或 事件通知 的方式,等待多个文件描述符上发生的事件,并在事件发生时通知应用程序处理。
3. 常见的 IO 多路复用机制
select
- 原理 :
通过将一组文件描述符放入一个集合中,交给内核监听。内核阻塞直到其中一个文件描述符准备就绪。- 特点:
- 跨平台支持好。
- 文件描述符数量有限制(通常是 1024)。
- 缺点:
- 每次调用都需要重新设置监听集合。
- 随着文件描述符增多,性能下降。
poll
- 原理 :
类似于select
,但用链表代替固定大小的集合。- 特点:
- 无文件描述符数量限制。
- 支持更大规模的并发连接。
- 缺点:
- 仍需轮询文件描述符,性能会随描述符增多而下降。
epoll
- 原理 :
提供事件驱动机制,支持 边缘触发 和 水平触发 模式。- 特点:
- 只需注册一次文件描述符,后续只需监听。
- 高效,适合高并发场景。
- 优点:
- 内核维护了一个事件表,避免了重复传递描述符集合。
- 边缘触发模式减少了无效通知。
4. IO 多路复用与传统模型的比较
模型 描述 优缺点 阻塞 IO 程序阻塞直到 IO 完成。 简单直观,但效率低。 非阻塞 IO 程序不断轮询 IO 状态,直到操作完成。 CPU 开销大,适用于短时间 IO 操作。 IO 多路复用 一个线程监控多个文件描述符的 IO 状态,统一处理就绪事件。 高效,适用于大量连接但 IO 操作较少的场景。 多线程/多进程 IO 为每个连接创建线程/进程进行 IO 操作。 简化编程模型,但线程/进程开销大,不适合高并发。 异步 IO 操作提交后立即返回,由操作系统完成 IO,并通知程序。 高效,但实现复杂,且操作系统支持有限。
5. IO 多路复用的应用场景
高并发网络服务器:
- 需要同时处理大量的客户端连接。
- 示例:Web 服务器、聊天室、负载均衡器。
实时通信:
- 实时监控多个 IO 通道,快速响应。
- 示例:即时聊天、视频直播。
文件监控和操作:
- 需要监听多个文件或套接字的状态。
- 示例:日志实时监控工具。
6. IO 多路复用的优缺点
优点
- 单线程即可管理多个 IO,减少线程上下文切换。
- 提高了系统的吞吐能力,适合高并发场景。
- 灵活,可根据不同的 IO 状态做不同处理。
缺点
- 编程复杂度较高。
select
和poll
的性能在高连接数时会显著下降。- 对某些事件处理可能需要特殊优化。
7. epoll 的工作模式
水平触发(LT, Level Triggered) :
文件描述符有事件发生时会不断通知应用程序,直到事件被处理完毕。
特点:简单可靠,但可能导致重复通知。边缘触发(ET, Edge Triggered) :
文件描述符只有在事件发生的那一刻通知一次,之后不会重复通知。
特点:高效,但要求应用程序及时处理数据。
总结
IO 多路复用是现代高并发服务器的基础,能够高效管理多个 IO 通道。在实际开发中,根据场景选择合适的多路复用机制(如
select
、poll
或epoll
),并结合具体业务需求和性能要求进行优化设计。
自定义协议
✨作为程序员的我们一般都是工作在应用层的,应用层所涉及到的许多的网络通信协议大多都是程序员自己自定义的;这里的自定义的并不是说单独写一个新的协议,类似于 TCP、UDP、IP协议这种,当然了,我们也大概率没有那个能力。
✨这里的自定义协议通常规定客户端在请求时发送给服务器的特定数据,然后服务端根据这些数据进行解析,随后返回响应给客户端。
✨自定义协议通常指在网络通信中,为特定应用设计的一套规则,用于定义通信双方如何组织、传输和解释数据。通常情况下,程序员需要根据实际需求在应用层设计自定义协议,确保不同系统或设备之间能够正确通信。
如何自定义协议
✨第一:需要明确需求,根据业务场景和逻辑进行自定义规则的编写,确定传输哪些数据。
✨第二:约定好数据的组织格式。
✨我们以点外卖为例子进行说明,进行点外卖前,我们肯定得先选择哪个店家,所以软件会显示出数家外卖店家;在显示出外卖店家前,还有一个步骤是用户看不到,那就是在点击进入店家浏览页面时,你作为客户端会立即向该软件的服务器发送一个请求;但不能单单地发送请求,这里就需要明确客户端发送哪些数据。
比如:请求应该发送 客户的id、客户的位置、客户的经纬度、客户的偏好......,有了这些数据,服务器就会根据这些数据进行解析。
服务器解析完后,就会给客户端返回一个响应,响应的数据可能包括:商家id、商家名称、商家图片、商家评分、商家热门商品、配送费用......
在传输数据之前,如果没有一个约定好的格式对这些数据进行组织,这些数据就会乱套,导致数据解析发送错误,这便是第二步的 约定好数据的组织格式 的重要性。
组织数据的格式
✨纯文本
数据以简单的字符串形式存储,每行或每部分以分隔符(如逗号、空格、换行符)区分。
sql# 请求数据 114514,江西省赣州市....,24N115E,螺狮粉\n # 响应数据 1,商家名称1,商家图片1,商家评分1,商家热门商品1,配送费用\n 2,商家名称2,商家图片2,商家评分2,商家热门商品2,配送费用\n 3,商家名称3,商家图片3,商家评分3,商家热门商品3,配送费用\n 4,商家名称4,商家图片4,商家评分4,商家热门商品4,配送费用\n
类似于上面的组织格式,以 ',' 来规定数据列之间的间隔,以' \n' 来规定数据行之间的间隔;当然,这里的格式纯属由程序员喜好自定义,这便是自定义的一步。
优点:
- 简单易懂,几乎不需要学习成本。
- 易于使用文本编辑器查看和修改。
- 对小型文件非常友好。
缺点:
- 缺乏结构化信息,难以表示复杂数据。
- 易出错,无法验证数据有效性。
- 没有标准化支持。
适用场景:简单的日志记录、小型配置文件。
✨XML
(eXtensible Markup Language)一种标记语言,使用标签组织层级化数据;类似于 HTML,但 HTML 的标签是规定好的,而 XML 则可以由程序员自定义。
XML<!-- 请求 --> <request> <userId>114514</userId> <userPosition>江西省赣州市</userPosition> <transit>24N115E</transit> <favor>螺狮粉</favor> </request> <!-- 响应 --> <response> <shop> <id>1</id> <nickName>商家名称1</nickName> <image>商家图片1</image> <rank>商家评分1</rank> <hotGoods>商家热门商品1</hotGoods> <sendPrice>配送费用</sendPrice> </shop> </response>
- 优点:
- 可读性强
- 表示层次结构和复杂关系非常直观。
- 可扩展性强,可以自定义标签。
- 配备丰富的验证工具(如 DTD、XSD)。
- 缺点:
- 数据量大,冗余较多,影响传输效率。
- 人工编辑不直观,容易出错。
- 性能比 JSON 和二进制格式差。
- 传输带宽消耗大
- 适用场景:早期的 Web 服务(SOAP)、配置文件、文档管理。
✨JSON
(JavaScript Object Notation)一种轻量级的数据交换格式,基于键值对。
javascript{ userId:114514, userPosition:江西省赣州市, transit:24N115E, favor:螺狮粉 }
优点:
- 简单易读,结构直观。
- 数据量较小,相比 XML 更适合网络传输。
- 被广泛支持,大多数语言都内置 JSON 解析库。
- 支持数组,易表示复杂数据结构。
缺点:
- 不支持注释,调试配置文件时不够灵活。
- 无内置数据类型定义(如数字的范围、枚举值等)。
- 在数据量特别大时,解析性能仍逊于二进制格式。
适用场景:RESTful API、前后端数据交换、轻量级配置文件。
✨Protobuf
(Protocol Buffers)Google 开发的一种高效的二进制序列化格式。
javascript// 数据结构定义文件 .proto message User { int32 id = 1; string position = "江西省赣州市"; string transit = "24N115E"; string favor = "螺狮粉"; } // 对应的二进制序列化数据: 0A 05 41 6C 69 63 65 10 1E 1A 08 4E 65 77 20 59 6F 72 6B
- 优点:
- 序列化后数据体积小,性能高。
- 使用
.proto
文件强类型定义数据结构,防止数据错误。- 向后兼容性好,可升级字段。
- 缺点:
- 可读性差,不适合直接查看和调试。
- 初学者需要学习其数据定义和生成工具的使用。
- 适用场景:高性能分布式系统、大规模数据传输(如微服务之间通信)。
✨对比总结
特性 纯文本 XML JSON Protobuf 可读性 很高 中等 高 很低 结构化支持 低 很强 强 很强 效率(传输/解析) 较低 较低 较高 很高 文件体积 较小 很大 较小 很小 兼容性 低 中等 高 很高 使用场景 简单场景 配置和层次数据 API 和交换数据 高性能传输 选择数据格式的建议
- 小型项目或临时数据:使用纯文本或 JSON。
- 复杂结构的配置文件:可以使用 JSON 或 XML。
- 高性能需求或大规模系统:优先选择 Protobuf。
- 需要易读性和广泛兼容性:选择 JSON。
- 层次结构复杂且需要强校验:选择 XML。
不同场景选择合适的格式,能够显著提高项目的可维护性和效率。
下节我们将进入著名的应用层协议------HTTP/HTTPS协议的讲解~~~毕竟不知后事如何,且听下回分解
❤️❤️❤️❤️❤️❤️❤️