前言

暑假把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的更多相关文章

  1. 用c#写的一个局域网聊天客户端 类似小飞鸽

    用c#写的一个局域网聊天客户端 类似小飞鸽 摘自: http://www.cnblogs.com/yyl8781697/archive/2012/12/07/csharp-socket-udp.htm ...

  2. 聊天程序(基于Socket、Thread)

    聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现 ...

  3. C#基于Socket的CS模式的完整例子

    基于Socket服务器端实现本例主要是建立多客户端与服务器之间的数据传输,首先设计服务器.打开VS2008,在D:\C#\ch17目录下建立名为SocketServer的Windows应用程序.打开工 ...

  4. 基于Qt的P2P局域网聊天及文件传送软件设计

    基于Qt的P2P局域网聊天及文件传送软件设计 zouxy09@qq.com http://blog.csdn.net/zouxy09         这是我的<通信网络>的课程设计作业,之 ...

  5. 基于Socket客户端局域网或广域网内共享同一短信猫收发短信的开发解决方案

    可使同一网络(局域网或广域网)内众多客户端,共享一个短信猫设备短信服务器进行短信收发,短信服务器具备对客户端的管理功能. 下面是某市建设银行采用本短信二次开发平台时实施的系统方案图: 在该方案中,考虑 ...

  6. 基于EPOLL模型的局域网聊天室和Echo服务器

    一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说sel ...

  7. Qt NetWork即时通讯网络聊天室(基于TCP)

    本文使用QT的网络模块来创建一个网络聊天室程序,主要包括以下功能: 1.基于TCP的可靠连接(QTcpServer.QTcpSocket) 2.一个服务器,多个客户端 3.服务器接收到某个客户端的请求 ...

  8. 聊天程序——基于Socket、Thread (二)

    聊天程序简述 1.目的:主要是为了阐述Socket,以及应用多线程,本文侧重Socket相关网路编程的阐述.如果您对多线程不了解,大家可以看下我的上一篇博文浅解多线程 . 2.功能:此聊天程序功能实现 ...

  9. 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

    搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...

随机推荐

  1. Python入门教程(2)

    人生苦短,我玩蛇0.0! Python(英语发音:/ˈpaɪθən/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991 ...

  2. C#如何获取指定周的日期范围

    1. 不允许跨年 1) 第一周的第一天从每年的第一天开始,最后一周的最后一天为每年的最后一天. static void Main(string[] args) { DateTime first, la ...

  3. hadoop使用笔记

    一:hadoop程序添加三方包: 使用hadoop jar 运行时 抛出 java.lang.NoClassDefFoundError 原因:找不到三方包 解决方案: 1.可以将需要使用的包添加进 $ ...

  4. 《Django By Example》第八章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:还有4章!还有4章全书就翻译完成了 ...

  5. php回滚

    $m=D('YourModel');//或者是M();$m2=D('YouModel2');$m->startTrans();//在第一个模型里启用就可以了,或者第二个也行$result=$m- ...

  6. Linux服务器下Java环境搭建

    前言: 在centOS下,像阿里云等都预先设置了jdk,不过不是SUN的java JDK,一般情况要重新装jdk,而且一般情况下自己装的Jdk相对来说易控制版本,稳定性更高.所以以下是我卸载预装jdk ...

  7. ajax 写登录

    AJAX的全称是Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). ajax的优点: 1.最大的一点是页面无刷新,用户的体验非常好. 2.使用 ...

  8. Matlab自带常用的分类器,直接复制用就好了,很方面。

    很方面的,懒得自己写了. clc   clear all     load('wdtFeature');         %   训练样本:train_data             % 矩阵,每行 ...

  9. 1619: [Usaco2008 Nov]Guarding the Farm 保卫牧场

    1619: [Usaco2008 Nov]Guarding the Farm 保卫牧场 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 498  Solve ...

  10. wcf、web api、webservicer 之间的区别

    名次注解 SOAP 简单对象访问协议(SOAP)是一种轻量的.简单的.基于 XML 的协议,是交换数据的一种协议规范,是一种轻量的.简单的.基于XML(标准通用标记语言下的一个子集)的协议,它被设计成 ...