【局域网聊天客户端篇】基于socket与Qt

前言

暑假把linux下的高级编程和网络编程学习了一遍,学习很重要,但是也得有个练手的地方,所以必须做做项目来认识下自己所学习的知识。

能够找到小伙伴一起做项目也是一件很快乐的事情的,很幸运的有两个小伙伴一起做这个项目,而我正好负责整个客户端模块,她两负责编写服务器的模块。

 

开始吧:

项目具体描述:做一个可以实现单个服务器响应多客户端私聊和群聊的聊天工具。具体功能越丰富越好。

下面我以我们做项目的形式来讲讲我们的项目的实现。

 

项目策划

虽然只是私底下做项目,但是我们还是做了个小小的项目时间和项目实现上的规划,相当于一个实现的计划。

 

几个问题

这个项目有几个核心的东西必须讨论清楚:

(1)用户信息存储的数据库表

(2)自己定制的信息协议

 

自己定制协议,这样可以很大程度上放开很多东西,有了自己的协议我可以把所有的东西都变成一条消息,比如在实现私聊,群聊,离线消息,加好友等,都可以设置为一条请求消息和一条对应的回复消息的协议直接进行消息的打包,传输和解析操作。

下面看看外面自己定制的协议:

 1 #ifndef AGREEMENT
 2 #define AGREEMENT
 3 
 4 /*
 5 注册:101+用户名长度+用户名+密码长度+密码+密保问题+密保答案长度+密保答案
 6 登录:102+用户名长度+用户名+密码长度+密码
 7 改密:103+用户名长度+用户名+密保问题+密保答案长度+密保答案+密码长度+新密码
 8 退出:104+用户名长度+用户名
 9 加入群聊:105+用户名长度+用户名
10 回复:
11 111+回复类型+消息长度+消息 1:成功,2:用户名重复失败
12 112+回复类型+消息长度+消息 1:成功 2:失败(用户名,密码)
13 113+回复类型+消息长度+消息 1:成功 2:失败(用户名,密保)
14 
15 
16 201+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+消息长度+消息
17 202+发送者用户名长度+发送者用户名+消息长度+消息
18 203+被禁言用户名长度+被禁言用户名+禁言时间消息(一个字节)
19 204+被删除用户名长度+被删除用户名
20 205+指定用户名长度+指定用户名
21 206+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+消息长度+消息
22 207+发送者用户名长度+发送者用户名+接受者用户名长度+接受者用户名+文件消息长度+文件消息
23 208+用户数量+用户名长度+用户名
24 */
25 //枚举:
26 /*消息类型*/
27 enum{REGISTER,LOGIN,CHPASSWD,QUIT,JOIN,PMSG,QMSG,GAGMSG,DELMSG,SETMSG,LATEMSG,FILEMSG,USERLIST,REG_ACK,LOG_ACK,CHPW_ACK};
28 
29 /*101解析*/
30 enum{REG_TYPE,REG_USERLEN,REG_USER,REG_PWLEN,REG_PW,REG_QUE,REG_ANSLEN,REG_ANS};
31 /*102解析*/
32 enum{LOG_TYPE,LOG_USERLEN,LOG_USER,LOG_PWLEN,LOG_PW};
33 /*103解析*/
34 enum{CHPW_TYPE,CHPW_USERLEN,CHPW_USER,CHPW_QUE,CHPW_ANSLEN,CHPW_ANS,CHPW_PWLEN,CHPW_PW};
35 /*104解析*/
36 enum{QUIT_TYPE,QUIT_USERLEN,QUIT_USER};
37 /*105解析*/
38 enum{JOIN_TYPE,JOIN_USERLEN,JOIN_USER};
39 
40 /*201解析*/
41 enum{PRI_TYPE,PRI_FROMLEN,PRI_FROM,PRI_TOLEN,PRI_TO,PRI_MSGLEN,PRI_MSG};
42 /*202解析*/
43 enum{QM_TYPE,QM_FROMLEN,QM_FROM,QM_MSGLEN,QM_MSG};
44 /*203解析*/
45 enum{GAG_TYPE,GAG_USERLEN,GAG_USER,GAG_TIME};
46 /*204解析*/
47 enum{DEL_TYPE,DEL_USERLEN,DEL_USER};
48 /*205解析*/
49 enum{SET_TYPE,SET_USERLEN,SET_USER};
50 /*206解析*/
51 enum{LATE_TYPE,LATE_FROMLEN,LATE_FROM,LATE_TOLEN,LATE_TO,LATE_MSGLEN,LATE_MSG};
52 /*207解析*/
53 enum{FILE_TYPE,FILE_FROMLEN,FILE_FROM,FILE_TOLEN,FILE_TO,FILE_MSGLEN,FILE_MSG};
54 /*208解析*/
55 enum{USERL_TYPE,USERL_USERLEN,USERL_USER};
56 
57 /*111解析*/
58 enum{REG_ACK_TYPE,REG_ACK_REPLY,REG_ACK_MSGLEN,REG_ACK_MSG};
59 /*112解析*/
60 enum{LOG_ACK_TYPE,LOG_ACK_REPLY,LOG_ACK_MSGLEN,LOG_ACK_MSG};
61 /*113解析*/
62 enum{CHPW_ACK_TYPE,CHPW_ACK_REPLY,CHPW_ACK_MSGLEN,CHPW_ACK_MSG};
63 
64 #include <QString>
65 #include <QCryptographicHash>
66 
67 QString md5(QString str);
68 
69 #endif // AGREEMENT
agreement.h

其中enum(枚举)的定义主要是为了后面的消息解析的连续性。

并且看的出我们的协议是分为两个大组的,最重要的是可扩展性很强,你可以随时的加上一些协议,删除一些协议。灵活性是比较强的。

数据库我们采用的是sqlite本地数据库,数据库表主要是必须定下来防止后续的改动,不能轻易改动。

 

做项目必须让头脑时刻清醒着,比较好的是在之前建立项目的框架图:

这是我们第一次讨论的框架图,并讨论了几个重要的问题

总的来说,框架很简单,主要就是服务器跟数据库的对话,还有客户端跟服务器的对话,服务器本身可以作为一个管理员的身份。

 

我主要负责客户端的编写,客户端就是一些信息的解析和发送,并且这次做的是界面的客户端,所以主要还是客户端的界面上的功能的实现。而作为客户端的框架也需要提前想好,这里的主要原因是我用的是Qt界面编程,使用信号与槽的机制进行界面类之间的通信,这里主要的关键在于你的socket只有一个,因为你只能连接一次服务器,就不断开的,所以你的socket在类之间不好传递,所以你就需要在一个界面类中发送所有的socket消息。具体看下下面的一张图界面关系:

通信层的socket在”登陆后的主界面“上,接受的数据包都在这里。

刚开始的时候我把登录界面作为主界面,后来发现这是错误的,因为私聊界面和群聊界面是依托在登陆后的界面上,也就是说如果我以登录界面为socket通信层,那么私聊界面离登录界面有两层的距离,这样通信起来会极大的不方便。

所以写代码前的考虑显得很重要,我因为这个问题整个项目的修改了两次,这在项目中是大忌,因为那个时候你会觉得崩溃的感觉。

 

下面是我的解析包的代码:

  1 void MyTcpSocket::readyReadSlot()
  2 {
  3     qDebug()<<"have data";
  4     /*read first byte to get msgType*/
  5     while(this->bytesAvailable() > 0)
  6     {
  7         unsigned char oneByte;
  8         if(this->bytesAvailable()>=1 && currType == -1)
  9         {
 10             this->read((char*)&oneByte,1);
 11             qDebug()<<"onebyte:"<<oneByte;
 12 
 13             switch(oneByte)
 14             {
 15                 case 111:
 16                     msgType = REG_ACK;
 17                     currType = REG_ACK_TYPE;
 18                     break;
 19                 case 112:
 20                     msgType = LOG_ACK;
 21                     currType = LOG_ACK_TYPE;
 22                     break;
 23                 case 113:
 24                     msgType = CHPW_ACK;
 25                     currType = CHPW_ACK_TYPE;
 26                     break;
 27                 case 201:
 28                     msgType = PMSG;
 29                     currType = PRI_TYPE;
 30                     break;
 31                 case 202:
 32                     msgType = QMSG;
 33                     currType = QM_TYPE;
 34                     break;
 35                 case 203:
 36                     msgType = GAGMSG;
 37                     currType = GAG_TYPE;
 38                     break;
 39                 case 204:
 40                     msgType = DELMSG;
 41                     currType = DEL_TYPE;
 42                     break;
 43                 case 205:
 44                     msgType =  SETMSG;
 45                     currType = SET_TYPE;
 46                     break;
 47                 case 208:
 48                     msgType = USERLIST;
 49                     currType = USERL_TYPE;
 50                     break;
 51                 case 209:
 52                     msgType = BROAD;
 53                     currType = BROAD_TYPE;
 54                     break;
 55 
 56 
 57                 default:
 58                     qDebug()<<"UNKNOW msgType";
 59                     break;
 60             }
 61 
 62         }
 63 
 64         /*read next data*/
 65         unsigned char ackType,msgLen;
 66         QByteArray msg;
 67         switch(msgType)
 68         {
 69             case REG_ACK:
 70             {
 71 
 72                 if(this->bytesAvailable()>=1 && currType==REG_ACK_TYPE)
 73                 {
 74                     this->read((char*)&ackType,1);
 75                     currType = REG_ACK_REPLY;
 76                     qDebug()<<"ackType"<<ackType;
 77                 }
 78                 if(this->bytesAvailable()>=1 && currType==REG_ACK_REPLY)
 79                 {
 80                     this->read((char*)&msgLen,1);
 81                     currType = REG_ACK_MSGLEN;
 82                     qDebug()<<"msgLen:"<<msgLen;
 83                 }
 84                 if(this->bytesAvailable()>=msgLen && currType==REG_ACK_MSGLEN)
 85                 {
 86                     msg.resize(msgLen);
 87                     this->read(msg.data(),msg.size());
 88                     currType = -1;
 89                     msgType = -1;
 90                     qDebug()<<"msg:"<<QString(msg);
 91                     emit registerAckSignal(ackType,msg);
 92                     qDebug()<<"send register signal";
 93                     ackType = -1;
 94                     msg.clear();
 95                 }            
 96                 break;
 97             }
 98             case LOG_ACK:
 99             {
100 
101                 if(this->bytesAvailable()>=1 && currType==LOG_ACK_TYPE)
102                 {
103                     this->read((char*)&ackType,1);
104                     currType = LOG_ACK_REPLY;
105                     qDebug()<<"ackType"<<ackType;
106                 }
107                 if(this->bytesAvailable()>=1 && currType==LOG_ACK_REPLY)
108                 {
109                     this->read((char*)&msgLen,1);
110                     currType = LOG_ACK_MSGLEN;
111                     qDebug()<<"msgLen:"<<msgLen;
112                 }
113                 if(this->bytesAvailable()>=msgLen && currType==LOG_ACK_MSGLEN)
114                 {
115                     msg.resize(msgLen);
116                     this->read(msg.data(),msg.size());
117                     currType = -1;
118                     msgType  = -1;
119                     qDebug()<<"msg:"<<QString(msg);
120                     qDebug()<<"login send signal";
121                     emit loginAckSignal(ackType,msg);
122 
123                     ackType = -1;
124                     msg.clear();
125                 }
126 
127                 break;
128             }
129             case CHPW_ACK:
130             {
131 
132                 if(this->bytesAvailable()>=1 && currType==CHPW_ACK_TYPE)
133                 {
134                     this->read((char*)&ackType,1);
135                     currType = CHPW_ACK_REPLY;
136                     qDebug()<<"ackType"<<ackType;
137                 }
138                 if(this->bytesAvailable()>=1 && currType==CHPW_ACK_REPLY)
139                 {
140                     this->read((char*)&msgLen,1);
141                     currType = CHPW_ACK_MSGLEN;
142                     qDebug()<<"msgLen:"<<msgLen;
143                 }
144                 if(this->bytesAvailable()>=msgLen && currType==CHPW_ACK_MSGLEN)
145                 {
146                     msg.resize(msgLen);
147                     this->read(msg.data(),msg.size());
148                     currType = -1;
149                     msgType = -1;
150                     qDebug()<<"msg:"<<QString(msg);
151                     emit chpasswdAckSignal(ackType,msg);
152                     qDebug()<<"chpasswd signal send";
153                     ackType = -1;
154                     msg.clear();
155                 }
156 
157                 break;
158             }
159             /*204read del msg*/
160             case DELMSG:
161             {
162                 char userLen;
163                 QByteArray user;
164                 if(this->bytesAvailable()>=1 && currType == DEL_TYPE)
165                 {
166                     this->read((char*)&userLen,1);
167                     currType = DEL_USERLEN;
168                     qDebug()<<"userLen:"<<userLen;
169                 }
170                 if(this->bytesAvailable()>=userLen && currType == DEL_USERLEN)
171                 {
172                     user.resize(userLen);
173                     this->read(user.data(),user.size());
174                     currType = -1;
175                     msgType = -1;
176                     //QMessageBox::information(this,"del","you have been deleted,beacause you a choubi!!!");
177                     qDebug()<<"user:"<<user;
178                     exit(0);
179                 }
180                 break;
181             }
182             case SETMSG:
183             {
184                 QString user;
185                 unsigned char userLen;
186                 if(this->bytesAvailable()>=1 && currType == SET_TYPE)
187                 {
188                     this->read((char*)&userLen,1);
189                     currType = SET_USERLEN;
190                 }
191                 if(this->bytesAvailable()>=userLen && currType == SET_USERLEN)
192                 {
193                     QByteArray ba;
194                     ba.resize(userLen);
195                     this->read(ba.data(),ba.size());
196                     currType = -1;
197                     msgType = -1;
198 
199                     user = QString(ba);
200 
201                     emit setAdminSignal(user);
202                 }
203                 break;
204             }
205 
206 
207             /*208read users list*/
208             case USERLIST:
209             {
210                 unsigned char actType,userLen;
211                 QByteArray user;
212                 if(this->bytesAvailable()>=1 && currType == USERL_TYPE)
213                 {
214                     this->read((char*)&actType,1);
215                     currType = USERL_ACT;
216                     qDebug()<<"actType:"<<actType;
217                 }
218                 if(this->bytesAvailable()>=1 && currType == USERL_ACT)
219                 {
220                     this->read((char*)&userLen,1);
221                     currType = USERL_USERLEN;
222                     qDebug()<<"userLen:"<<userLen;
223                 }
224                 if(this->bytesAvailable()>=userLen && currType == USERL_USERLEN)
225                 {
226                     user.resize(userLen);
227                     this->read(user.data(),user.size());
228                     currType = -1;
229                     msgType = -1;
230                     qDebug()<<"user:"<<QString(user);
231                     //emit signal to add user in listWidget
232                     emit addUserSignal(actType,QString(user));
233                 }
234                 break;
235             }
236             case PMSG:
237             {
238                 QString send,rec,time,msg;
239                 unsigned char sendLen,recLen,msgLen;
240                 if(this->bytesAvailable()>=1 && currType == PRI_TYPE)
241                 {
242                     this->read((char*)&sendLen,1);
243                     currType = PRI_FROMLEN;
244                     qDebug()<<"sendLen:"<<sendLen;
245                 }
246                 if(this->bytesAvailable()>=sendLen && currType == PRI_FROMLEN)
247                 {
248                     QByteArray ba;
249                     ba.resize(sendLen);
250                     this->read(ba.data(),ba.size());
251                     send = QString(ba);
252                     currType = PRI_FROM;
253                     qDebug()<<"send:"<<send;
254                 }
255                 if(this->bytesAvailable()>=1 && currType == PRI_FROM)
256                 {
257                     this->read((char*)&recLen,1);
258                     currType = PRI_TOLEN;
259                     qDebug()<<"recLen:"<<recLen;
260                 }
261                 if(this->bytesAvailable()>=recLen && currType == PRI_TOLEN)
262                 {
263                     QByteArray ba1;
264                     ba1.resize(recLen);
265                     this->read(ba1.data(),ba1.size());
266                     rec = QString(ba1);
267                     currType = PRI_TO;
268                     qDebug()<<"rec:"<<rec;
269                 }
270                 if(this->bytesAvailable()>=1 && currType == PRI_TO)
271                 {
272                     this->read((char*)&msgLen,1);
273                     currType = PRI_MSGLEN;
274                     qDebug()<<"msgLen:"<<msgLen;
275                 }
276                 if(this->bytesAvailable()>=msgLen && currType == PRI_MSGLEN)
277                 {
278                     QByteArray ba2;
279                     ba2.resize(msgLen);
280                     this->read(ba2.data(),ba2.size());
281                     time = QString(ba2).mid(0,18);
282                     msg = QString(ba2).mid(19);
283                     qDebug()<<"time:"<<time<<"msg:"<<msg;
284 
285                     currType = -1;
286                     msgType = -1;
287 
288                     emit priChatMsgSignal(send,send+" "+time+"\n"+msg);
289                 }
290                 break;
291             }
292             case QMSG:
293             {
294                 QString from,time,msg;
295                 unsigned char fromLen,msgLen;
296                 if(this->bytesAvailable()>=1 && currType == QM_TYPE)
297                 {
298                     this->read((char*)&fromLen,1);
299                     currType = QM_FROMLEN;
300                     qDebug()<<"fromLen:"<<fromLen;
301                 }
302                 if(this->bytesAvailable()>=fromLen && currType == QM_FROMLEN)
303                 {
304                     QByteArray ba;
305                     ba.resize(fromLen);
306                     this->read(ba.data(),ba.size());
307                     from = QString(ba);
308                     currType = QM_FROM;
309                     qDebug()<<"from:"<<from;
310                 }
311                 if(this->bytesAvailable()>=1 && currType == QM_FROM)
312                 {
313                     this->read((char*)&msgLen,1);
314                     currType = QM_MSGLEN;
315                     qDebug()<<"msgLen"<<msgLen;
316                 }
317                 if(this->bytesAvailable()>=msgLen && currType == QM_MSGLEN)
318                 {
319                     QByteArray ba;
320                     ba.resize(msgLen);
321                     this->read(ba.data(),ba.size());
322                     time = QString(ba).mid(0,18);
323                     msg = QString(ba).mid(19);
324                     currType = -1;
325                     msgType = -1;
326 
327                     qDebug()<<"msg:"<<time<<" "<<msg;
328                     emit groupChatMsgSignal(from+" "+time+"\n"+msg);
329                 }
330                 break;
331             }
332             case BROAD:
333             {
334                 QString msg;
335                 unsigned char msgLen;
336                 if(this->bytesAvailable()>=1 && currType == BROAD_TYPE)
337                 {
338                     this->read((char*)&msgLen,1);
339                     currType = BROAD_MSGLEN;
340                     qDebug()<<"msgLen:"<<msgLen;
341                 }
342                 if(this->bytesAvailable()>=msgLen && currType == BROAD_MSGLEN)
343                 {
344                     QByteArray ba;
345                     ba.resize(msgLen);
346                     this->read(ba.data(),ba.size());
347                     currType = -1;
348                     msgType = -1;
349                     msg = QString(ba);
350                     emit broadMsgSignal(msg);
351                 }
352                 break;
353             }
354             case GAGMSG:
355             {
356                 QString user;
357                 unsigned char userLen;
358                 char time;
359                 if(this->bytesAvailable()>=1 && currType == GAG_TYPE)
360                 {
361                     this->read((char*)&userLen,1);
362                     currType = GAG_USERLEN;
363                     qDebug()<<"userLen:"<<userLen;
364                 }
365                 if(this->bytesAvailable()>=userLen && currType == GAG_USERLEN)
366                 {
367                     QByteArray ba;
368                     ba.resize(userLen);
369                     this->read(ba.data(),ba.size());
370                     currType = GAG_USER;
371                     user = QString(ba);
372                     qDebug()<<"user:"<<user;
373                 }
374                 if(this->bytesAvailable()>=1 && currType == GAG_USER)
375                 {
376                     this->read((char*)&time,1);
377                     currType = -1;
378                     msgType = -1;
379                     qDebug()<<"time:"<<time;
380                     emit gagMsgSignal(user,time);
381                 }
382                 break;
383             }
384 
385 
386             default:
387             qDebug()<<"UNKNOW msgType2";
388             break;
389         }//switch
390     }//while
391 }//readData
View Code

基本上是一个模式的解析,这里用上了之前定于的枚举解析,很方便的防止包不完整的情况的发生

 

总结

我们用了四天做完了这个项目,做完后的感觉是,一些基础的知识很重要,这个项目的基础知识你必须手到擒来。在写代码前一定要把很多东西考虑清楚,想清楚之后去写代码就只是做码农的工作了,只是客户端的逻辑复杂一点点。在协议,框架等东西都很确定的情况下,你就不会晕,而是给自己一个模块一个模块的去写出来,所以,框架才是最重要的。

 

posted @ 2015-09-06 17:48  _Leo_H  阅读(1626)  评论(0编辑  收藏  举报