1.项目概述
这个项目是找来的源代码,我把源代码一行一行的自己敲进去再运行调试出来的,实际上是学习,不是我的什么开发。该项目有两个部分,一个是客户端,一个是服务器端。客户端负责监听客户端发来的信息并做出相应的处理,发送广播。客户端实现注册、登录、对话、接受广播等功能
在客户端中,没有多进程,只有一个进程负责对所有用户的处理,所以服务器广播的发送就要求所有客户端绑定一个端口。客户端中的好友列表里实际上是服务器数据库里所有的 用户,这也就是说这个项目实际上是个简单的聊天室程序,而且没有群聊,只能进行点对点的聊天,还没有文件传送这样的功能。
这里刨个坑,以后有时间有能里的时候会弥补上面的缺陷,做成一个完整的聊天室程序。具体就是:服务器改为多进程服务器,可以为每个客户开辟一个进程;客户端中实现群聊、文件传输功能。
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.代码上传
今天的文章基于QT的网络聊天系统分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/27366.html