一百一十九、简易版网络聊天室实现
119.1 QT实现连接TCP协议
119.1.1 基于TCP的通信流程
119.1.2 QT中实现服务器过程
- 使用QTcpServer实例化一个服务器对象
- 设置监听状态,通过listen()函数,可以监听特定的主机,也可以监听所有客户端,端口号可以是系统自动分配的,也可以是指定端口号。
- 如果有客户端发来连接请求,那么服务器就自动发射一个newConnection信号,我们就可以将该信号连接到自定义的槽函数中处理该客户端的操作。
- 此时服务器和客户端已经建起了连接,可以调用nextPandingConnection获取最新连接的客户端套接字,可以将该套接字存放在服务器的客户端容器中。
- 当客户端发来数据时,该客户端就会自动发射一个readyRead信号,我们可以将该信号连接到自定义的槽函数中读取客户端数据。
- 通过read(),readLine(),readAll()读取套接字里的数据,可以通过write()往套接字中写入数据
- 关闭服务器使用close即可
119.1.3 QT中实现客户端过程
- 使用QTcpSocket实例化一个客户端对象
- 将客户端连接到服务器,使用connectToHost, 给定主机地址,端口号
- 如果连接成功,该客户端会自动发射connected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。
- 此时,客户端和服务器已经建立了连接,如果服务端发来数据,那么该客户端会自定发射readyRead信号,可以将该信号连接到自定义的槽函数中读取服务端中数据
- 可以使用read() readLine() readAll()读取套接字中数据,使用write往套接字中写入数据
- 客户端断开与服务器的连接,使用disConnectFromHost, 如果断开成功,客户端会自动发射disconnected信号,我们可以将该信号连接到自定义的槽函数中处理相关逻辑代码。
119.2 服务器端
119.2.1 UI 界面
119.2.2 qt_server.h
#ifndef QT_SERVER_H
#define QT_SERVER_H#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QTcpSocket>
#include <QMessageBox>
#include <QDebug>QT_BEGIN_NAMESPACE
namespace Ui { class Qt_Server; }
QT_END_NAMESPACEclass Qt_Server : public QWidget
{Q_OBJECTpublic:Qt_Server(QWidget *parent = nullptr);~Qt_Server();private slots:void on_startBtn_clicked();public slots:// 自定义的 处理连接的槽函数声明void newConnection_slot();// 自定义的 处理接收数据的槽函数声明void readyRead_slot();private:Ui::Qt_Server *ui;QTcpServer *server;QList<QTcpSocket *> socketList;
};
#endif // QT_SERVER_H
119.2.3 qt_server.cpp
#include "qt_server.h"
#include "ui_qt_server.h"Qt_Server::Qt_Server(QWidget *parent): QWidget(parent), ui(new Ui::Qt_Server)
{ui->setupUi(this);// 给服务器指针创建空间server = new QTcpServer(this);
// socketList = new QList<>();
}Qt_Server::~Qt_Server()
{delete ui;
}// 点击启动服务器按钮对应的槽函数实现
void Qt_Server::on_startBtn_clicked()
{if(ui->startBtn->text() == "启动服务器"){// 获取UI界面上输入的端口号quint16 port = ui->portEdit->text().toUInt();// 使服务器进入监听状态,返回值是bool类型if(server->listen(QHostAddress::Any,port)){QMessageBox::information(this,"提示","服务器启动成功");}else{QMessageBox::critical(this,"错误","服务器启动失败");}// 此时服务器已经进入监听状态,如果有客户端发来链接请求,// 则服务器会自动发射一个newConnection信号,需要将该信号连接到自定义的槽函数中处理连接的套接字connect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);ui->startBtn->setText("已启动服务器");ui->startBtn->setStyleSheet("background-color:green");}else{server->close();disconnect(server, &QTcpServer::newConnection, this, &Qt_Server::newConnection_slot);ui->startBtn->setStyleSheet("background-color:white");ui->startBtn->setText("启动服务器");}}// 自定义的 处理连接的槽函数声明
void Qt_Server::newConnection_slot()
{qDebug() << "有新客户的连接";// 获取最新连接的客户端套接字,并放入容器中,此时客户端与服务器已经建立起连接QTcpSocket *s = server->nextPendingConnection();socketList.push_back(s);// 如果有客户端发送数据,那么此客户端就会自动发射readyRead信号// 需要将该信号与自定义的处理接收数据的槽函数连接connect(s, &QTcpSocket::readyRead, this, &Qt_Server::readyRead_slot);
}// 自定义的 处理接收数据的槽函数实现
void Qt_Server::readyRead_slot()
{// 移除无效的客户端// 遍历所有的客户端for(int i=0; i<socketList.count(); i++){// 判断每个客户端的状态,返回值是枚举类型// socketList.at(i)->state();if(0 == socketList.at(i)->state()){// 移除当前客户端,通过下标删除socketList.removeAt(i);}}// 遍历容器,找到有需要读取数据的客户端for(int i=0; i<socketList.count(); i++){// 如果当前客户端的 有效字节数 不为0,代表当前客户端有需要读取的数据if(0 != socketList.at(i)->bytesAvailable()){// 读取客户端中的数据QByteArray msg = socketList.at(i)->readAll();// 将读取的数据放到UI界面上ui->listWidget->addItem(QString::fromLocal8Bit(msg));// 将数据发送给所有客户端for (int j=0; j<socketList.count(); j++) {socketList.at(i)->write(msg);}}}
}
119.2.4 main.cpp
#include "qt_server.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Qt_Server w;w.show();return a.exec();
}
119.3 客户端
119.3.1 UI 界面
119.3.2 qt_client.h
#ifndef QT_CLIENT_H
#define QT_CLIENT_H#include <QWidget>
#include <QTcpServer>
#include <QList>
#include <QTcpSocket>
#include <QMessageBox>
#include <QDebug>QT_BEGIN_NAMESPACE
namespace Ui { class Qt_Client; }
QT_END_NAMESPACEclass Qt_Client : public QWidget
{Q_OBJECTpublic:Qt_Client(QWidget *parent = nullptr);~Qt_Client();public slots:void connected_slot();void readyRead_slot();private slots:void on_connectBtn_clicked();void on_msgBtn_clicked();void on_disconnectBtn_clicked();private:Ui::Qt_Client *ui;// 定一个客户端对象QTcpSocket *socket;// 定义一个用户名变量QString uname;
};
#endif // QT_CLIENT_H
119.3.3 qt_client.cpp
#include "qt_client.h"
#include "ui_qt_client.h"Qt_Client::Qt_Client(QWidget *parent): QWidget(parent), ui(new Ui::Qt_Client)
{ui->setupUi(this);// 给客户端对象创建空间socket = new QTcpSocket(this);// 初始化UI界面的组件状态ui->msgEdit->setEnabled(false);ui->msgBtn->setEnabled(false);ui->disconnectBtn->setEnabled(false);}Qt_Client::~Qt_Client()
{delete ui;
}void Qt_Client::connected_slot()
{// 告诉服务器我进来了QString msg = uname + " 来了,快接驾";// 将这个消息发送给服务器socket->write(msg.toLocal8Bit());// 此时说明服务器与客户端已经建立连接// 现在将UI界面的组件状态修改一下ui->msgEdit->setEnabled(true);ui->msgBtn->setEnabled(true);ui->disconnectBtn->setEnabled(true);ui->unameEdit->setEnabled(false);ui->ipEdit->setEnabled(false);ui->portEdit->setEnabled(false);ui->connectBtn->setEnabled(false);// 如果服务器发来数据,客户端会自动发射readyRead信号// 因此需要将readyRead信号连接到自定义的槽函数// 因为只需要连接一次,所以也是该在构造函数中写连接函数connect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);
}void Qt_Client::readyRead_slot()
{// 走到了这一步,说明服务器给客户端发送了消息,现在需要进行读取QByteArray msg = socket->readAll();// 将这个数据放到UI界面的消息显示框中ui->listWidget->addItem(QString::fromLocal8Bit(msg));
}// 连接服务器按钮 对应的槽函数
void Qt_Client::on_connectBtn_clicked()
{// 获取UI界面的IP和PORT,还有unameQString ip = ui->ipEdit->text();quint16 port = ui->portEdit->text().toUInt();uname = ui->unameEdit->text();// 使客户端连接服务器socket->connectToHost(ip, port);// 判断客户端是否成功连接服务器,成功则客户端会自动发射connected信号// 将该信号连接到自定义的槽函数中// 因为只需要连接一次,所以连接函数应该写在构造函数中connect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);
}void Qt_Client::on_msgBtn_clicked()
{// 获取UI界面上输入的内容QString msg = uname + " : " + ui->msgEdit->toPlainText();// 将消息发送给服务器socket->write(msg.toLocal8Bit());}void Qt_Client::on_disconnectBtn_clicked()
{QString msg = uname + " 走咯,我还会再回来的";// 将消息发送给服务器socket->write(msg.toLocal8Bit());// 断开链接socket->close();disconnect(socket, &QTcpSocket::readyRead, this, &Qt_Client::readyRead_slot);disconnect(socket, &QTcpSocket::connected, this, &Qt_Client::connected_slot);// 更改UI界面的组件状态ui->msgEdit->setEnabled(false);ui->msgBtn->setEnabled(false);ui->disconnectBtn->setEnabled(false);ui->unameEdit->setEnabled(true);ui->ipEdit->setEnabled(true);ui->portEdit->setEnabled(true);ui->connectBtn->setEnabled(true);
}
119.3.4 main.cpp
#include "qt_client.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Qt_Client w;w.show();return a.exec();
}