IMLite轻量级即时通信工具开发指南
花了一周时间开发了一个简单的即时通信工具,勉强算是程序原型。现在我把开发流程和一些个人的想法记录下来。本文首先介绍程序架构和通信接口,之后会聚焦到服务器的信号槽设计原则,接下来将解释有关TCP通信的粘包问题和解决方案,最后一个部分是一些改进建议。
源码下载:https://gitee.com/learnhow/imlite
程序架构图例:

一、架构方案介绍
主程序(MainWindow)启动服务器(TcpServer),在收到客户端的连接请求之后会开启一个线程并创建子连接(TcpSocket)。目前支持的消息类型有三种,分别为:Message——由客户端发起的端到端消息或直接由服务器发起的广播消息,这是即时通信最基础的通信要求。MessageConnCallback——由服务器向新连接的终端发起的socketID反向注册信息,目的是告知客户端本次连接在服务器端生成的socketID。MessageConnecter——由服务器向所有终端发布的一条广播,通知所有终端其他的终端socketID和客户端自定义的nickname,这是实现端到端通信的基础。
端到端通信的原理实际上是由客户端发送一条携带了接收方socketID的信息。服务器在收到后会解析并进行转发。
为了让多种消息类型能够统一,程序提供了MessageInterface接口和TcpSocketData对象。所有的消息类型都必须实现MessageInterface接口,并且在发送端和接收端都必须通过TcpSocketData来注册消息类型包括进行序列化和反序列化。
NetPacket提供了消息打包和解包方法。为发送的数据包加上一个字符串作为包尾,并且在接收端可以根据包尾来解包并分割为原始报文。更详细的信息将在第三节介绍。
SQLiteService实现了一种简单的数据保存方法,由于程序开发的重点不在于此,并没有做专门设计。二次开发中应该会重新设计,这里一带而过。
二、通信机制介绍
本例中所有对象的通信都采用信号槽,信号槽是Qt提供的一种消息流转机制。在命名规则上,程序按照Qt的命名方案:槽的命名使用动词一般现在时,信号命名使用动词过去时。并且以“谁创建谁连接”的原则编写,例如:TcpServer负责创建所有的子连接TcpSocket,因此与它的信号槽连接都会放在TcpServer中实现。
接下来以客户端向服务器端发起连接请求为例做简单介绍。服务器(TcpServer)通过incomingConnection(qintptr)方法产生socketID,立刻通过子连接向终端发送connectionCallback信号,接着会发送广播向终端通知有新的连接加入,最后还会向UI层发送一个与显示有关的信号(程序中表现为在QWidgetList中增加一个Item)。
// 当有新的终端连接请求时被调用
void TcpServer::incomingConnection(qintptr handle)
{
... clientStatusConnect(QString("%1").arg(handle));
} void TcpServer::clientStatusConnect(QString socketDescriptor)
{ socketnicknamemap.insert(socketDescriptor, "Guest");
// 向终端反向注册handle
MessageConnCallback callback;
callback.setSocketDescriptor(socketDescriptor);
emit connectionCallback(callback); ① MessageConnecter msgConn;
msgConn.setConnectors(socketnicknamemap);
emit terminalsPublished(msgConn); ② // 反馈信号至UI:增加终端
emit clientStatusTriggered(socketDescriptor, );
}
其它的代码就不一一解释了。
三、Tcp粘包和解决方案
观察上面的源码片段,当有新连接产生后,服务器会“同时”向终端发送两条数据(注:①②)。客户端的void dataRecv()方法会调用QByteArray buff = tcpSocket->readAll()读取数据,这时有极大的几率发生“粘包”——服务器发送的两条报文A、B,客户端以A+B作为一条报文读取出来。我的解决方案是在发送端为每段报文增加一个字符串(CF07D)作为结束标志,在接收端就以此标志对报文分割。
QByteArray NetPacket::package(const QByteArray &data)
{
QByteArray wrap(data);
wrap += splite;
return wrap;
} QList<QByteArray> NetPacket::unpackage(const QByteArray &wrap)
{
QList<QByteArray> bufflist;
int pos = ;
int prev = ;
while((pos = wrap.indexOf(splite, pos)) != -) {
QByteArray part = wrap.mid(prev, pos);
bufflist.push_back(part);
pos += splite.length();
prev = pos;
}
return bufflist;
}
四、问题及改进建议
(1)信号槽是一个好东西,但是不能滥用:程序中所有对象的数据流转都是通过信号槽机制。不可否认这样做似乎减少了模块耦合,但是增加了维护的难度。并且看上去也显得“乱糟糟”的。如果一条数据同时需要经过多个对象处理,就必须为此设计多个信号槽。
(2)把消息分开传输:服务器的自连接与客户端之间只使用了一个socket端口通信(8361),这样在接收端就必须为不同的消息类型设计不同的type值。更加合理的做法是让客户端与服务器同时使用多个端口进行通信,既有利于需求扩展又不至于让接收端的方法无限膨胀。
IMLite轻量级即时通信工具开发指南的更多相关文章
- 某即时通信工具与RMS结合
某客户内部使用及时通信工具与RMS相结合,如果客户使用了海外版Office 365E3可以直接在手机端使用Office app打开. 如果客户没有使用海外版Office 365E3的版本,需要结合本地 ...
- QQ 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件
QQ 编辑 腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信(IM)软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功 ...
- iOS开发-即时通信XMPP
1. 即时通信 1> 概述 即时通讯(Instant Messaging)是目前Internet上最为流行的通讯方式,各种各样的即时通讯软件也层出不穷,服务提供商也提供了越来越丰富的通讯服务功能 ...
- android-使用环信SDK开发即时通信功能及源代码下载
近期项目中集成即时聊天功能.挑来拣去,终于选择环信SDK来进行开发,选择环信的主要原因是接口方便.简洁.说明文档清楚易懂.文档有android.ios.和后台server端.还是非常全的. 环信官网: ...
- iOS开发之使用XMPPFramework实现即时通信(三)
你看今天是(三)对吧,前面肯定有(一)和(二),在发表完iOS开发之使用XMPPFramework实现即时通信(一)和iOS开发之使用XMPPFramework实现即时通信(二)后有好多的小伙伴加我Q ...
- iOS开发之使用XMPPFramework实现即时通信(二)
上篇的博客iOS开发之使用XMPPFramework实现即时通信(一)只是本篇的引子,本篇博客就给之前的微信加上即时通讯的功能,主要是对XMPPFramework的使用.本篇博客中用到了Spark做测 ...
- 基于Nodejs开发的web即时聊天工具
由于公司需要开发web即时聊天的功能,开始时我们主要的实施方法是用jquery的ajax定时(10秒)轮询向服务器请求,由于是轮询请求,对 服务器的压力比较大.我们网站上线的时间不长,访问量不是很大, ...
- 精通BIRT:Eclipse商务智能报表工具开发实践指南
http://blog.csdn.net/birtbird/article/details/8935520 [置顶] 精通BIRT:Eclipse商务智能报表工具开发实践指南 分类: BIRT 201 ...
- iOS开发之使用XMPPFramework实现即时通信
iOS开发之使用XMPPFramework实现即时通信 关于XMPP的理论介绍在本篇博客中就不做赘述了,如何在我们之前的微信中加入XMPP协议来实现通信呢?下面将会介绍一下XMPP的基本的知识,让 ...
随机推荐
- JVM笔记1-内存溢出分析问题与解决
假设我们项目中JVM内存溢出了,大项目中上百万行代码,是很难定位的.因此我们需要借用一个Memory Analyzer工具, 官网地址为:http://www.eclipse.org/download ...
- org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.coder520.mamabike.user.dao.UserMapper.selectByPrimaryKey
这个异常是IDEA中漏加载mapper.xml文件,在classes中没找到,所以要在配置文件中加入: !--如果不添加此节点mybatis的mapper.xml文件都会被漏掉.--> < ...
- 类似fabric主机管理demo
类似于fabric的主机管理系统 可以批量对主机进行操作 批量上传文件 批量下载文件 批量执行命令 demo代码 #!/usr/bin/env python # -*- coding:utf-8 -* ...
- 使用mongoVUE删除大量数据的情况下失效问题
昨天有一个系统出现了问题,导致半夜时大量的错误数据产生,早晨一早接到上边通知让把这些数据尽数删除. 不可否认在数据操作时mongoVUE更加直观,因此一般情况下我也都是使用这个工具,但是今天却出现了问 ...
- 【dedecms】DEDE列表页调用文章内容第一张图片(非缩略图)方法
打开 ../ include/ common.func.php 添加代码 //将缩放图转变为文章第一张图片 function firstimg($str_pic) { $str_sub=substr( ...
- Rwordseg使用
#用于下载安装rJava 和 Rwordseg,如果安装了就注释掉 install.packages("rJava") install.packages("Rwordse ...
- HTML中的select下拉框内容显示不全的解决办法
HTML中的select下拉框内容显示不全的解决办法 今天,我遇到这样一个问题:查询栏中的下拉框中的内容过长,导致部分被覆盖了. 查询了一些资料,有的说用函数控制,有的说用事件控制,有的看不懂,有的实 ...
- 最新数组方法(包括es6)
整理目前所用过的数组方法,学习了新增的es6方法. 1 arr.push() 从后面添加元素,返回值为添加完后的数组的长度 let arr = [1,2,3,4,5] console.log(arr. ...
- luogu【P1144】最短路计数
原题入口 这道题 一道有关于最短路的图论问题. 要求从1开始求解最短路的条数. 这个题十分有趣,首先,跑裸的spfa(或者dijkstra)算出从1开始的最短路的长度. 再其次,计数的话,可以用记忆化 ...
- HDU1166敌兵布阵(线段树,树状数组)
题面 C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况 ...