1. 即时通讯简述
即时通讯是端开发工作中常见的需要,本篇文章以作者工作中应用 FLutter 开发社交软件即时通讯需要为背景,形容一下即时通讯功能设计的要点。
2. 重要概念
即时通讯须要前后端配合,约定音讯格局与音讯内容。本次 IM 客户端需要开发应用了公司已有的基于 Socket.io 搭建的后盾,下文形容波及到的一些概念。
2.1 WebSocket 协定
WebSocket 是一种在单个 TCP 连贯上进行全双工通信的协定。WebSocket 协定与传统的 HTTP 协定的次要区别为,WebSocket 协定容许服务端被动向客户端推送数据,而传统的 HTTP 协定服务器只有在客户端被动申请之后能力向客户端发送数据。在没有 WebSocket 之前,即时通讯大部分采纳长轮询形式。
2.2 Socket.io 和 WebSocket 的区别
Socket.io 不是 WebSocket,它只是将 WebSocket 和轮询(Polling)机制以及其它的实时通信形式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,WebSocket 仅仅是 Socket.io 实现即时通信的一个子集。因而 WebSocket 客户端连贯不上 Socket.io 服务端,当然 Socket.io 客户端也连贯不上 WebSocket 服务端。
2.3 服务端 socket 音讯
了解了服务端 socket 音讯也就了解了服务器端的即时通讯逻辑,服务器收回的 socket 音讯能够分为两种:
-
服务器被动收回的音讯:
例如,社交软件中的 A 用户给 B 用户收回了音讯,服务器在收到 A 用户的音讯后,通过 socket 链接,将 A 用户的音讯转发给 B 用户,B 用户客户端接管到的音讯就属于服务器被动收回的。其余比拟常见的场景例如直播软件中,全平台用户都会收到的礼物音讯播送。
-
服务器在接管到客户端音讯后的返回音讯:
例如,长链接心跳机制,客户端向服务器发送 ping 音讯,服务器在胜利承受客户端的 ping 音讯后返回的 pong 音讯就属于服务器的返回音讯。其余常见的场景如社交软件中 A 用户给 B 用户收回了音讯,服务器在收到 A 用户的音讯后,给 A 客户端返回一条音讯,供 A 客户端理解音讯的发送状态,判断发送是否胜利。大部分场景,服务器在接管到客户端被动收回的音讯之后都须要返回一条音讯。
3. 客户端实现流程
几个设计客户端即时通讯的重点。
3.1 心跳机制
所谓心跳就是客户端收回 ping 音讯,服务器胜利收到后返回 pong 音讯。当客户端一段时间内不在发送 ping 音讯,视为客户端断开,服务器就会被动敞开 socket 链接。当客户端发送 ping 音讯,服务器一段时间内没有返回 pong 音讯,视为服务器断开,客户端就会启动重连机制。
3.2 重连机制
重连机制为客户端从新发动连贯,常见的重连条件如下:
- 客户端发送 ping 音讯,服务器一段时间内没有返回 pong。
- 客户端网络断开。
- 服务器被动断开连接。
- 客户端被动连贯失败。
当呈现极其状况(客户端断网)时,频繁的重连可能会导致资源的节约,能够设置一段时间内的最大重连次数,当重连超过肯定次数时,休眠一段时间。
3.3 音讯发送流程
- 将音讯存储到本地数据库,发送状态设为期待。
- 发送 socket 音讯。
- 接管到服务器返回的 socket 音讯后,将本地数据库期待状态的音讯改为胜利。
注意事项:
将音讯存储到本地数据库时须要生成一个 id 存入数据库,同时传给服务器,当收到音讯时依据 id 判断更新本地数据库的哪一条音讯。
3.4 音讯接管流程
3.5 其余相干
- 聊天页音讯的排序:在查问本地数据库时应用
order by
按工夫排序。 - 音讯列表:也举荐做本地存储,当收到音讯的时候须要先判断本地音讯列表是否有以后音讯用户的对话框,如果没有就先插入,有就更新。音讯列表的保护就不开展说了,感兴趣能够看代码。
- 图片语音音讯:将图片和语言先上传到专门的服务器上(各种专门的云存储服务器),sokcet 音讯和本地存储传递的是云服务器上的 URL。
- 多人聊天(群聊):与单人聊天逻辑基本一致,区别位本地数据库须要增加一个会话 ID 字段,关上一个群就查问对应会话 ID 的数据。聊天音讯不再是谁发给谁,而是在哪个群聊下。
4. 客户端 Flutter 代码
把局部代码贴上来,残缺我的项目在作者的 github 上。
4.1 心跳机制
heart() {pingTimer = Timer.periodic(Duration(seconds: 30), (data) {if (pingWaitTime >= 60) {socket.connect();
pingWaitTime = 0;
pingWaitTimer!.cancel();
ping();}
if (!pingWaitFlag) ping();});
}
ping() {debugPrint("ping");
String pingData =
'{"type":"ping","payload":{"front":true},"msg_id":${DateTime.now().millisecondsSinceEpoch}}';
socket.emit("message", pingData);
pingWaitFlag = true;
pingWaitTime = 0;
pingWaitTimer = Timer.periodic(Duration(seconds: 1), (data) {
pingWaitTime++;
print(data.hashCode);
if (pingWaitTime % 10 == 0) debugPrint(pingWaitTime.toString());
});
}
//pong
if (socketMessage.type == PONG && socketMessage.code == 1000) {
pingWaitFlag = false;
pingWaitTimer!.cancel();
pingWaitTime = 0;
}
4.2 本地数据库设计
数据库表的设计是比拟重要的,了解了数据库设计,读代码也就无压力了。
// 音讯表
CREATE TABLE chatDetail (
chat_id TEXT PRIMARY KEY,// 主键
from_id TEXT,// 发送人
to_id TEXT,// 接管人
created_at TEXT,
content TEXT,// 音讯内容
image TEXT,//UI 展现用,用户头像
name TEXT,//UI 展现用,用户名
sex TEXT,//UI 展现用,用户性别
status TEXT,// 音讯状态
type INTEGER,// 音讯类型,图片 / 文字 / 语音等
chat_object_id TEXT// 聊天对象 ID,对以后用户而言的聊天对象,是一系列本地操作的外围
)
// 音讯列表表
CREATE TABLE chatList (
cov_id TEXT,
unread_count INTEGER,
last_msg_text TEXT,
last_msg_at TEXT,
image TEXT,
name TEXT,
sex TEXT,
chat_object_id TEXT PRIMARY KEY)
5. 总结
无论是 Flutter 技术,或是 IOS/Android/Web。只有把握了即时通讯的外围开发流程,不同的技术只是 API 有些变动。API 往往看文档就能解决,大前端或是特定平台的工程师还是要把握外围开发流程,会几种做同样事件的 API 意义不大。
demo 写的比较简单,有问题能够评论。
我的项目 github 地址。