mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2460 字
9 分钟
Linux 网络编程(20):IM 聊天系统——添加好友与项目总结

IM 聊天系统:添加好友与项目总结#

本节完成 IM 聊天系统的添加好友流程,梳理好友申请从发起、转发、回复到关系入库和列表刷新的完整通信链路。

在此基础上,总结注册、登录、聊天和添加好友等功能的协议处理规律,分析当前项目在重复登录、异常退出和并发容量方面的问题,并整理文件传输、群聊、音视频等后续扩展方向。

一、添加好友的完整通信流程#

1.1 A 与 B 的角色#

为了避免写错目标用户,编写代码时应先判断当前程序扮演的角色:

  • A:发起好友申请的人;
  • B:收到好友申请并选择同意或拒绝的人;
  • 服务端:负责转发请求、保存好友关系、更新好友列表并转发回复。

客户端之间不能直接通信。A 的请求和 B 的回复都必须经过服务端转发。

1.2 RQ 与 RS#

  • RQ:Request,请求;
  • RS:Response,回复。

本次流程中:

  • A 向服务端发送 PROT_ADD_FRIEND_RQ
  • 服务端把这个请求转发给 B;
  • B 作出选择后发送 PROT_ADD_FRIEND_RS
  • 服务端处理回复,再把结果转发给 A。

二、客户端处理好友申请#

2.1 B 客户端需要完成的工作#

B 客户端收到 PROT_ADD_FRIEND_RQ 后需要:

  1. 将缓冲区转换为好友申请结构体;
  2. 弹出对话框,询问是否同意添加好友;
  3. 构造 PROT_ADD_FRIEND_RS
  4. 填写 B 和 A 的用户信息;
  5. 根据按钮设置同意或拒绝结果;
  6. 把回复发送给服务端。

2.2 回复结构体中各字段代表谁#

这段代码运行在 B 客户端,因此:

字段保存的用户来源
addFriRs.useridB当前主界面登录用户 ID
addFriRs.usernickB当前主界面登录用户昵称
addFriRs.friidA原好友申请中的 userid
addFriRs.frinickA原好友申请中的 usernick

因此,服务端收到这个回复后,要查找 A 的 ID 时应使用 rs->friid,不能使用 rs->userid

三、服务端处理添加好友回复#

3.1 在 Kernel.h 中声明处理函数#

// 处理添加好友回复
void dealAddFriendRs(char* data, int len, unsigned long from);

3.2 在协议函数数组中注册#

m_protFuncArr[DEF_PROT_ADD_FRIEND_RS - DEF_PROT_BASE]
= &Kernel::dealAddFriendRs;

注册以后,服务端收到 DEF_PROT_ADD_FRIEND_RS 协议包时,dealData 才能通过协议头找到 dealAddFriendRs

3.3 服务端处理步骤#

服务端收到 B 的回复后分四步处理:

  1. 判断 B 是否同意添加好友;
  2. 如果同意,将好友关系双向写入 t_friend
  3. 如果同意,调用已有函数更新 A、B 两端的好友列表;
  4. 无论同意还是拒绝,都把回复转发给 A。

3.4 为什么好友关系要插入两次#

t_friend 中的好友关系采用双向存储:

(A, B)
(B, A)

只保存一条记录时,从另一个用户的角度查询好友列表可能查不到这段关系。因此,B 同意后执行两次 insert

insert into t_friend values (A, B);
insert into t_friend values (B, A);

在当前回复结构体中:

  • rs->friid 是 A;
  • rs->userid 是 B。

3.5 为什么调用一次就能更新双方列表#

getUserInfoAndFrienfInfo(rs->friid);

此时传入的是 A 的 ID。前面已经把 A、B 的双向关系写入数据库,因此该函数查询 A 的好友列表时一定能够查到 B。已有函数会:

  1. 查询 B 的信息并发送给 A,使 B 出现在 A 的好友列表中;
  2. 如果 B 在线,再把 A 的信息发送给 B,使 A 出现在 B 的好友列表中。

所以调用一次即可更新在线的 A、B 两端好友列表。传入 B 的 ID 可以得到同理的效果。

3.6 为什么最终要发给 rs->friid#

B 构造回复时:

userid = B
friid = A

最终需要收到“同意/拒绝”提示的是最初发起申请的 A,所以服务端必须使用:

m_mapUserIdToSocket[rs->friid]

如果好友关系已经写入数据库、双方列表也已更新,但 A 没有弹出结果提示,首先检查这里是否把 useridfriid 写反了。

四、IM 项目的协议处理规律#

4.1 注册与登录#

注册和登录属于典型的一问一答:

客户端发送 RQ
服务端处理业务和数据库
服务端沿原 Socket 返回 RS

4.2 聊天#

聊天请求由 A 发给服务端:

  • B 在线:服务端把 A 的 RQ 直接转发给 B;
  • B 不在线:服务端构造失败 RS 返回 A;
  • 完整项目还应把离线消息写入数据库,等 B 登录后再投递。

4.3 添加好友#

添加好友比普通请求多一次往返:

A --RQ--> 服务端 --RQ--> B
B --RS--> 服务端 --RS--> A

同意时,服务端还要完成数据库写入和双方好友列表刷新。

4.4 协议分发的固定步骤#

每增加一种服务端协议处理,基本都遵循以下步骤:

  1. 在协议头文件中定义结构体和协议类型;
  2. Kernel.h 中声明处理函数;
  3. Kernel.cpp 中实现处理函数;
  4. 把成员函数地址注册到 m_protFuncArr
  5. 客户端构造协议包并发送;
  6. 服务端根据协议类型分发、处理,并进行回复或转发。

五、当前项目的三个主要问题#

5.1 同一用户重复登录#

当前在线表主要是:

map<int, SOCKET> m_mapUserIdToSocket;

一个用户 ID 只能保存一个 Socket。同一账号再次登录时,新 Socket 会覆盖旧 Socket,导致:

  • 多个客户端看起来都能发送消息;
  • 服务端只保留最后一次登录的 Socket;
  • 只有最后一次登录的客户端能够收到发给该用户的消息。

处理方向:

  • 同一设备上不允许同一账号重复登录;
  • 若要允许同一账号在不同设备同时登录,在线关系的数据结构和消息投递策略必须能够保存并处理多个连接。

5.2 客户端异常退出#

正常关闭窗口时,客户端可以主动发送下线请求;但程序崩溃、断网、断电等异常退出无法保证发送下线包。此时服务端和好友可能仍认为该用户在线。

解决方向是引入心跳机制:

客户端或服务端定时发送心跳包
在规定时间内收到回复:连接正常
连续超时或 recv 检测到断开:判定下线
清理 userId—Socket 映射并通知在线好友

5.3 Windows 阻塞多线程模型的容量限制#

当前服务端采用阻塞式 I/O,并为每个连接创建接收线程。以典型的 32 位进程环境为例:

  • 一个进程的虚拟地址空间约为 4 GB;
  • 其中通常约 0~2 GB 为用户空间,2~4 GB 为内核空间;
  • 一个线程的默认栈约为 1 MB;
  • 因此可创建的线程数量有限,难以支撑大量用户同时在线。

六、项目功能扩展思路#

6.1 文件传输#

文件通常比普通聊天消息大,一个数据包无法一次传完,因此要考虑:

  • 文件拆包和分片编号;
  • 已经传输的长度与总长度;
  • 断网后从头发送还是断点续传;
  • 服务端是否保存文件;
  • 保存多长时间、过期后如何删除;
  • 小文件是否自动接收,大文件是否等待用户点击下载。

如果服务端保存文件,不应直接把大文件内容全部放进数据库。更合理的设计是:

  • 文件内容保存在专门的文件服务器或磁盘目录;
  • 数据库保存发送者 ID、接收者 ID、发送时间、文件名和文件路径等元数据;
  • 接收者下载时先查询数据库,再根据路径读取并传输文件。

断点续传需要记录接收进度。网络恢复后,只发送尚未完成的分片,而不是无条件从头开始。

6.2 群聊#

群聊不只是循环转发消息,还需要先设计:

  • 创建群;
  • 群主、管理员、普通成员等角色;
  • 不同角色的操作权限;
  • 修改群名等管理权限;
  • 申请加入、群主审批或直接加入等入群方式;
  • 群成员能否邀请其他用户;
  • 在线群消息转发和离线群消息保存。

最基本的群消息转发是查询群成员,然后逐个给在线成员发送;完整实现还需处理离线存储和权限控制。

6.3 音视频通话#

音视频通话的主要环节:

音视频采集
编码与压缩
网络传输
解码与播放
音画同步

音视频数据量大,需要考虑实时性、带宽、编码效率以及播放时的同步问题。

6.4 语音消息#

语音消息相对实时音视频通话简单,但仍需要:

  1. 限制录音时长;
  2. 调用系统能力进行语音采集;
  3. 必要时压缩;
  4. 通过网络传输;
  5. 接收后播放。

6.5 屏幕共享#

屏幕共享的核心流程与视频传输相似:采集屏幕画面、编码压缩、持续传输、接收端解码显示。还要考虑帧率、清晰度、带宽和延迟。

6.6 其他可扩展内容#

  • 修改个人资料、头像和签名;
  • 好友备注;
  • 删除好友和黑名单;
  • 类似朋友圈的动态功能;
  • 离线聊天、离线好友申请;
  • 第三方支付一般接入微信或支付宝,不建议自行实现支付安全体系。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Linux 网络编程(20):IM 聊天系统——添加好友与项目总结
https://joyanblog.com/posts/linux-network-programming-20-im-chat-system-add-friend-project-summary/
作者
Joyan
发布于
2026-08-12
许可协议
CC BY-NC-SA 4.0

目录