基于QT的网络聊天系统

来源:互联网 发布:php 实现域名跳转 编辑:程序博客网 时间:2024/04/29 18:16
在客户端中,没有多进程,只有一个进程负责对所有用户的处理,所以服务器广播的发送就要求所有客户端绑定一个端口。客户端中的好友列表里实际上是服务器数据库里所有的 用户,这也就是说这个项目实际上是个简单的聊天室程序,而且没有群聊,只能进行点对点的聊天,还没有文件传送这样的功能。

           这里刨个坑,以后有时间有能里的时候会弥补上面的缺陷,做成一个完整的聊天室程序。具体就是:服务器改为多进程服务器,可以为每个客户开辟一个进程;客户端中实现群聊、文件传输功能。

      2.运行环境与项目部署

           QT4.7,以及QT -Embedded移植,安装sqlite数据库,详见我的其他博客。

      3.使用说明和界面介绍

      服务器端如下:

      左上部分是对数据库的显示,这里只显示了ID、昵称、在线状态。

       

       IP地址填写你的服务器主机的IP ,端口绑定8888,点击“开始监听”以后,就开始监听。发送按钮就可以把“hello client”发送出去。

     客户端界面如下:

        在登录和注册之前必须要设置服务器的IP地址和端口(和上面必修一致),在程序中已经写好,点击设置就可以,

        点击确定后

        才可注册和登录。

       点击注册新帐号后弹出注册界面

        填好上诉信息后确定即完成注册,然后在上面的界面中登录,登录后弹出绘画界面

           下面是接受服务器的广播,上面是在线用户列表,点击其中一个弹出对话界面,

        "ssss"是自己发送的内容,有下面的文本框输入,点击“发送”后显示在上面,对方的回话也现在在此。


      4.tcp和udp消息的说明

         在具体设计和实现之前,首先列出内部通信的信息。客户端和服务器端的功能实现就是依靠接受不同的信息,判别后才完成相应的功能 

Tcp消息服务器端客户端(1):  “MSG_CLIENT_USER_REGISTER”接收
并读取一起发来的ID,PWD,Name,插入数据库,若已有则发送(2),插入成功(注册成功)则发送(3)发送
从注册界面获取的数据验证后发送,一起发送的还有ID,PWD,Name。(2):"MSG_ID_ALREADY_EXIST"发送
新用户数据插入数据库操作时发现已有该用户,发送此信息接收
接收后弹出Qessage对话框,提示已经注册,不要重复注册(3):"MSG_CLIENT_REGISTER_SUCCESS"发送接受
接受后将此消息通过udp发送给客户端(4):"MSG_USER_LOGIN"接收
同时接收的还有ID和PWD。服务器首先查询该ID是否存在,不存在则发送(5),密码不对则发送(6),已经登录则发送(7),最后发送(8)发送
由登录界面的确定按钮发送,同时发送的还有ID和PWD。(5):"MSG_ID_NOEXIST"发送
服务器查询该ID不存在接收
接收后弹出Qessage对话框,提示该ID不存在(6):"MSG_PWD_ERROR"发送
服务器查询密码不匹配接收
接收后弹出Qessage对话框,提示密码不匹配(7):"MSG_LOGIN_ALREADY"发送
服务器查询该用户已登录接收
接收后弹出Qessage对话框,提示该用户已登录(8):"MSG_LOGIN_SUCCESS"发送
发送并在数据库中修改状态为登录状态,并存储本次登录的IP接收
接收后弹出好友界面准备聊天


Udp消息服务器端客户端(1)."MSG_CLIENT_NEW_CONN"接收
同时接受的还有本次登录的ID,此后向发送本次消息的客户端回写(5)、向所有客户端发送(7)发送
会话界面初始化时发送,同时发送的还有该次登录的ID(2)."MSG_CLIENT_REGISTER_SUCCESS"接收
接受后刷新显示数据库的tableview发送
在TCP接受(3)后发送(3)."MSG_USER_LOGOUT"接收
首先在数据库中设置该用户下线,刷新tableview,随后向所有用户发送(8)发送
会话界面按下退出按钮后发送,一起发送的还有该次登录的ID(4)."MSG_CLIENT_CHAT"接收
接收后向对话方的客户端发送(4),一起发送的还有发起对话的客户ID以及谈话内容发送
一起发送的还有自己的ID(发起会话的ID),对话方的ID以及谈话内容
接收
接收后调用回话界面,显示发来的内容和发起人的ID(5)."MSG_ALL_USER_ONLINE"接收
同时发送的还有所有在线用户的ID和NAME接收
接受到的还有所有在线用户的ID和NAME,用于显示自己的好友列表。(6)."MSG_SERVER_INFO"发送
向所有用户发送系统消息,由服务器端发送按钮发送接收
显示系统消息(7)."MSG_NEW_USER_LOGIN"发送
向所有用户发送新登录用户的ID和NAME接收
在好友列表中添加这名刚登录的用户(8)."MSG_CLIENT_LOGOUT"发送
在接收(3)后向所有客户端发送,一起发送的还有下线用户的ID和name(显示在各个用户列表的),接收
一起接收的还有下线用户的ID和NAME,接受后在自己的显示列表中删除这名用户

      5.部分重要功能语句

         很多核心的设计和代码编写都是重复的,了解这些代码将有益于程序的理解

        (1).Tcp和Udp消息发送

             QString msgType="MSG_ID_ALREADY_EXIST";
             QByteArray block;
             QDataStream out(&block,QIODevice::WriteOnly);          //拿QByteArray对象来进行加工也就是所谓的串行化
             out.setVersion(QDataStream::Qt_4_6);         //设置数据流的版本,客户端和服务器端使用的版本要相同
             out<<(quint16)0<<msgType;                           //这里其实要分成两部分看前面的0只是用来占空间的,后面才是真正要发送的数据
             out.device()->seek(0);                                      //吧流定位到开始的位置,这个位置是刚才用数字0来进行填充的空间
             out<<(quint16)(block.size()-sizeof(quint16));           ////这里是计算出真正要发送的数据大小,把这个计算后的值填入之前提前占好的空间中

            使用out<<(quint16) 0,在block的开始添加了一个quint16大小的空间,也就是两字节的空间,它用于后面放置文件的大小信息。然后out<<msgType输入实际的文件(当然msgType后面也跟追加内容,比如ID,PWD,NAME),这里是字符串"MSG_ID_ALREADY_EXIST"。当文件输入完成后我们在使用out.device()->seek(0);返回到block的开始,加入实际的文件大小信息,也就是后面的代码,它是实际文件的大小:out<<(quint16) (block.size() – sizeof(quint16))。这样block.size包括两部分:文件大小信息(2字节)+文件实际大小(msgType的长度)。

我们都在数据流的最开始写入完整文件的大小信息,这样接收端就可以根据大小信息来判断是否接受到了完整的文件。

         尤其是在使用Tcp时:TCP数据是一串长长的流,你事先不知道它的长度,因此你需要现用一个东西来占用TCP流最开始的那段空间,当加入真正要发送的数据的时候,流的大小才能确定下来,这个时候就吧计算好的结果放到之前占的那个空间去

       (2)static和const修饰类的成员函数

            static修饰静态成员函数,const修饰的成员函数的this指针所指向的对象是一个常量。

            静态函数只能使用本类中的静态成员数据或函数,不能使用非静态成员;const修饰的成员函数不能调用、修改对象的数据成员,在函数体内只能调用const修饰的成员函数,

           static和const所修饰的成员函数在类呗引用时会自动运行。

           使用QSqlQueryModel和QTableView可以显示数据库,代码里,数据库里有一列是表示是否在线(用01存储),1表示在线,0表示离线,但是显示的时候不希望显示“1”或者“0”,而是希望显示汉字“在线”、“离线”。
  码里首先写了一个class MySqlQueryModel : public QSqlQueryModel,然后只有一个共有成员:
QVariant data(const QModelIndex &item,int role=Qt::DisplayPropertyRole) const;
其内容如下:
  QVariant value=QSqlQueryModel::data(index,role);
  if(
value.isValid() && role==Qt::DisplayRole && index.column()==2)
  {
  value=(value.toInt()==1?tr("在线"):tr("离线"));
  return value;
  }

然后在引用QSqlQueryModel时,数据库里的0和1会自动转换成“在线”、“离线”。

      (3)正则表达式

          用户在界面输入的数据必须进行检验,检验合格后才能使用。比如ID号要求5~9位数字,IP号要求型如xxx.xxx.xxx.xxx等等。正则表达式准确书写比较繁琐,这里列出用的。

        QRegExp rx("^[1-9]{1,2}[0-9]{4,7}$");                //5-9位ID号,第一位不能为0

        QRegExp rxIp("\\d+\\.\\d+\\.\\d+\\.\\d+");               //IP地址
        QRegExp rxPort(("[1-9]\\d{3,4}"));                       //端口号

        rx.setPatternSyntax(QRegExp::RegExp);
        if(!rx.exactMatch(id))
        {
            QMessageBox::warning(NULL,tr("提示"),tr("请输入5~9位数的QQ号"));
        }

       6.一些问题

        在研究和调试代码的过程中遇到很多问题,有的很有价值,这里列出来。

        (1).在一台机器中为什么不能登录两个客户端?

               单进程的服务器广播发送要求所有客户端绑定一个公告已知的端口,程序中是“6666”,所以一台机器中不能有两个客户端运行。解决这个问题应该是设计多进程服务器,为每个客户端保留其端口号。

         (2).Tcp和Udp消息的功能有何不同?

              Tcp主要用户登录,Udp用于通信。

         (3).chatFrrm Hash

               这是一个类似与快捷方式的东西,把某个类和某个字符串(也可以是其他的)绑定。在聊天中,用户点击好友列表中的好友,相应会弹出和谁对话的对话框,比如,字符'a'就绑定了和'a'用户的聊天界面(类)

                          

       7.代码上传
0 0