目录
任务
聊天工具的设计与实现
要求
编写一个基于Linux操作系统+ C语言的聊天应用程序,涉及的知识:多进程、进程通信、Qt、MySQL等知识。
基本功能
两个主机端(服务器和客户端)进行图形化界面通信;
一、任务及要求分析
编写一个基于Linux操作系统+ C语言的聊天应用程序,涉及的知识:多进程、进程通信、Qt、MySQL等知识。
- 创建两个主机端,分别为服务器Server和客户端Client,两个主机可以进行图形化界面通信。
- 使用mysql创建数据库ly,创建两个表user和chat,分别用于存储用户信息和通信内容信息。
- 使用QT实现图形化界面,通过QT Designer设计窗口。
二、实验过程
2.1 程序框架设计
2.1.1源文件导图
创建两个项目,分别人Client和Server,创建相关文件。
2.2.2 Server和Client窗口视图及属性介绍
使用QT自带的ui设计模块,按照如下图进行设计。
xx.ui文件在运行时,会自动创建ui_xx.h头文件。
2.2 数据库准备
在终端打开mysql,创建数据库ly,创建表user用于存储用户信息,创建表chat用于存储通信内容及相关信息。
为表user添加新用户:用户名为ly,密码为1234。
表chat为空。
2.3注册及登录
2.3.1 注册
输入用户名和密码,点击注册按钮,进行用户注册。注册成功显示“注册成功!”,否则显示“注册失败”。
注册成功后,在终端打开mysql,查询user表,新增一条记录。
2.3.2登录
输入用户名和密码,用户存在且密码正确显示“登录成功!”,否则显示“登录失败!”。
登录后,再次登录会显示“已登录”。
2.3.3 通信
在各自输入区输入信息后,点击发送,会同时显示到两个窗口的通信内容记录框内。
同时,打开数据库查看chat表,新增记录。
2.3.4 关闭服务器,断开连接
三、调试过程中出现的问题及相应解决办法
问题一:客户端无法连接到服务器。
解决办法:在Server.pro和Client.pro文件中添加语句:
QT += network sql
问题二:通信内容不能是中文,如果输入中文会乱码。
解决方法:在数据传输过程中,采用utf-8编码。
问题三:使用QT连接mysql时,连接不上,显示错误信息“...not load”。Mysql配置有问题,QT版本过高(5.9)也会导致无法连接。
解决方法:重新配置mysql,下载低版本QT(5.6)。
问题四:安装完mysql后,第一次运行时,会提示输入密码,但密码是随机生成的,根本不知道密码,无法进入mysql。
解决方法:打开/etc/my.cnf文件,加上一行代码skip-grant-tables,表示无密码运行mysql。然后再直接用mysql -uroot登录,发现登录成功。然后执行sql语句修改密码 update user set Password='密码' where User='root'。最后把之前加的那段代码删掉,就可以使用自己的密码登录了。
问题五:编写完代码后成功运行,然后我想改一下文件名,改完之后,程序不能运行。报错:invalid use of incomplete type 'class Ui::Server'ui(new Ui:: Server)。
解决方法:在其对应的ui文件中,整个界面的ObjectName没有进行更改,打开其对应的ui文件,将其ObjectName更改即可。ObjectName即是在Designer界面下,选中控件后右边属性框的前列,修改名称后,重新构建,发现构建成功。
四、程序源码及其注释
4.1 Server.h
#ifndef SERVER_H
#define SERVER_H
#include <QDialog>
#include <QTcpSocket>
#include <QTcpServer>
#include <QMessageBox>
#include <QDebug>
#include <QDateTime>
#include <QSqlError>
#include <QSqlDatabase>
#include <QSqlQuery>
namespace Ui {
class Server;
}
class Server : public QDialog
{
Q_OBJECT
public:
explicit Server(QWidget *parent = 0);
~Server();
private slots:
// 定义函数,在cpp里实现。
void on_stopButton_clicked();
void acceptConnection();
void sendMessage();
void displayError(QAbstractSocket::SocketError);
void receiveMessage();
void saveMessage(QString , QString , QString);
private:
Ui::Server *ui;
QTcpServer *tcpServer;
QTcpSocket *tcpSocketConnection;
};
#endif
4.2 Server.cpp
#include "server.h"
#include "ui_server.h"
Server::Server(QWidget *parent) :
QDialog(parent),
ui(new Ui::Server)
{
ui->setupUi(this);
tcpServer=new QTcpServer(this);
// 调用listen监听端口,设置IP地址和端口号。
if (!tcpServer->listen(QHostAddress::Any, 7777)) {
qDebug() << tcpServer->errorString();
close();
}
tcpSocketConnection = NULL;
// 连接信号newConnection
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(acceptConnection()));
// 将发送按钮和sendMessage函数关联起来
connect(ui->pbtnSend,SIGNAL(clicked(bool)),this,SLOT(sendMessage()));
}
Server::~Server()
{
delete ui;
}
void Server::acceptConnection()
{
// 调用nextPendingConnection获取连接进来的socket
tcpSocketConnection = tcpServer->nextPendingConnection();
// 根据不同的信号将tcpSocketConnection和相关函数关联
connect(tcpSocketConnection,SIGNAL(disconnected()),this,SLOT(deleteLater()));
connect(tcpSocketConnection,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(displayError(QAbstractSocket::SocketError)));
// 接受Client发来的消息,readyRead()准备读取信号,异步读取数据。
connect(tcpSocketConnection, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
}
// 关闭服务器,断开连接。
void Server::on_stopButton_clicked()
{
tcpSocketConnection->abort();
QMessageBox::about(NULL,"Connection","服务器关闭,连接终止。");
}
// 向Client发送消息
void Server::sendMessage()
{
//获取 输入框 里所输入的信息。
QString str = ui->textEdit_input->text();
//获取当前时间
QDateTime time = QDateTime::currentDateTime();
QString nowtime = time.toString("yyyy-MM-dd hh:mm:ss");
//显示在Server的消息记录里
ui->textEdit_log->append(nowtime + " Server:");
ui->textEdit_log->append(" " + str);
tcpSocketConnection->write(ui->textEdit_input->text().toUtf8());
// 将这条内容的有关信息存储到mysql。
saveMessage(nowtime, "Server", str);
}
// 存储到mysql
void Server::saveMessage(QString time, QString user, QString content)
{
// 连接并打开mysql
QSqlDatabase dataBase=QSqlDatabase::addDatabase("QMYSQL");
dataBase.setHostName("localhost");
dataBase.setUserName("root");
dataBase.setPassword("123456");
dataBase.setDatabaseName("ly");
dataBase.open();
QSqlQuery query(dataBase);
QString sql=QString("select *from chat");
query.exec(sql);
if(query.numRowsAffected() == 0)
{
// 将信息insert到chat表里。
QString savesql = QString("INSERT INTO chat(time, user, content)");
savesql += QString(" VALUES('%1','%2','%3')").arg(time).arg(user).arg(content);
}
}
// 接收从Client发送来的消息。
void Server::receiveMessage()
{
QDateTime time = QDateTime::currentDateTime();
QString nowtime = time.toString("yyyy-MM-dd hh:mm:ss");
// 使用readAll函数读取所有信息
QString str = tcpSocketConnection->readAll();
ui->textEdit_log->append(nowtime + " Client:");
ui->textEdit_log->append(" " + str);
}
// 异常信息
void Server::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpSocketConnection->errorString();
}
4.3 main.cpp
#include "server.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Server w;
w.setWindowTitle("Server");
w.show();
return a.exec();
}
4.4 client.h
#ifndef CLIENT_H
#define CLIENT_H
#include <QDialog>
#include <QTcpSocket>
#include <QObject>
#include <QMessageBox>
#include <QDebug>
#include <QDateTime>
#include <QSqlError>
#include <QSqlDatabase>
#include <QSqlQuery>
namespace Ui {
class Client;
}
class Client : public QDialog
{
Q_OBJECT
public:
explicit Client(QWidget *parent = 0);
~Client();
private slots:
void on_connectButton_clicked();
void receiveMessage();
void displayError(QAbstractSocket::SocketError);
void sendMessage();
bool check(QString ,QString );
void on_logonbutton_clicked();
void saveMessage(QString , QString , QString);
private:
Ui::Client *ui;
QTcpSocket *tcpSocket;
};
#endif // CLIENT_H
4.5 client.cpp
#include "client.h"
#include "ui_client.h"
Client::Client(QWidget *parent) :
QDialog(parent),
ui(new Ui::Client)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
// 关联登录按钮和函数,进行确认登录并连接到服务器
connect(ui->connectButton,SIGNAL(clicked()),this,SLOT(on_connectButton_clicked()));
connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(displayError(QAbstractSocket::SocketError)));
// 接受Server发来的消息,readyRead()准备读取信号,异步读取数据。
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
// 将发送按钮和sendMessage函数关联起来
connect(ui->pbtnSend2,SIGNAL(clicked(bool)),this,SLOT(sendMessage()));
}
Client::~Client()
{
delete ui;
}
// 连接数据库,判断所输入的用户名和密码是否正确
bool Client::check(QString ID, QString PW)
{
QSqlDatabase dataBase=QSqlDatabase::addDatabase("QMYSQL");
dataBase.setHostName("localhost");
dataBase.setUserName("vici");
dataBase.setPassword("123456");
dataBase.setDatabaseName("ly");
dataBase.open();
QSqlQuery showquery(dataBase);
QString showsql=QString("select *from user;");
showquery.exec(showsql);
if(showquery.numRowsAffected() != 0)
{
while(showquery.next())
{
if(showquery.value(0).toString() == ID && showquery.value(1).toString() == PW)
return true;
}
}
return false;
}
// 登录按钮,登录信息正确则连接到服务器
void Client::on_connectButton_clicked()
{
if(tcpSocket->state()!=QAbstractSocket::ConnectedState)
{
// 获取用户名和密码
QString ID = ui->IDLineEdit->text();
QString PW = ui->PWLineEdit->text();
// 检查
if(check(ID, PW))
{
tcpSocket->connectToHost("127.0.0.1", 7777);
if(tcpSocket->waitForConnected(10000))
{
QMessageBox::about(NULL, "Connection", "登录成功!");
}
else
{
QMessageBox::about(NULL,"Connection","登录失败!");
}
}
}
else
QMessageBox::information(NULL,"","已登录。");
}
// 发送信息,并存储到数据库
void Client::sendMessage()
{
QString str = ui->textEdit_input2->text();
QDateTime time = QDateTime::currentDateTime();
QString nowtime = time.toString("yyyy-MM-dd hh:mm:ss");
ui->textEdit_log2->append(nowtime + " Client:");
ui->textEdit_log2->append(" " + str);
tcpSocket->write(ui->textEdit_input2->text().toUtf8()); //toLatin1
saveMessage(nowtime, "Client", str);
}
// 接收Server发来的消息,并显示到消息记录框里。
void Client::receiveMessage()
{
QDateTime time = QDateTime::currentDateTime();
QString nowtime = time.toString("yyyy-MM-dd hh:mm:ss");
QString str = tcpSocket->readAll();
ui->textEdit_log2->append(nowtime + " Server:");
ui->textEdit_log2->append(" " + str);
}
// 将信息存储到数据库
void Client::saveMessage(QString time, QString user, QString content)
{
QSqlDatabase dataBase=QSqlDatabase::addDatabase("QMYSQL");
dataBase.setHostName("localhost");
dataBase.setUserName("root");
dataBase.setPassword("123456");
dataBase.setDatabaseName("ly");
dataBase.open();
QSqlQuery query(dataBase);
QString sql=QString("select *from chat");
query.exec(sql);
if(query.numRowsAffected() == 0)
{
QString savesql = QString("INSERT INTO chat(time, user, content)");
savesql += QString(" VALUES('%1','%2','%3')").arg(time).arg(user).arg(content);
}
}
// 注册按钮,并存储到数据库里。
void Client::on_logonbutton_clicked()
{
QSqlDatabase dataBase=QSqlDatabase::addDatabase("QMYSQL");
dataBase.setHostName("localhost");
dataBase.setUserName("vici");
dataBase.setPassword("123456");
dataBase.setDatabaseName("ly");
dataBase.open();
QSqlQuery query(dataBase);
QString ID = ui->IDLineEdit->text();
QString PW = ui->PWLineEdit->text();
QString sql=QString("select *from user");
query.exec(sql);
if(query.numRowsAffected() == 0)
{
QString savesql = QString("INSERT INTO user(ID, PW)");
savesql += QString(" VALUES('%1','%2')").arg(ID).arg(PW);
bool ok=query.exec(savesql);
if(ok)
{
QMessageBox::about(NULL, "Save", "注册成功!");
}
else
{
QMessageBox::about(NULL, "Save", "注册失败!");
}
}
}
void Client::displayError(QAbstractSocket::SocketError)
{
qDebug() << tcpSocket->errorString();
}
4.6 main.cpp
#include "client.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Client w;
w.setWindowTitle("Client");
w.show();
return a.exec();
}