即时通讯是端开发工作中常见的需求,本篇文章以作者工作中使用FLutter开发社交软件即时通讯需求为背景,描述一下即时通讯功能设计的要点。
即时通讯需要前后端配合,约定消息格式与消息内容。本次IM客户端需求开发使用了公司已有的基于Socket.io搭建的后台,下文描述涉及到的一些概念。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket协议与传统的HTTP协议的主要区别为,WebSocket协议允许服务端主动向客户端推送数据,而传统的HTTP协议服务器只有在客户端主动请求之后才能向客户端发送数据。在没有WebSocket之前,即时通讯大部分采用长轮询方式。
Socket.io不是WebSocket,它只是将WebSocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,WebSocket仅仅是Socket.io实现即时通信的一个子集。因此WebSocket客户端连接不上Socket.io服务端,当然Socket.io客户端也连接不上WebSocket服务端。
理解了服务端socket消息也就理解了服务器端的即时通讯逻辑,服务器发出的socket消息可以分为两种:
服务器主动发出的消息:
例如,社交软件中的A用户给B用户发出了消息,服务器在收到A用户的消息后,通过socket链接,将A用户的消息转发给B用户,B用户客户端接收到的消息就属于服务器主动发出的。其他比较常见的场景例如直播软件中,全平台用户都会收到的礼物消息广播。
服务器在接收到客户端消息后的返回消息:
例如,长链接心跳机制,客户端向服务器发送ping消息,服务器在成功接受客户端的ping消息后返回的pong消息就属于服务器的返回消息。其他常见的场景如社交软件中A用户给B用户发出了消息,服务器在收到A用户的消息后,给A客户端返回一条消息,供A客户端了解消息的发送状态,判断发送是否成功。大部分场景,服务器在接收到客户端主动发出的消息之后都需要返回一条消息。
几个设计客户端即时通讯的重点。
所谓心跳就是客户端发出ping消息,服务器成功收到后返回pong消息。当客户端一段时间内不在发送ping消息,视为客户端断开,服务器就会主动关闭socket链接。当客户端发送ping消息,服务器一段时间内没有返回pong消息,视为服务器断开,客户端就会启动重连机制。
重连机制为客户端重新发起连接,常见的重连条件如下:
当出现极端情况(客户端断网)时,频繁的重连可能会导致资源的浪费,可以设置一段时间内的最大重连次数,当重连超过一定次数时,休眠一段时间。
注意事项:
将消息存储到本地数据库时需要生成一个id存入数据库,同时传给服务器,当收到消息时根据id判断更新本地数据库的哪一条消息。
order by
按时间排序。把部分代码贴上来,完整项目在作者的github上。
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; }
数据库表的设计是比较重要的,理解了数据库设计,读代码也就无压力了。
//消息表 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)
无论是Flutter技术,或是IOS/Android/Web。只要掌握了即时通讯的核心开发流程,不同的技术只是API有些变化。API往往看文档就能解决,大前端或是特定平台的工程师还是要掌握核心开发流程,会几种做同样事情的API意义不大。
demo写的比较简单,有问题可以评论。
项目github地址