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的基本的知识,让 ...
随机推荐
- 设置TCP_USER_TIMEOUT参数来判断tcp连接是否断开
[TOC] 1. bug描述 前段时间遇到这样的一个问题,openstack一个控制节点宕机后,在宕机后一段时间内创建的虚拟机,一直卡在创建中的状态.有的甚至要等到16分钟之后虚拟机才会切换到下一个状 ...
- BZOJ 2429: [HAOI2006]聪明的猴子
Description 在一个热带雨林中生存着一群猴子,它们以树上的果子为生.昨天下了一场大雨,现在雨过天晴,但整个雨林的地 表还是被大水淹没着,部分植物的树冠露在水面上.猴子不会游泳,但跳跃能力比较 ...
- 借助TZImagePickerController三方库理解自定义相册
借助TZImagePickerController三方库理解自定义相册 1.整体架构分析 整体框架大致可以分为几个部分 <1>工具类-TZImageManager:这个类主要是工作是提供一 ...
- php学习笔记upset函数。
如果在函数中我们不希望再使用某个变量.可以使用unset()销毁函数, ②在php函数中,我们可以给某些参数赋一个默认初值. ③可以传一个默认的参数. ④php在传递变量的时候,默认是传递值传递,如果 ...
- freemarker写select组件报错总结(一)
1.具体错误如下 六月 25, 2014 11:26:29 下午 freemarker.log.JDK14LoggerFactory$JDK14Logger error 严重: Template pr ...
- 芝麻HTTP:Scrapy小技巧-MySQL存储
这两天上班接手,别人留下来的爬虫发现一个很好玩的 SQL脚本拼接. 只要你的Scrapy Field字段名字和 数据库字段的名字 一样.那么恭喜你你就可以拷贝这段SQL拼接脚本.进行MySQL入库处理 ...
- Java并发系列[8]----CyclicBarrier源码分析
现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始.例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始.在JUC包中为我们 ...
- n人围成一圈报数
题目:有n个人围成一圈,顺序排号.从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来的第几号的那位 思路:用一个数组存这n个人,里面的初始状态全设为1,表示都还在圈子里面. ...
- C#抽象方法与抽象实例--C#基础
1.抽象方法与抽象类的声明 1)抽象类和抽象方法声明必须包含abstract 2)抽象方法的声明没有方法体:public abstract void fly(); 3)抽象类和抽象法前加上public ...
- Python爬虫 股票数据爬取
前一篇提到了与股票数据相关的可能几种数据情况,本篇接着上篇,介绍一下多个网页的数据爬取.目标抓取平安银行(000001)从1989年~2017年的全部财务数据. 数据源分析 地址分析 http://m ...