【局域网聊天客户端篇】基于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 ...
随机推荐
- 手把手教你用Mysql-Cluster-7.5搭建数据库集群
前言 当你的业务到达一定的当量,肯定需要一定数量的数据库来负载均衡你的数据库请求,我在之前的博客中已经说明了,如何实现负载均衡,但是还有一个问题就是数据同步,因为负载均衡的前提就是,各个服务器的数据库 ...
- 北邮OJ
90. 字符串转换 时间限制 1000 ms 内存限制 65536 KB 题目描述 我们将仅由若干个同一小写字母构成的字符串称之为简单串,例如"aaaa"是一个简单串,而" ...
- wemall app商城源码中ScrollView中嵌套ListView主要代码
很多时间我们在scorllview中嵌入listview的时候,都只能看到listview显示一行数据,而我们的要求是显示多行,即我们数据的行数, 当ListView的高度设定一定的值时,ListVi ...
- Uri API
四中LaunchMode:http://blog.csdn.net/liuhe688/article/details/6754323 onNewIntent:http://www.cnblogs.co ...
- osprofiler在openstack Cinder里的使用
最近在做OpenStack Cinder driver的性能调试, 之前一直是通过在driver里面加入decorator,完成driver各个接口的执行时间的统计. 其实在openstack,已经在 ...
- kali linux qq 2013
按照网上的教程折腾了好几个小时,都没有搞定的qq for linux 在意外的尝试中成功了 文章有参考网友教程的部分:http://xiao106347.blog.163.com/blog/stati ...
- 开源的.NET定时任务组件Hangfire解析
项目慢慢就要开工了,很多园友都在问这个事情,看来大伙对这事很上心啊,事情需要一步步的来,尽量写出一个我们都满意的项目.以前每次在博客前面都会扯淡一下,不过很多人都抱怨这样做不好,加上我这人扯淡起来就停 ...
- 【树莓派】Linux 测网速及树莓派源
这篇文章比较杂,其中包含三点:linux环境中测试网络速度,树莓派下载软件的源,部分我写好的脚本: 一.Linux 测网速 Linux 测网速: sar -n DEV 1 100 1代表一秒统计并显示 ...
- Excel制作多选下拉框代码以及图示
1.首先 点击Sheet1(需要显示多选框的页) ,然后右键查看代码,进入编辑界面 2.写入如下代码 Private Sub Worksheet_SelectionChange(ByVal Targ ...
- iOS开发之URLSession
1.概述 n NSURLSession是iOS7中新的网络接口,与NSURLConnection是并列的. n 当程序在前台时,NSURLSession与NSURLConnection大部分可以互 ...