本篇主要讲述客户端设计过程并对客户端代码做简要解析介绍旨在与大家交流、学习同时请大家赐教。基本实现了注册、登录、私聊、群聊、等基础功能。近期服务端会一直开启,注册成功后系统会自动添加博主为好友、同时群组列表包含一个共有群包含所有用户。大家可以下载测试版体验交流下。
主要内容
- 基本信息
- 编码设计
- MainWidget主界面类
- LandWidget登录类
- Register注册类
- ChatFri聊天界面类
- FriList好友列表类
- Serch搜索类
- 写在最后
- 源码下载地址
- 鸣谢
基本信息
- 开发环境:Qtcreater5.1.2
- 服务端IP:39.106.102.75
- 端口号:8888
- 通信协议:聊天软件开发_1_数据交互协议_客户端 聊天软件开发_2_数据交互协议_服务端
- 测试版下载 build 1.1.0.2019.7.27点击下载
下载成功后解压点击WangWangChatClient可直接运行
- 效果预览
- 登录
- 注册
-
主界面
-
聊天界面
- 登录
编码设计
项目包含六个Qt设计师界面类,分别为MainWidget(主界面)、LandWidget(登录)、Register(注册)、FriList(好友or群列表)、ChatFri(聊天界面)、Serch(搜索)同时每个类配一个.cpp文件和.ui文件分别是对类的实现以及界面设计。两个资源包,分别是pic.qrc(存放表情包),icon.qrc(存放头像),sound.qrc(存放提示音)。下面将做详细介绍
1、MainWidget主界面类
打开程序后主函数创建主界面对象,默认隐藏主界面。构造函数调用connectToServer()与服务器建立连接,随后创建登录对象,并建立登录成功信号与销毁登录界面显示主界面的连接。
connect(land,&LandWidget::landSuccess,
[=]()
{
//当登陆界面传来登陆成功信号时
land->hide(); //隐藏登陆窗口
this->show(); //显示主窗口
this->user_active = land->user_active;
delete land; //登陆成功后销毁登陆界面对象
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(slotRecv()));
this->requestUserInfo(); //请求用户信息
//this->setUserInfo("00","kidjkod","this is a test user");
}
);
接着调用requestUserInfo()向服务端请求用户信息,在槽函数slotRecv()中处理服务端收到的消息,对消息类型进行解析并调用不同的函数进行处理
switch (mess_type) {
case 0:
//收到服务器发送的用户信息
setUserInfo(temp);//设置信息
break;
case 1:
//收到私聊消息
this->dealMessage(temp);
break;
case 2:
//好友信息
this->setFriend(temp);
break;
case 3:
//群组信息
setGroup(temp);
break;
case 4:
//收到群组消息
dealMessageGroup(temp);
default:
break;
}
完整.h声明文件
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QWidget>
#include <QTcpSocket>
#include <QJsonObject>
#include <QGroupBox>
#include <QToolButton>
#include <QList>
#include "frilist.h"
#include "landwidget.h"
#include "chatfri.h"
#include <QMediaPlayer>
namespace Ui {
class mainWidget;
}
class mainWidget : public QWidget
{
Q_OBJECT
public:
explicit mainWidget(QWidget *parent = nullptr);
~mainWidget();
bool connectToServer(); //连接服务器
void setUserInfo(QString info); //设置用户信息
void setFriend(QString info); //加载好友列表
void setGroup(QString info); //加载群组
void requestUserInfo(); //请求用户信息
void dealMessage(QString info); //处理私聊消息
void dealMessageGroup(QString info); //处理群聊消息
public slots:
void slotRecv(); //收到服务器消息
private slots:
void on_pushButton_clicked(); //关于按钮点击事件
void on_pushButton_2_clicked(); //搜索按钮点击事件
private:
Ui::mainWidget *ui; //界面
QTcpSocket *tcpSocket; //通信套接字声明
LandWidget *land; //登陆界面声明
int user_active; //当前登录的用户
FriList *fr; //好友列表
QMediaPlayer *player; //播放提示音
};
/******结构体Friend用于保存已加载的好友列表******/
typedef struct{
QString id; //帐号
QString user; //昵称
QString icon; //头像
QString signature; //签名
QString status; //是否在线
}Friend;
#endif // MAINWIDGET_H
2、 LandWidget登录类
登录类主要包括对ui界面输入信息的打包、发送注册信息到服务端、处理服务端返回的信息以及记住帐号密码。构造函数中对相关信息进行初始化,包括设置窗口标题,禁止最大化按钮、禁止拖动窗口以及设置密码编辑框为隐藏模式。
LandWidget::LandWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::LandWidget)
{
ui->setupUi(this);
setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint); // 禁止最大化按钮
setFixedSize(445,508); // 禁止拖动窗口大小
setWindowTitle(QStringLiteral("登录")); //设置窗口标题
ui->password->setEchoMode(QLineEdit::Password); //设置密码编辑框为隐藏模式
}
用户点击登录按钮后对输入信息进行简要的合法性判断(主要判断交由服务端进行),合法将信息打包发至服务端等待服务端返回信息
void LandWidget::on_send_clicked()
{
//点击登陆按钮时
if(ui->id->text()=="")
{
//当id未填写时
QMessageBox::warning(this,QStringLiteral("错误"),QStringLiteral("请填写id"));
}else {
if(ui->password->text()=="")
{
//当密码未填写时
QMessageBox::warning(this,QStringLiteral("错误"),QStringLiteral("请填写密码"));
}else {
//账号密码都填写时
if(ui->remember->isChecked())
{
//当记住密码被选中时
}
tcpSocket->write(landPack(ui->id->text().toInt(),ui->password->text()).toUtf8().data());//将登陆信息打包发给服务器
//emit landSuccess();
}
}
}
服务端返回错误则弹出警告框提示密码错误返回正确则发送landSuccess()信号给主界面,登录窗口的销毁由主界面进行。点击登录窗口中的注册按钮是创建注册对象,显示注册窗口,注册完成后销毁注册对象
void LandWidget::on_registeButton_clicked()
{
//点击注册按钮
regi = new Register;
regi->show();
regi->setSocket(this->tcpSocket);
}
完整.h声明文件
#ifndef LANDWIDGET_H
#define LANDWIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include "register.h"
namespace Ui {
class LandWidget;
}
class LandWidget : public QWidget
{
Q_OBJECT
public:
int user_active; //当前用户
explicit LandWidget(QWidget *parent = nullptr);
~LandWidget();
void setTcpSocket(QTcpSocket *tcp); //设置通信套接字
QString landPack(int id,QString password); //打包登陆信息
bool judgement(QByteArray array); //判断服务器返回的数据
public slots:
void slotDataRecv(); //处理收到的服务器消息
signals:
void landSuccess(); //登陆成功信号
private slots:
void on_send_clicked(); //登录按钮点击事件
void on_registeButton_clicked(); //注册按钮点击事件
private:
Ui::LandWidget *ui; //ui界面
QTcpSocket *tcpSocket; //通信套接字
Register *regi; //注册对象声明
};
#endif // LANDWIDGET_H
3、 Register注册类
注册对象由登录对象创建,构造函数中包括对相关数据的初始化,添加待用户选择的头像、简单的合法性检查、与服务端信息交互以及提示用户
Register::Register(QWidget *parent) :
QWidget(parent),
ui(new Ui::Register)
{
ui->setupUi(this);
setWindowTitle(QStringLiteral("注册"));
ui->pass->setEchoMode(QLineEdit::Password); //设置密码编辑框为隐藏模式
ui->pass_->setEchoMode(QLineEdit::Password); //设置密码编辑框为隐藏模式
setWindowFlags(windowFlags()&~Qt::WindowMaximizeButtonHint); // 禁止最大化按钮
setFixedSize(441,507); // 禁止拖动窗口大小
icon = "00";
icoDia = new QDialog(this);
layout_fri = new QVBoxLayout(icoDia);
layout_fri->setMargin(20);
layout_fri->setAlignment(Qt::AlignBaseline);
//添加头像
for(int i=0;i<7;i++)
{
QString path = QString(":/icon/0%1.ico").arg(i);
QPushButton *bu_temp = new QPushButton(icoDia);
bu_temp->setIcon(QPixmap(path));
qDebug()<<"path is "<<path;
icolist.append(bu_temp);
layout_fri->addWidget(bu_temp);
connect(bu_temp,&QPushButton::clicked,
[=]() mutable
{
this->icoDia->hide();
ui->pushButton_2->setIcon(QPixmap(path));
//ui->messEdit->append("\n");
icon[0] = path[7];
icon[1] = path[8];
}
);
}
}
用户点击注册按钮后程序简要检查信息完整性(其它检查由服务端进行处理),并等待服务端返回分配给用户的帐号,提示用户注册成功
void Register::regiSucce(QString info)
{
//
int i;
QString id;
for(i=0;i<info.size();i++)
{
if(info[i]==' ')
{
i++;
break;
}
}
for(int j=0;i<info.size();i++,j++)
{
id[j] = info[i];
}
qDebug()<<"服务器发来的id是 "<<id;
QString temp = QString("系统给您分配的id是\n[%1]\n<请牢记您的id>").arg(id);
QMessageBox::warning(this,QStringLiteral("id分配"),temp);
this->hide();
}
完整.h声明文件
#ifndef REGISTER_H
#define REGISTER_H
#include <QWidget>
#include <QTcpSocket>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>
namespace Ui {
class Register;
}
class Register : public QWidget
{
Q_OBJECT
public:
explicit Register(QWidget *parent = nullptr);
~Register();
void setSocket(QTcpSocket *soc )
{
tcpSocket = soc;
}
void regiSucce(QString info);
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::Register *ui;
QTcpSocket *tcpSocket;
QDialog *icoDia;
QVBoxLayout *layout_fri;
QList <QPushButton *> icolist;
QString icon;
};
#endif // REGISTER_H
4、ChatFri聊天界面类
此类主要包括记录当前聊天对象、提供待用户选择的表情包、处理用户输入的信息、以及显示当前聊天对象发来的信息,此处应注意对群消息或私聊消息的不同处理。用户点击表情选择时打开创建表情界面并显示,点击选择相应表情后在输入栏进行显示
void ChatFri::AddIco()
{
//添加表情
for(int i=0;i<7;i++)
{
QString path = QString(":/face/face0%1.ico").arg(i);
QPushButton *bu_temp = new QPushButton(icoDia);
bu_temp->setIcon(QPixmap(path));
qDebug()<<"path is "<<path;
icolist.append(bu_temp);
layout_fri->addWidget(bu_temp);
connect(bu_temp,&QPushButton::clicked,
[=]() mutable
{
this->icoDia->hide();
qDebug()<<path;
QString path_ = QString("<img src=\"%1\"/>").arg(path);
ui->messEdit->insertHtml(path_);
//ui->messEdit->append("\n");
}
);
}
}
用户点击发送按钮时,打包输入框信息,发送给服务端,同时调用显示消息以将当前用户发送的消息显示在显示框
void ChatFri::on_sendButton_clicked()
{
//发送
qDebug()<<"ui:"<<ui->messEdit->toPlainText();
qDebug()<<ui->messEdit->toHtml();
QString str = ui->messEdit->toHtml();
//QString temp = ui->messEdit->toPlainText();
recvMessage(str,this->id,false);
//str = QString("message %1 %2 %3").arg(this->id).arg(this->friend_id).arg(str);
if(id_group == 7001)
{
//群消息
str = QString("groupmessage %1 %2 %3").arg(this->id).arg(this->id_group).arg(str);
ui->messEdit->clear();
tcpSocket->write(str.toUtf8().data());
}
else{
str = QString("message %1 %2 %3").arg(this->id).arg(this->friend_id).arg(str);
ui->messEdit->clear();
tcpSocket->write(str.toUtf8().data());
}
}
收到消息时对消息进行相关判断并组时间、昵称以及内容包然后进行显示
void ChatFri::recvMessage(QString mess,int id_,bool group)
{
//ui->messBrowser->setAlignment(Qt::AlignRight);
qDebug()<<"line 55 收到消息";
QString temp_user;
//int us = 100001;
qDebug()<<"shoudaoxiaoxi "<<id<<" "<<id_;
if(id_ == this->id)
{
temp_user = user;
ui->messBrowser->setAlignment(Qt::AlignRight);
}else{
temp_user = userfriend;
ui->messBrowser->setAlignment(Qt::AlignLeft);
}
QDateTime current_date_time =QDateTime::currentDateTime();
QString current_date =current_date_time.toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
QString temp;
if(group)
{
temp = QString("%1 [%2]\n").arg(id_).arg(current_date);
}
else{
temp = QString("%1 [%2]\n").arg(temp_user).arg(current_date);
}
ui->messBrowser->append(temp);
ui->messBrowser->insertHtml(mess);
ui->messBrowser->append("\n");
QScrollBar *scrollbar = ui->messBrowser->verticalScrollBar();
if (scrollbar)
{
scrollbar->setSliderPosition(scrollbar->maximum());
}
}
完整.h声明文件
#ifndef CHATFRI_H
#define CHATFRI_H
#include <QWidget>
#include <QDialog>
#include <QList>
#include <QPushButton>
#include <QDebug>
#include <QVBoxLayout>
#include <QDateTime>
#include <QTcpSocket>
#include <QCloseEvent>
namespace Ui {
class ChatFri;
}
class ChatFri : public QWidget
{
Q_OBJECT
public:
explicit ChatFri(QWidget *parent = nullptr);
~ChatFri();
void AddIco(); //添加
void recvMessage(QString mess,int id,bool group);
void setInfo(int id_,int frid_,QString user,QString fri); //设置对话框
int getFrid()
{
return this->friend_id;
}
void setSocket(QTcpSocket *sock)
{
this->tcpSocket = sock;
}
int id_group;
private slots:
void on_pushButton_pressed();
void on_sendButton_clicked();
void on_pushButton_2_clicked();
protected:
//void mousePressEvent(QMouseEvent* event);
public slots:
protected:
void closeEvent(QCloseEvent *event);
signals:
void kill();
private:
QVBoxLayout *layout_fri;
QVBoxLayout *layout_lis;
QList <QPushButton *> icolist;
Ui::ChatFri *ui;
QDialog *icoDia;
int id; //当前用户
int friend_id; //对话框用户
QString user;
QString userfriend;
QTcpSocket *tcpSocket;
};
#endif // CHATFRI_H
5、FriList好友列表类
此类是针对之前设计过的一个类进行了细微修改,读者可参考 Qt学习——利用QToolBox实现QQ好友列表
6、Serch搜索类
主要功能是搜索添加好友,目前还未开发此模块
写在最后
- 完整项目 https://github.com/zhaomangang/WangWangChat.git
- 客户端暂时吿一段落,下期将详细介绍服务端编码,有很多地方仍存在bug,只是实现了基本功能,后面有时间了会继续完善。同时也欢迎大家在此基础上进行完善。不足之处,敬请指出。