1761 字
7 分钟
Linux 网络编程(12):IM 聊天系统——项目分析和架构搭建
IM 聊天系统——项目分析和架构搭建
本节开始搭建 IM 聊天系统项目,首先分析注册、登录、聊天、添加好友和上下线等基本功能,并划分客户端、服务端、数据库及网络通信模块的职责。
随后通过中介者模式降低 kernel 与具体网络实现之间的耦合,对比单例、工厂和中介者模式各自解决的问题,并使用抽象父类 INet 统一 TCP 与 UDP 网络层接口。
一、IM 聊天系统的基本功能
主要功能包括:
- 注册;
- 登录;
- 聊天;
- 添加好友;
- 上线与下线。
登录成功可以看作用户上线,退出或断开连接则对应下线。
项目整体采用 C/S(Client/Server,客户端/服务端)架构:
- 客户端负责界面展示、收集用户输入、组织请求;
- 服务端负责接收请求、处理业务、访问数据库并返回结果。
二、客户端与服务端的类职责
1. 服务端
| 模块/类 | 主要职责 |
|---|---|
| 数据库类 | 连接数据库、查询、更新、断开连接 |
kernel 类 | 处理收到的数据,组织需要发送的数据 |
| 中介者类 | 发送数据、转发数据、打开网络、关闭网络 |
| 网络类 | 初始化网络、发送数据、接收数据、关闭网络 |
2. 客户端
| 模块/类 | 主要职责 |
|---|---|
| UI 界面类 | 显示界面,采集用户输入的数据 |
kernel 类 | 处理收到的数据,组织需要发送的数据 |
| 中介者类 | 发送数据、转发数据、打开网络、关闭网络 |
| 网络类 | 初始化网络、发送数据、接收数据、关闭网络 |
客户端与服务端都需要网络类和中介者类,二者的整体结构相近,具体网络行为有所不同。
3. 数据流向
flowchart LR
UI["UI 界面"] -->|"用户输入"| CK["客户端 Kernel"]
CK -->|"待发送的数据"| CM["客户端中介者"]
CM --> CN["客户端网络类"]
CN <-->|"TCP / UDP"| SN["服务端网络类"]
SN --> SM["服务端中介者"]
SM -->|"转发收到的数据"| SK["服务端 Kernel"]
SK <-->|"查询、更新"| DB["数据库类"]
中介者本身通常不负责真正的业务处理和底层网络通信,它负责在 kernel 与网络类之间传递消息:
- 发送方向:
kernel → 中介者 → 网络类; - 接收方向:
网络类 → 中介者 → kernel。
三、中介者模式
1. 为什么引入中介者
如果 kernel 直接依赖 UDP、TCP 客户端、TCP 服务端等具体网络类,那么网络实现一变化,kernel 也需要跟着修改,类之间会形成较强的耦合。
加入中介者后:
kernel面向中介者接口发送数据;- 网络类收到数据后交给中介者;
- 中介者再把数据转发给
kernel; kernel与具体网络实现不再直接交互。
用租房中介作类比:租客和房东不直接反复沟通,而是让中介在双方之间传递信息。软件中的中介者同样用于集中协调对象之间的交互。
2. 中介者模式解决的问题
- 减少对象之间的直接引用;
- 把复杂的交互关系集中到中介者中;
- 降低模块耦合;
- 便于替换底层网络实现;
- 使客户端与服务端能够复用相似的框架。
四、单例模式、工厂模式、中介者模式比较
| 对比项 | 单例模式 | 工厂模式 | 中介者模式 |
|---|---|---|---|
| 类型 | 创建型模式 | 创建型模式 | 行为型模式 |
| 核心问题 | 一个类怎样只保留一个实例 | 对象应由谁、按什么条件创建 | 多个对象之间怎样协作 |
| 核心做法 | 隐藏构造,提供唯一访问入口 | 把 new 和类型选择封装起来 | 让对象通过中介者交互 |
| 主要收益 | 避免重复实例,便于共享统一状态 | 调用方不依赖具体创建过程 | 减少对象之间的网状依赖 |
| 主要风险 | 容易形成全局状态,测试困难 | 类型过多时工厂层次会膨胀 | 中介者可能承担过多逻辑 |
1. 三者不是互相替代关系
- 单例模式解决“对象有几个”;
- 工厂模式解决“对象怎么创建”;
- 中介者模式解决“对象怎么交互”。
2. 选择原则
- 只是需要唯一对象:考虑单例;
- 需要隐藏复杂创建过程或切换实现:考虑工厂;
- 多个模块彼此直接依赖、交互混乱:考虑中介者;
- 不要为了“使用设计模式”而使用设计模式,应先判断项目中真正存在的问题。
五、网络层的抽象设计
程序需要支持 TCP 和 UDP,因此不能把所有网络代码都堆在一个类中。这里设计了一个抽象父类 INet,再由具体网络类继承并实现它。
1. 类层次
INet(抽象父类)├── TcpClient├── TcpServer└── Udp父类规定所有网络类共同具有的行为:
- 初始化网络;
- 关闭网络;
- 发送数据;
- 开始接收数据。
具体子类根据协议和角色实现这些函数。
这种设计利用了多态:上层统一保存 INet*,指针实际指向哪个子类对象,就执行哪个子类实现。以后换协议时,上层调用形式不必跟着改变。
网络层和中介者层还可以单独封装成库,在其他需要网络通信的项目中复用。
2. 为什么 UDP 只有一个类,TCP 却分为两个类
- UDP 客户端和服务端的流程基本相同,主要差异是是否绑定本地 IP 和端口;UDP 客户端也可以主动绑定,因此二者可以共用一个
Udp类。 - TCP 客户端要主动
connect服务端;TCP 服务端则要bind、listen、accept。 - 一个 TCP 服务端可以连接多个客户端:除了监听套接字,还要保存每个已连接客户端对应的套接字,管理方式明显不同。
因此这里把 TCP 拆成 TcpClient 和 TcpServer,而 UDP 只设计一个 Udp 类。
3. 不同网络角色的大致流程
| 网络角色 | 主要流程 |
|---|---|
| UDP 客户端 | 加载库、创建套接字、绑定 IP 和端口、发送、接收、关闭套接字、卸载库 |
| UDP 服务端 | 加载库、创建套接字、绑定 IP 和端口、接收、发送、关闭套接字、卸载库 |
| TCP 客户端 | 加载库、创建套接字、连接服务端、发送、接收、关闭套接字、卸载库 |
| TCP 服务端 | 加载库、创建套接字、绑定 IP 和端口、监听、接受连接、接收、发送、关闭套接字、卸载库 |
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
Linux 网络编程(12):IM 聊天系统——项目分析和架构搭建
https://joyanblog.com/posts/linux-network-programming-12-im-chat-system-analysis-architecture/ 相关文章 智能推荐
1
Linux 网络编程(16):IM 聊天系统——中介者类实现与 Qt、VS 通信
Linux 网络编程 Linux 网络编程系列第 16 篇,整理 IM 聊天系统中介者类的实现,以及 Qt 客户端、VS 服务端、网络层与 Kernel 之间的数据传递。
2
Linux 网络编程(15):IM 聊天系统——网络功能实现:TCP 协议服务端
Linux 网络编程 Linux 网络编程系列第 15 篇,整理 IM 聊天系统 TCP 服务端的多客户端接收、线程与套接字映射、资源回收,以及网络层向中介者移交数据的实现。
3
Linux 网络编程(20):IM 聊天系统——添加好友与项目总结
Linux 网络编程 Linux 网络编程系列第 20 篇,整理 IM 聊天系统的添加好友完整通信流程、请求与回复协议分发规律、现有架构问题,以及文件传输、群聊和音视频等扩展方向。
4
Linux 网络编程(19):IM 聊天系统——转码、下线与聊天功能实现
Linux 网络编程 Linux 网络编程系列第 19 篇,整理 IM 聊天系统的用户下线、在线与离线聊天、添加好友请求持久化,以及 Qt 客户端和 VS 服务端之间的中文转码。
5
Linux 网络编程(17):IM 聊天系统——数据库设计和创建、注册功能实现、登录功能实现
Linux 网络编程 Linux 网络编程系列第 17 篇,整理 IM 聊天系统的协议分发、用户与好友数据库设计、MySQL 连接以及注册功能,并为登录请求处理预留入口。





