文章目录
- 前言
- 数据中心类
- 数据持久化
- 网络通信类
- http客户端
- websocket客户端
前言
对即时通信系统客户端部分的网络通讯流程的总结。
数据中心类
通过model/datacenter.h 中的DataCenter 类来管理所有客⼾端需要的数据.这是⼀个单例类.
这里管理一份数据有两个作用,1.通过本地缓存一份数据避免重复的向服务端获取。2.通过这些变量来实现数据的异步调用。
其中包含有:用户个人信息/会话列表/好友列表/好友申请列表/好友搜索结果列表/历史消息列表。
还有会话ID和会话成员的映射,会话ID和未读消息的映射,会话ID和最近消息列表。
//列出 DataCenter中要组织管理的所有数据//当前客户端登录到服务器对应的登录会话 idQString loginSessionId = "";//当前的用户信息UserInfo* myself = nullptr;// 好友列表QList<UserInfo>* friendList = nullptr;//会话列表QList<ChatSessionInfo>* chatSessionList = nullptr;//记录当前选中的会话是哪个QString currentChatSessionId = "";//记录每个会话中,都有哪些成员(主要针对群聊) key为chatSessionId,value为成员列表QHash<QString,QList<UserInfo>>* memberList = nullptr;//待处理的好友申请列表QList<UserInfo>* applyList = nullptr;//每个会话的最近消息列表,key为chatSessionid,value为消息列表QHash<QString,QList<Message>>* recentMessages = nullptr;//存储每个会话,未读消息的个数,key为ChatSessionId,value为未读消息的个数QHash<QString,int>* unreadMessageCount = nullptr;//用户的好友搜索结果QList<UserInfo>* searchUserResult = nullptr;//历史消息搜索结果QList<Message>* searchMessageResult = nullptr;//短信验证码的验证IdQString currentVerifyCodeId = "";// 让 DataCenter 持有 NetClient 实例.network::NetClient netClient;
同时三个很重要的变量,登录会话ID/当前选中会话/验证码ID。
登录会话ID是登录成功后服务器返回的一个token,用于身份鉴权。往后的每次请求都需要带上这个token。
数据持久化
同时客户但把登录会话ID和会话的未读记录持久化到了文件中。其中登录会话ID的持久化是为了后续实现扩展功能。未读信息个数则是为了实现未读消息提示。
void DataCenter::initDataFile()
{// 构造出文件的路径, 使用 appData 存储文件QString basePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);QString filePath = basePath + "/ChatClient.json";LOG() << "filePath=" << filePath;QDir dir;if (!dir.exists(basePath)) {dir.mkpath(basePath);}// 构造好文件路径之后, 把文件创建出来.// 写方式打开, 并且写入初始内容QFile file(filePath);if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {LOG() << "打开文件失败!" << file.errorString();return;}// 打开成功, 写入初始内容.QString data = "{\n\n}";file.write(data.toUtf8());file.close();
}
网络通信类
通过network/NetClient.h 中的NetClient 类来管理所有的和服务器通信的内容.NetClient 内部⼜分成httpClient 和websocketClient 两个部分.DataCenter 中会持有NetClient 的指针.
private:model::DataCenter* dataCenter;// http 客户端QNetworkAccessManager httpClient;// websocket 客户端QWebSocket websocketClient;// 序列化器QProtobufSerializer serializer;
http客户端
客户端的通信流程这里总结一下,
- 首先在dataCenter类中有netClient的实例,我们向服务器发起请求都是通过dataCenter对象来间接调用netClient对象中的http对象向服务器发起请求的。
- netclient类中的http对象负责发起请求,并通过连接信号槽,接收返回的响应,并判断结果是否正确
- 在槽函数中对响应中的数据保存到dataCenter中
- 通过DataCenter发起信号,通知主界面更新UI。
- 至此就实现了异步的网络请求。
websocket客户端
由于websocket需要建立tcp长连接之后才可以通信,所以在客户端登录成功后,需要进行websocket的初始化。这里通过open函数与服务器建立连接。绑定信号槽在连接建立成功后会给服务器发送身份验证请求。服务器需要建立用户ID与websocket连接的映射管理。
通过连接信号槽处理二进制消息,服务器只会给我们发送二进制消息。
void NetClient::initWebSocket()
{//1.准备好所有需要的信号槽connect(&websocketClient,&QWebSocket::connected,this,[=]() {qDebug() << "webSocket 连接成功";//连接建立成功后,进行身份认证sendAuth();});connect(&websocketClient,&QWebSocket::disconnected,this,[=]() {qDebug() << "webSocket 连接断开";});connect(&websocketClient,&QWebSocket::errorOccurred,this,[=](QAbstractSocket::SocketError error) {qDebug() << "webSocket 连接出错" << error;});connect(&websocketClient,&QWebSocket::textMessageReceived,this,[=](const QString& message) {qDebug() << "webSocket 收到文本消息!" << message;});connect(&websocketClient,&QWebSocket::binaryMessageReceived,this,[=](const QByteArray& byteArray) {qDebug() << "webSocket 收到二进制消息!" << byteArray.length();//这里是webSocket接收数据的入口//反序列化出 protoBuf对象lkm_im::NotifyMessage notifyMessage;notifyMessage.deserialize(&serializer,byteArray);handleWsResponse(notifyMessage);});//2.和服务器真正建立连接websocketClient.open(WEBSOCKET_URL);
}
websocket通信的流程总结一下,在这里的信号槽是处理的入口,通过反序列化构建对象。
通过事件类型进行分类处理,对返回的数据通过dataCenter进行保存,然后通过dataCenter发送信号,通知主界面进行更新UI。