【局域网聊天客户端篇】基于socket与Qt
前言
暑假把linux下的高级编程和网络编程学习了一遍,学习很重要,但是也得有个练手的地方,所以必须做做项目来认识下自己所学习的知识。
能够找到小伙伴一起做项目也是一件很快乐的事情的,很幸运的有两个小伙伴一起做这个项目,而我正好负责整个客户端模块,她两负责编写服务器的模块。
开始吧:
项目具体描述:做一个可以实现单个服务器响应多客户端私聊和群聊的聊天工具。具体功能越丰富越好。
下面我以我们做项目的形式来讲讲我们的项目的实现。
项目策划
虽然只是私底下做项目,但是我们还是做了个小小的项目时间和项目实现上的规划,相当于一个实现的计划。
几个问题
这个项目有几个核心的东西必须讨论清楚:
(1)用户信息存储的数据库表
(2)自己定制的信息协议
自己定制协议,这样可以很大程度上放开很多东西,有了自己的协议我可以把所有的东西都变成一条消息,比如在实现私聊,群聊,离线消息,加好友等,都可以设置为一条请求消息和一条对应的回复消息的协议直接进行消息的打包,传输和解析操作。
下面看看外面自己定制的协议:
#ifndef AGREEMENT
#define AGREEMENT /*
注册:101+用户名长度+用户名+密码长度+密码+密保问题+密保答案长度+密保答案
登录:102+用户名长度+用户名+密码长度+密码
改密:103+用户名长度+用户名+密保问题+密保答案长度+密保答案+密码长度+新密码
退出:104+用户名长度+用户名
加入群聊:105+用户名长度+用户名
回复:
111+回复类型+消息长度+消息 1:成功,2:用户名重复失败
112+回复类型+消息长度+消息 1:成功 2:失败(用户名,密码)
113+回复类型+消息长度+消息 1:成功 2:失败(用户名,密保) 201+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+消息长度+消息
202+发送者用户名长度+发送者用户名+消息长度+消息
203+被禁言用户名长度+被禁言用户名+禁言时间消息(一个字节)
204+被删除用户名长度+被删除用户名
205+指定用户名长度+指定用户名
206+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+消息长度+消息
207+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+文件消息长度+文件消息
208+用户数量+用户名长度+用户名
*/
//枚举:
/*消息类型*/
enum{REGISTER,LOGIN,CHPASSWD,QUIT,JOIN,PMSG,QMSG,GAGMSG,DELMSG,SETMSG,LATEMSG,FILEMSG,USERLIST,REG_ACK,LOG_ACK,CHPW_ACK}; /*101解析*/
enum{REG_TYPE,REG_USERLEN,REG_USER,REG_PWLEN,REG_PW,REG_QUE,REG_ANSLEN,REG_ANS};
/*102解析*/
enum{LOG_TYPE,LOG_USERLEN,LOG_USER,LOG_PWLEN,LOG_PW};
/*103解析*/
enum{CHPW_TYPE,CHPW_USERLEN,CHPW_USER,CHPW_QUE,CHPW_ANSLEN,CHPW_ANS,CHPW_PWLEN,CHPW_PW};
/*104解析*/
enum{QUIT_TYPE,QUIT_USERLEN,QUIT_USER};
/*105解析*/
enum{JOIN_TYPE,JOIN_USERLEN,JOIN_USER}; /*201解析*/
enum{PRI_TYPE,PRI_FROMLEN,PRI_FROM,PRI_TOLEN,PRI_TO,PRI_MSGLEN,PRI_MSG};
/*202解析*/
enum{QM_TYPE,QM_FROMLEN,QM_FROM,QM_MSGLEN,QM_MSG};
/*203解析*/
enum{GAG_TYPE,GAG_USERLEN,GAG_USER,GAG_TIME};
/*204解析*/
enum{DEL_TYPE,DEL_USERLEN,DEL_USER};
/*205解析*/
enum{SET_TYPE,SET_USERLEN,SET_USER};
/*206解析*/
enum{LATE_TYPE,LATE_FROMLEN,LATE_FROM,LATE_TOLEN,LATE_TO,LATE_MSGLEN,LATE_MSG};
/*207解析*/
enum{FILE_TYPE,FILE_FROMLEN,FILE_FROM,FILE_TOLEN,FILE_TO,FILE_MSGLEN,FILE_MSG};
/*208解析*/
enum{USERL_TYPE,USERL_USERLEN,USERL_USER}; /*111解析*/
enum{REG_ACK_TYPE,REG_ACK_REPLY,REG_ACK_MSGLEN,REG_ACK_MSG};
/*112解析*/
enum{LOG_ACK_TYPE,LOG_ACK_REPLY,LOG_ACK_MSGLEN,LOG_ACK_MSG};
/*113解析*/
enum{CHPW_ACK_TYPE,CHPW_ACK_REPLY,CHPW_ACK_MSGLEN,CHPW_ACK_MSG}; #include <QString>
#include <QCryptographicHash> QString md5(QString str); #endif // AGREEMENT
agreement.h
其中enum(枚举)的定义主要是为了后面的消息解析的连续性。
并且看的出我们的协议是分为两个大组的,最重要的是可扩展性很强,你可以随时的加上一些协议,删除一些协议。灵活性是比较强的。
数据库我们采用的是sqlite本地数据库,数据库表主要是必须定下来防止后续的改动,不能轻易改动。
做项目必须让头脑时刻清醒着,比较好的是在之前建立项目的框架图:

这是我们第一次讨论的框架图,并讨论了几个重要的问题
总的来说,框架很简单,主要就是服务器跟数据库的对话,还有客户端跟服务器的对话,服务器本身可以作为一个管理员的身份。
我主要负责客户端的编写,客户端就是一些信息的解析和发送,并且这次做的是界面的客户端,所以主要还是客户端的界面上的功能的实现。而作为客户端的框架也需要提前想好,这里的主要原因是我用的是Qt界面编程,使用信号与槽的机制进行界面类之间的通信,这里主要的关键在于你的socket只有一个,因为你只能连接一次服务器,就不断开的,所以你的socket在类之间不好传递,所以你就需要在一个界面类中发送所有的socket消息。具体看下下面的一张图界面关系:

通信层的socket在”登陆后的主界面“上,接受的数据包都在这里。
刚开始的时候我把登录界面作为主界面,后来发现这是错误的,因为私聊界面和群聊界面是依托在登陆后的界面上,也就是说如果我以登录界面为socket通信层,那么私聊界面离登录界面有两层的距离,这样通信起来会极大的不方便。
所以写代码前的考虑显得很重要,我因为这个问题整个项目的修改了两次,这在项目中是大忌,因为那个时候你会觉得崩溃的感觉。
下面是我的解析包的代码:
void MyTcpSocket::readyReadSlot()
{
qDebug()<<"have data";
/*read first byte to get msgType*/
while(this->bytesAvailable() > )
{
unsigned char oneByte;
if(this->bytesAvailable()>= && currType == -)
{
this->read((char*)&oneByte,);
qDebug()<<"onebyte:"<<oneByte; switch(oneByte)
{
case :
msgType = REG_ACK;
currType = REG_ACK_TYPE;
break;
case :
msgType = LOG_ACK;
currType = LOG_ACK_TYPE;
break;
case :
msgType = CHPW_ACK;
currType = CHPW_ACK_TYPE;
break;
case :
msgType = PMSG;
currType = PRI_TYPE;
break;
case :
msgType = QMSG;
currType = QM_TYPE;
break;
case :
msgType = GAGMSG;
currType = GAG_TYPE;
break;
case :
msgType = DELMSG;
currType = DEL_TYPE;
break;
case :
msgType = SETMSG;
currType = SET_TYPE;
break;
case :
msgType = USERLIST;
currType = USERL_TYPE;
break;
case :
msgType = BROAD;
currType = BROAD_TYPE;
break; default:
qDebug()<<"UNKNOW msgType";
break;
} } /*read next data*/
unsigned char ackType,msgLen;
QByteArray msg;
switch(msgType)
{
case REG_ACK:
{ if(this->bytesAvailable()>= && currType==REG_ACK_TYPE)
{
this->read((char*)&ackType,);
currType = REG_ACK_REPLY;
qDebug()<<"ackType"<<ackType;
}
if(this->bytesAvailable()>= && currType==REG_ACK_REPLY)
{
this->read((char*)&msgLen,);
currType = REG_ACK_MSGLEN;
qDebug()<<"msgLen:"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType==REG_ACK_MSGLEN)
{
msg.resize(msgLen);
this->read(msg.data(),msg.size());
currType = -;
msgType = -;
qDebug()<<"msg:"<<QString(msg);
emit registerAckSignal(ackType,msg);
qDebug()<<"send register signal";
ackType = -;
msg.clear();
}
break;
}
case LOG_ACK:
{ if(this->bytesAvailable()>= && currType==LOG_ACK_TYPE)
{
this->read((char*)&ackType,);
currType = LOG_ACK_REPLY;
qDebug()<<"ackType"<<ackType;
}
if(this->bytesAvailable()>= && currType==LOG_ACK_REPLY)
{
this->read((char*)&msgLen,);
currType = LOG_ACK_MSGLEN;
qDebug()<<"msgLen:"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType==LOG_ACK_MSGLEN)
{
msg.resize(msgLen);
this->read(msg.data(),msg.size());
currType = -;
msgType = -;
qDebug()<<"msg:"<<QString(msg);
qDebug()<<"login send signal";
emit loginAckSignal(ackType,msg); ackType = -;
msg.clear();
} break;
}
case CHPW_ACK:
{ if(this->bytesAvailable()>= && currType==CHPW_ACK_TYPE)
{
this->read((char*)&ackType,);
currType = CHPW_ACK_REPLY;
qDebug()<<"ackType"<<ackType;
}
if(this->bytesAvailable()>= && currType==CHPW_ACK_REPLY)
{
this->read((char*)&msgLen,);
currType = CHPW_ACK_MSGLEN;
qDebug()<<"msgLen:"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType==CHPW_ACK_MSGLEN)
{
msg.resize(msgLen);
this->read(msg.data(),msg.size());
currType = -;
msgType = -;
qDebug()<<"msg:"<<QString(msg);
emit chpasswdAckSignal(ackType,msg);
qDebug()<<"chpasswd signal send";
ackType = -;
msg.clear();
} break;
}
/*204read del msg*/
case DELMSG:
{
char userLen;
QByteArray user;
if(this->bytesAvailable()>= && currType == DEL_TYPE)
{
this->read((char*)&userLen,);
currType = DEL_USERLEN;
qDebug()<<"userLen:"<<userLen;
}
if(this->bytesAvailable()>=userLen && currType == DEL_USERLEN)
{
user.resize(userLen);
this->read(user.data(),user.size());
currType = -;
msgType = -;
//QMessageBox::information(this,"del","you have been deleted,beacause you a choubi!!!");
qDebug()<<"user:"<<user;
exit();
}
break;
}
case SETMSG:
{
QString user;
unsigned char userLen;
if(this->bytesAvailable()>= && currType == SET_TYPE)
{
this->read((char*)&userLen,);
currType = SET_USERLEN;
}
if(this->bytesAvailable()>=userLen && currType == SET_USERLEN)
{
QByteArray ba;
ba.resize(userLen);
this->read(ba.data(),ba.size());
currType = -;
msgType = -; user = QString(ba); emit setAdminSignal(user);
}
break;
} /*208read users list*/
case USERLIST:
{
unsigned char actType,userLen;
QByteArray user;
if(this->bytesAvailable()>= && currType == USERL_TYPE)
{
this->read((char*)&actType,);
currType = USERL_ACT;
qDebug()<<"actType:"<<actType;
}
if(this->bytesAvailable()>= && currType == USERL_ACT)
{
this->read((char*)&userLen,);
currType = USERL_USERLEN;
qDebug()<<"userLen:"<<userLen;
}
if(this->bytesAvailable()>=userLen && currType == USERL_USERLEN)
{
user.resize(userLen);
this->read(user.data(),user.size());
currType = -;
msgType = -;
qDebug()<<"user:"<<QString(user);
//emit signal to add user in listWidget
emit addUserSignal(actType,QString(user));
}
break;
}
case PMSG:
{
QString send,rec,time,msg;
unsigned char sendLen,recLen,msgLen;
if(this->bytesAvailable()>= && currType == PRI_TYPE)
{
this->read((char*)&sendLen,);
currType = PRI_FROMLEN;
qDebug()<<"sendLen:"<<sendLen;
}
if(this->bytesAvailable()>=sendLen && currType == PRI_FROMLEN)
{
QByteArray ba;
ba.resize(sendLen);
this->read(ba.data(),ba.size());
send = QString(ba);
currType = PRI_FROM;
qDebug()<<"send:"<<send;
}
if(this->bytesAvailable()>= && currType == PRI_FROM)
{
this->read((char*)&recLen,);
currType = PRI_TOLEN;
qDebug()<<"recLen:"<<recLen;
}
if(this->bytesAvailable()>=recLen && currType == PRI_TOLEN)
{
QByteArray ba1;
ba1.resize(recLen);
this->read(ba1.data(),ba1.size());
rec = QString(ba1);
currType = PRI_TO;
qDebug()<<"rec:"<<rec;
}
if(this->bytesAvailable()>= && currType == PRI_TO)
{
this->read((char*)&msgLen,);
currType = PRI_MSGLEN;
qDebug()<<"msgLen:"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType == PRI_MSGLEN)
{
QByteArray ba2;
ba2.resize(msgLen);
this->read(ba2.data(),ba2.size());
time = QString(ba2).mid(,);
msg = QString(ba2).mid();
qDebug()<<"time:"<<time<<"msg:"<<msg; currType = -;
msgType = -; emit priChatMsgSignal(send,send+" "+time+"\n"+msg);
}
break;
}
case QMSG:
{
QString from,time,msg;
unsigned char fromLen,msgLen;
if(this->bytesAvailable()>= && currType == QM_TYPE)
{
this->read((char*)&fromLen,);
currType = QM_FROMLEN;
qDebug()<<"fromLen:"<<fromLen;
}
if(this->bytesAvailable()>=fromLen && currType == QM_FROMLEN)
{
QByteArray ba;
ba.resize(fromLen);
this->read(ba.data(),ba.size());
from = QString(ba);
currType = QM_FROM;
qDebug()<<"from:"<<from;
}
if(this->bytesAvailable()>= && currType == QM_FROM)
{
this->read((char*)&msgLen,);
currType = QM_MSGLEN;
qDebug()<<"msgLen"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType == QM_MSGLEN)
{
QByteArray ba;
ba.resize(msgLen);
this->read(ba.data(),ba.size());
time = QString(ba).mid(,);
msg = QString(ba).mid();
currType = -;
msgType = -; qDebug()<<"msg:"<<time<<" "<<msg;
emit groupChatMsgSignal(from+" "+time+"\n"+msg);
}
break;
}
case BROAD:
{
QString msg;
unsigned char msgLen;
if(this->bytesAvailable()>= && currType == BROAD_TYPE)
{
this->read((char*)&msgLen,);
currType = BROAD_MSGLEN;
qDebug()<<"msgLen:"<<msgLen;
}
if(this->bytesAvailable()>=msgLen && currType == BROAD_MSGLEN)
{
QByteArray ba;
ba.resize(msgLen);
this->read(ba.data(),ba.size());
currType = -;
msgType = -;
msg = QString(ba);
emit broadMsgSignal(msg);
}
break;
}
case GAGMSG:
{
QString user;
unsigned char userLen;
char time;
if(this->bytesAvailable()>= && currType == GAG_TYPE)
{
this->read((char*)&userLen,);
currType = GAG_USERLEN;
qDebug()<<"userLen:"<<userLen;
}
if(this->bytesAvailable()>=userLen && currType == GAG_USERLEN)
{
QByteArray ba;
ba.resize(userLen);
this->read(ba.data(),ba.size());
currType = GAG_USER;
user = QString(ba);
qDebug()<<"user:"<<user;
}
if(this->bytesAvailable()>= && currType == GAG_USER)
{
this->read((char*)&time,);
currType = -;
msgType = -;
qDebug()<<"time:"<<time;
emit gagMsgSignal(user,time);
}
break;
} default:
qDebug()<<"UNKNOW msgType2";
break;
}//switch
}//while
}//readData
基本上是一个模式的解析,这里用上了之前定于的枚举解析,很方便的防止包不完整的情况的发生
总结
我们用了四天做完了这个项目,做完后的感觉是,一些基础的知识很重要,这个项目的基础知识你必须手到擒来。在写代码前一定要把很多东西考虑清楚,想清楚之后去写代码就只是做码农的工作了,只是客户端的逻辑复杂一点点。在协议,框架等东西都很确定的情况下,你就不会晕,而是给自己一个模块一个模块的去写出来,所以,框架才是最重要的。
【局域网聊天客户端篇】基于socket与Qt的更多相关文章
- 用c#写的一个局域网聊天客户端 类似小飞鸽
用c#写的一个局域网聊天客户端 类似小飞鸽 摘自: http://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.htm ...
- 聊天程序(基于Socket、Thread)
聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现 ...
- C#基于Socket的CS模式的完整例子
基于Socket服务器端实现本例主要是建立多客户端与服务器之间的数据传输,首先设计服务器.打开VS2008,在D:\C#\ch17目录下建立名为SocketServer的Windows应用程序.打开工 ...
- 基于Qt的P2P局域网聊天及文件传送软件设计
基于Qt的P2P局域网聊天及文件传送软件设计 zouxy09@qq.com http://blog.csdn.net/zouxy09 这是我的<通信网络>的课程设计作业,之 ...
- 基于Socket客户端局域网或广域网内共享同一短信猫收发短信的开发解决方案
可使同一网络(局域网或广域网)内众多客户端,共享一个短信猫设备短信服务器进行短信收发,短信服务器具备对客户端的管理功能. 下面是某市建设银行采用本短信二次开发平台时实施的系统方案图: 在该方案中,考虑 ...
- 基于EPOLL模型的局域网聊天室和Echo服务器
一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说sel ...
- Qt NetWork即时通讯网络聊天室(基于TCP)
本文使用QT的网络模块来创建一个网络聊天室程序,主要包括以下功能: 1.基于TCP的可靠连接(QTcpServer.QTcpSocket) 2.一个服务器,多个客户端 3.服务器接收到某个客户端的请求 ...
- 聊天程序——基于Socket、Thread (二)
聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现 ...
- 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)
搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...
随机推荐
- JAVA开发环境搭建 - Eclipse基本配置
Eclipse设置的内容包括许多方面,不同的开发人员,不同的项目需要,可能对Eclipse的设置不尽相同.如下内容仅是对本人的一些基本设置做一些记录,以作备忘.后期会逐渐对相关内容进行更新,仅供参考. ...
- 类比Spring框架来实现OC中的依赖注入
如果你之前使用过JavaEE开发中的Spring框架的话,那么你一定对依赖注入并不陌生.依赖注入(DI: Dependency Injection)是控制反转(IoC: Inversion of Co ...
- 《深入理解Java虚拟机》学习笔记之字节码执行引擎
Java虚拟机的执行引擎不管是解释执行还是编译执行,根据概念模型都具有统一的外观:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 栈帧(Stack Frame) ...
- 抓包工具Wireshark的使用
WireShark是非常流行的网络封包分析软件,功能十分强大.可以截取各种网络封包,显示网络封包的详细信息. WireShark界面简介 启动WireShark的界面如下: 选择网卡 wireshar ...
- Java设计模式之《享元模式》及应用场景
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6542449.html 享元模式:"享"就是分享之意,指一物被众人共享, ...
- 求int型正整数在内存中存储时1的个数
题目描述: 输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数. 输入描述: 输入一个整数(int类型) 输出描述: 这个数转换成2进制后,输出1的个数 输入例子: 5 输出例子: ...
- IOS本地日志记录方案
我们在项目中日志记录这块也算是比较重要的,有时候用户程序出什么问题,光靠服务器的日志还不能准确的找到问题. 现在一般记录日志有几种方式: 1.使用第三方工具来记录日志,如腾讯的Bugly,它是只把程序 ...
- 从Visual Studio看微软20年技术变迁
前言 这个世界从来都不缺变革,从工业革命到晶体管和集成电路,从生活电器到物联网,从简陋人机到精致体验,我们在享受技术带来的便捷的同时,也在为复杂设计而带来的挑战和生产力下降而痛并快乐着.而迫切期盼的, ...
- JS中undefined与null的有趣 关系
今天学习中遇到了一个有意思的问题. var obj = undefined 我们将一个对象设置为undefined typeof(obj)>>undefined 结果是undefined, ...
- TimeUnit枚举类
TimeUnit是 java.util.concurrent 中的一个枚举类.一般让线程进行睡眠时使用: TimeUnit.MILLISECONDS.sleep(100); 比如上面一行代码表示让当 ...