Qt实践:TCP服务器和客户端的设计

avatar
作者
筋斗云
阅读量:0

Qt:5.12.2

Qt Creator:4.11.1

通信方式:TCP\IP

语言:C++

一、TCP Client的设计

客户端的界面UI包含服务器IP、服务器端口、以及一些简单的功能,数据可选ASCLL及HEX格式。当未与服务器建立连接时,该客户端的部分控件设置为禁用。

自动获取本地IP并显示在QLineEdit中(该部分参考了一篇博客,链接在文末)

'getIp()'函数可以根据不同的操作系统获取本地 IP 地址。在 Windows 系统下,通过使用 'QHostInfo::fromName(QHostInfo::localHostName())'获取本地主机信息,并存储在 'vAddressList' 列表中;在非 Windows 系统下,它直接调用 'QNetworkInterface::allAddresses()`'获取所有网络接口信息。然后,遍历地址列表 ('vAddressList'),找到一个有效的 IPv4 地址并存储在 'myipv4Address'变量中。'initialize()`'函数调用 'getIp()`'获取本地 IP 地址,并将其显示在客户端 UI 的 IP 地址输入框 ('ui->ipLineEdit') 中。

QString Client::getIp() {     QString myipv4Address;  #ifdef _WIN32     // Windows 系统下获取本地主机信息     QHostInfo vHostInfo = QHostInfo::fromName(QHostInfo::localHostName());     QList<QHostAddress> vAddressList = vHostInfo.addresses(); #else     // 非 Windows 系统下获取所有网络接口信息     QList<QHostAddress> vAddressList = QNetworkInterface::allAddresses(); #endif      // 遍历地址列表以查找有效的 IPv4 地址     for (int i = 0; i < vAddressList.size(); i++) {         if (!vAddressList.at(i).isNull() &&             vAddressList.at(i) != QHostAddress::LocalHost &&             vAddressList.at(i).protocol() == QAbstractSocket::IPv4Protocol) {             myipv4Address = vAddressList.at(i).toString();             break;         }     }      return myipv4Address; }  void Client::initialize() {     QString localIP = getIp();     ui->ipLineEdit->setText(localIP); }

连接服务器

通过两个函数来更新客户端界面和连接状态。'connectToServer()'函数首先检查当前是否已连接到服务器。如果尚未连接,则从 IP 地址输入框和端口号输入框获取目标服务器的 IP 地址和端口号,并尝试连接到目标服务器。连接成功后,显示成功连接的消息框,更新连接状态和发送/接收选项,并启用发送文本框。如果连接失败,则显示连接失败的消息框。'disconnectFromServer()'函数首先检查当前是否已连接到服务器。如果已连接,则断开与服务器的连接,并更新连接状态和发送/接收选项。在断开连接后,禁用发送文本框。

void Client::connectToServer() {     // 检查当前是否已连接到服务器     if (!isConnected) {         // 从 IP 地址输入框和端口号输入框获取目标服务器的IP地址和端口号         QString ipAddress = ui->ipLineEdit->text();         quint16 port = ui->portLineEdit->text().toUShort();          // 尝试连接到目标服务器         tcpSocket->connectToHost(QHostAddress(ipAddress), port);          // 等待连接,最多等待3秒(3000毫秒)         if (tcpSocket->waitForConnected(3000)) {             // 连接成功的消息框提示             QMessageBox::information(this, "Connected", "Successfully connected to server!");             // 更新连接状态和发送/接收选项             isConnected = true;             updateSendOptions(true);             updateRecvOptions(true);             // 在成功连接后启用发送文本框             ui->sendTextEdit->setEnabled(true);         } else {             // 连接失败的消息框提示             QMessageBox::critical(this, "Error", "Connection failed!");         }     } }  void Client::disconnectFromServer() {     // 检查当前是否已连接到服务器     if (isConnected) {         // 断开与服务器的连接         tcpSocket->disconnectFromHost();         // 更新连接状态和发送/接收选项         isConnected = false;         updateSendOptions(false);         updateRecvOptions(false);         // 在断开连接后禁用发送文本框         ui->sendTextEdit->setEnabled(false);     } } 

数据收发逻辑

'sendNewLine()'函数首先检查是否已连接到服务器。如果已连接,则根据当前选择的数据格式(ASCII 或 HEX)从发送区文本框获取数据,并将其发送到服务器。'displayReceivedData()'函数读取从服务器接收到的数据,并根据当前选择的接收数据格式(ASCII 或 HEX)将其显示在接收区文本框中。如果停止显示复选框未选中,则继续显示数据。

void Client::sendNewLine() {     // 检查是否已经连接到服务器     if (isConnected) {         // 检查当前选择的发送数据格式是否为 HEX 格式         if (ui->sendFormatComboBox->currentIndex() == 1) { // HEX format             // 检查发送区文本框是否有数据             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 // 获取发送区文本内容,并清理其中的空格等空白字符                 QString hexData = ui->sendTextEdit->toPlainText().simplified(); // Remove spaces                 // 检查处理后的十六进制字符串长度是否为偶数                 if (hexData.size() % 2 != 0) {                     qDebug() << "Error: Incomplete hex data!";                     return;                 }                  // 创建字节数组用于存储转换后的字节数据                 QByteArray byteArray;                 byteArray.resize(hexData.length() / 2);                  bool ok;                 // 将十六进制字符串转换为字节数据                 for (int i = 0; i < hexData.length(); i += 2) {                     QString byte = hexData.mid(i, 2);                     byteArray[i / 2] = static_cast<char>(byte.toInt(&ok, 16));                     // 检查转换过程中是否有非法字符                     if (!ok) {                         qDebug() << "Error: Invalid hex data!";                         return;                     }                 }                  // 发送转换后的字节数据到服务器                 tcpSocket->write(byteArray);             }         } else { // ASCII format             // 检查发送区文本框是否有数据             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 // 将文本内容转换为 UTF-8 格式的字节数据,并发送到服务器                 tcpSocket->write(ui->sendTextEdit->toPlainText().toUtf8());             }         }     } else {         // 如果未连接到服务器,则无法发送数据         qDebug() << "Not connected to a server. Cannot send data.";     } }  void Client::displayReceivedData() {     // 读取接收到的数据     QByteArray receivedData = tcpSocket->readAll();      QString displayData;     if (ui->receiveFormatComboBox->currentIndex() == 1) { // HEX format         // 如果接收格式为 HEX,则将数据以十六进制格式显示         QString hexData;         for (int i = 0; i < receivedData.size(); ++i) {             hexData += QString("%1 ").arg(static_cast<unsigned char>(receivedData.at(i)), 2, 16, QChar('0')).toUpper();         }         displayData = hexData;     } else { // ASCII format         // 如果接收格式为 ASCII,则直接显示接收到的数据         displayData = QString::fromUtf8(receivedData);     }      if (!ui->stopDisplayCheckBox->isChecked()) {         // 如果停止显示复选框未选中,则将数据追加到接收区域文本框中         ui->recvTextEdit->append(displayData);     } }

二、TCP Server的设计

服务器部分的设计与客户端类似,以下会提供一些主要部分的代码及注释。

获取IP不再赘述,下面是服务器监听的逻辑

'on_connectButton_clicked()'函数用于启动服务器监听连接请求。首先检查服务器是否已处于监听状态。如果服务器尚未处于监听状态,则从端口号输入框获取端口号,并使用 'tcpServer->listen(QHostAddress::Any, port)`'启动服务器监听。如果启动失败,则显示错误消息。成功启动后,状态标签 ('ui->statusLabel') 显示为 "Server running: Waiting for connection..."。

'on_disconnectButton_clicked()'函数用于停止服务器监听。首先检查服务器是否正在监听。如果服务器正在监听连接,则使用 'tcpServer->close()'停止服务器监听。停止后,状态标签 ('ui->statusLabel') 显示为 "Server stopped."。

void Server::on_connectButton_clicked() {     // 启动服务器监听连接请求     if (!tcpServer->isListening()) {         quint16 port = ui->portLineEdit->text().toUShort();         if (!tcpServer->listen(QHostAddress::Any, port)) {             QMessageBox::critical(this, "Error", "Unable to start the server!");             return;         }         ui->statusLabel->setText("Server running: Waiting for connection...");     } }  void Server::on_disconnectButton_clicked() {     // 停止服务器监听     if (tcpServer->isListening()) {         tcpServer->close();         ui->statusLabel->setText("Server stopped.");     } }

数据收发逻辑

 'on_sendButton_clicked()'用于处理服务器端发送数据。首先检查是否与客户端连接,然后根据发送数据的格式从发送区获取数据并发送到客户端。如果未连接到客户端,则输出错误消息。

'displayReceivedData()'用于显示接收到的数据。它读取从客户端接收到的数据,并根据接收数据的格式(HEX 或 ASCII)将数据显示在接收区域。如果停止显示复选框未选中,则将数据继续到接收区。

void Server::on_sendButton_clicked() {     // 检查是否与客户端连接     if (tcpSocket->state() == QAbstractSocket::ConnectedState) {         // 检查发送数据的格式         if (ui->sendFormatComboBox->currentIndex() == 1) { // HEX format             // 从发送区获取HEX格式的数据并发送             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 QString hexData = ui->sendTextEdit->toPlainText().simplified(); // 移除空格                 if (hexData.size() % 2 != 0) {                     qDebug() << "Error: Incomplete hex data!";                     return;                 }                  QByteArray byteArray;                 byteArray.resize(hexData.length() / 2);                  bool ok;                 for (int i = 0; i < hexData.length(); i += 2) {                     QString byte = hexData.mid(i, 2);                     byteArray[i / 2] = static_cast<char>(byte.toInt(&ok, 16));                     if (!ok) {                         qDebug() << "Error: Invalid hex data!";                         return;                     }                 }                  // 发送HEX格式的数据                 tcpSocket->write(byteArray);             }         } else { // ASCII format             // 从发送区获取ASCII格式的数据并发送             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 tcpSocket->write(ui->sendTextEdit->toPlainText().toUtf8());             }         }     } else {         qDebug() << "No client connected. Cannot send data.";     } }  void Server::displayReceivedData() {     QByteArray receivedData = tcpSocket->readAll();      QString displayData;     if (ui->receiveFormatComboBox->currentIndex() == 1) { // HEX format         QString hexData;         for (int i = 0; i < receivedData.size(); ++i) {             hexData += QString("%1 ").arg(static_cast<unsigned char>(receivedData.at(i)), 2, 16, QChar('0')).toUpper();         }         displayData = hexData;     } else { // ASCII format         displayData = QString::fromUtf8(receivedData);     }      if (!ui->stopDisplayCheckBox->isChecked()) {         appendToReceive(displayData);     } }

三、功能测试

以下分为三部分进行验证,主要测试均为客户端与服务器之间的ASCLL与HEX数据收发。

1、自制客户端与野火网络调试助手之间通信

输入服务器端口8000,启动服务器监听,点击客户端连接按钮后提示“Successfully connected to server!”并且禁用的控件显示为可用状态,表明自制客户端与服务器实现了连接。当客户端以HEX格式发送数据32 32 32时,服务器接收到ASCLL数据2 2 2。

2、自制服务器与野火网络调试助手之间通信

输入服务器端口8000,启动服务器监听,点击野火客户端连接按钮后服务器的Label提示状态转为“Sever running : Waiting for connection...”表明客户端与自制服务器实现了连接。当客户端以HEX格式发送数据32 32 32时,服务器接收到ASCLL数据2 2 2。

3、自制服务器与自制客户端之间通信

输入服务器端口8000,启动服务器监听,点击客户端连接按钮后客户端提示“Successfully connected to server!”并且禁用的控件显示为可用状态,服务器的Label控件提示状态转为“Sever running : Waiting for connection...”表明自制客户端与自制服务器实现了连接。当客户端以HEX格式发送数据32 32 32时,服务器接收到ASCLL数据2 2 2。

四、程序源码

client.h

#ifndef CLIENT_H #define CLIENT_H  #include <QMainWindow> #include <QTcpSocket> #include <QHostAddress>  QT_BEGIN_NAMESPACE namespace Ui { class Client; } QT_END_NAMESPACE  class Client : public QMainWindow {     Q_OBJECT  public:     Client(QWidget *parent = nullptr);     ~Client();  private slots:     // 槽函数声明     void connectToServer();     void disconnectFromServer();     void sendNewLine();     void clearReceivedData();     void toggleDisplay();     void displayReceivedData();     void formatChanged(int index);     void updateSendOptions(bool connected);     void updateRecvOptions(bool connected);     void clearSend();     QString getIp(); // 获取本机IP的函数  private:     Ui::Client *ui;     QTcpSocket *tcpSocket;     bool isConnected;      void initialize(); // 初始化函数,用于显示本地IP地址; };  #endif // CLIENT_H 

client.cpp

#include "client.h" #include "ui_client.h" #include <QMessageBox> #include <QDebug> #include <QNetworkInterface> #include <QHostInfo>  Client::Client(QWidget *parent)     : QMainWindow(parent),       ui(new Ui::Client),       tcpSocket(new QTcpSocket(this)),       isConnected(false) {     ui->setupUi(this);      // 设置默认状态     updateSendOptions(false); // 更新发送选项状态为禁用     updateRecvOptions(false); // 更新接收选项状态为禁用      // 设置默认格式选项     ui->sendFormatComboBox->setCurrentIndex(0); // 设置发送格式为 ASCII     ui->receiveFormatComboBox->setCurrentIndex(0); // 设置接收格式为 ASCII      // 连接按钮信号和槽     connect(ui->connectButton, &QPushButton::clicked, this, &Client::connectToServer);     connect(ui->disconnectButton, &QPushButton::clicked, this, &Client::disconnectFromServer);     connect(ui->sendButton, &QPushButton::clicked, this, &Client::sendNewLine);     connect(ui->clearRecvButton, &QPushButton::clicked, this, &Client::clearReceivedData);     connect(ui->clearSendButton, &QPushButton::clicked, this, &Client::clearSend);     connect(ui->stopDisplayCheckBox, &QCheckBox::toggled, this, &Client::toggleDisplay);     connect(ui->receiveFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Client::formatChanged);     connect(tcpSocket, &QTcpSocket::readyRead, this, &Client::displayReceivedData);      // 初始化窗口,显示本地 IP 地址     initialize(); }  Client::~Client() {     delete ui; }  QString Client::getIp() {     QString myipv4Address;  #ifdef _WIN32     // Windows 系统下获取本地主机信息     QHostInfo vHostInfo = QHostInfo::fromName(QHostInfo::localHostName());     QList<QHostAddress> vAddressList = vHostInfo.addresses(); #else     // 非 Windows 系统下获取所有网络接口信息     QList<QHostAddress> vAddressList = QNetworkInterface::allAddresses(); #endif      // 遍历地址列表以查找有效的 IPv4 地址     for (int i = 0; i < vAddressList.size(); i++) {         if (!vAddressList.at(i).isNull() &&             vAddressList.at(i) != QHostAddress::LocalHost &&             vAddressList.at(i).protocol() == QAbstractSocket::IPv4Protocol) {             myipv4Address = vAddressList.at(i).toString();             break;         }     }      return myipv4Address; }  void Client::initialize() {     QString localIP = getIp();     ui->ipLineEdit->setText(localIP); }  void Client::connectToServer() {     if (!isConnected) {         QString ipAddress = ui->ipLineEdit->text();         quint16 port = ui->portLineEdit->text().toUShort();          tcpSocket->connectToHost(QHostAddress(ipAddress), port);          if (tcpSocket->waitForConnected(3000)) {             QMessageBox::information(this, "Connected", "Successfully connected to server!");             isConnected = true;             updateSendOptions(true);             updateRecvOptions(true);             ui->sendTextEdit->setEnabled(true); // Enable send text box upon successful connection         } else {             QMessageBox::critical(this, "Error", "Connection failed!");         }     } }  void Client::disconnectFromServer() {     if (isConnected) {         tcpSocket->disconnectFromHost();         isConnected = false;         updateSendOptions(false);         updateRecvOptions(false);         ui->sendTextEdit->setEnabled(false); // Disable send text box upon disconnection     } }  void Client::sendNewLine() {     if (isConnected) {         if (ui->sendFormatComboBox->currentIndex() == 1) { // HEX format             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 QString hexData = ui->sendTextEdit->toPlainText().simplified(); // Remove spaces                 if (hexData.size() % 2 != 0) {                     qDebug() << "Error: Incomplete hex data!";                     return;                 }                  QByteArray byteArray;                 byteArray.resize(hexData.length() / 2);                  bool ok;                 for (int i = 0; i < hexData.length(); i += 2) {                     QString byte = hexData.mid(i, 2);                     byteArray[i / 2] = static_cast<char>(byte.toInt(&ok, 16));                     if (!ok) {                         qDebug() << "Error: Invalid hex data!";                         return;                     }                 }                  tcpSocket->write(byteArray);             }         } else { // ASCII format             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 tcpSocket->write(ui->sendTextEdit->toPlainText().toUtf8());             }         }     } else {         qDebug() << "Not connected to a server. Cannot send data.";     } }  void Client::clearReceivedData() {     ui->recvTextEdit->clear(); }  void Client::toggleDisplay() {     if (ui->stopDisplayCheckBox->isChecked()) {         ui->recvTextEdit->setReadOnly(true);     } else {         ui->recvTextEdit->setReadOnly(false);     } }  void Client::displayReceivedData() {     QByteArray receivedData = tcpSocket->readAll();      QString displayData;     if (ui->receiveFormatComboBox->currentIndex() == 1) { // HEX format         QString hexData;         for (int i = 0; i < receivedData.size(); ++i) {             hexData += QString("%1 ").arg(static_cast<unsigned char>(receivedData.at(i)), 2, 16, QChar('0')).toUpper();         }         displayData = hexData;     } else { // ASCII format         displayData = QString::fromUtf8(receivedData);     }      if (!ui->stopDisplayCheckBox->isChecked()) {         ui->recvTextEdit->append(displayData);     } }  void Client::formatChanged(int index) {     Q_UNUSED(index);     displayReceivedData(); }  void Client::updateSendOptions(bool connected) {     ui->sendButton->setEnabled(connected);     ui->sendTextEdit->setEnabled(connected);     ui->clearSendButton->setEnabled(connected);     ui->sendFormatComboBox->setEnabled(connected); }  void Client::updateRecvOptions(bool connected) {     ui->clearRecvButton->setEnabled(connected);     ui->stopDisplayCheckBox->setEnabled(connected);     ui->receiveFormatComboBox->setEnabled(connected); }  void Client::clearSend() {     ui->sendTextEdit->clear(); }

server.h

#ifndef SERVER_H #define SERVER_H  #include <QMainWindow> #include <QTcpServer> #include <QTcpSocket>  QT_BEGIN_NAMESPACE namespace Ui { class Server; } QT_END_NAMESPACE  class Server : public QMainWindow {     Q_OBJECT  public:     explicit Server(QWidget *parent = nullptr);     ~Server();  private slots:     void on_connectButton_clicked();     void on_disconnectButton_clicked();     void on_sendButton_clicked();     void on_clearSendButton_clicked();     void on_clearReceiveButton_clicked();     void on_stopDisplayCheckBox_toggled(bool checked);     void on_receiveFormatComboBox_currentIndexChanged(int index);  private:     Ui::Server *ui;     QTcpServer *tcpServer;     QTcpSocket *tcpSocket;     QString localIP;      void displayLocalIP();     void appendToReceive(const QString &message);     void displayReceivedData(); };  #endif // SERVER_H 

server.cpp

#include "server.h" #include "ui_server.h" #include <QMessageBox> #include <QDebug> #include <QNetworkInterface>  Server::Server(QWidget *parent)     : QMainWindow(parent),       ui(new Ui::Server),       tcpServer(new QTcpServer(this)),       tcpSocket(new QTcpSocket(this)) {     ui->setupUi(this);      // 显示本地IP地址     displayLocalIP();      // 监听连接请求     connect(tcpServer, &QTcpServer::newConnection, this, [this]() {         // 接收连接并分配socket         tcpSocket = tcpServer->nextPendingConnection();         // 当有数据到达时触发显示数据函数         connect(tcpSocket, &QTcpSocket::readyRead, this, &Server::displayReceivedData);     }); }  Server::~Server() {     delete ui; }  void Server::displayLocalIP() {     // 获取本地IP地址并在UI中显示     QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();     for (const QHostAddress &address : ipAddressesList) {         if (address != QHostAddress::LocalHost && address.toIPv4Address()) {             localIP = address.toString();             ui->localIPLineEdit->setText(localIP);             break;         }     } }  void Server::appendToReceive(const QString &message) {     // 将接收到的信息追加到接收区     if (!ui->stopDisplayCheckBox->isChecked()) {         ui->receiveTextEdit->append(message);     } }  void Server::on_connectButton_clicked() {     // 启动服务器监听连接请求     if (!tcpServer->isListening()) {         quint16 port = ui->portLineEdit->text().toUShort();         if (!tcpServer->listen(QHostAddress::Any, port)) {             QMessageBox::critical(this, "Error", "Unable to start the server!");             return;         }         ui->statusLabel->setText("Server running: Waiting for connection...");     } }  void Server::on_disconnectButton_clicked() {     // 停止服务器监听     if (tcpServer->isListening()) {         tcpServer->close();         ui->statusLabel->setText("Server stopped.");     } }  void Server::on_sendButton_clicked() {     // 检查是否与客户端连接     if (tcpSocket->state() == QAbstractSocket::ConnectedState) {         // 检查发送数据的格式         if (ui->sendFormatComboBox->currentIndex() == 1) { // HEX format             // 从发送区获取HEX格式的数据并发送             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 QString hexData = ui->sendTextEdit->toPlainText().simplified(); // 移除空格                 if (hexData.size() % 2 != 0) {                     qDebug() << "Error: Incomplete hex data!";                     return;                 }                  QByteArray byteArray;                 byteArray.resize(hexData.length() / 2);                  bool ok;                 for (int i = 0; i < hexData.length(); i += 2) {                     QString byte = hexData.mid(i, 2);                     byteArray[i / 2] = static_cast<char>(byte.toInt(&ok, 16));                     if (!ok) {                         qDebug() << "Error: Invalid hex data!";                         return;                     }                 }                  // 发送HEX格式的数据                 tcpSocket->write(byteArray);             }         } else { // ASCII format             // 从发送区获取ASCII格式的数据并发送             if (!ui->sendTextEdit->toPlainText().isEmpty()) {                 tcpSocket->write(ui->sendTextEdit->toPlainText().toUtf8());             }         }     } else {         qDebug() << "No client connected. Cannot send data.";     } }  void Server::on_clearSendButton_clicked() {     // 清空发送区     ui->sendTextEdit->clear(); }  void Server::on_clearReceiveButton_clicked() {     // 清空接收区     ui->receiveTextEdit->clear(); }  void Server::on_stopDisplayCheckBox_toggled(bool checked) {     // 控制是否显示接收到的数据     Q_UNUSED(checked);     displayReceivedData(); }  void Server::displayReceivedData() {     QByteArray receivedData = tcpSocket->readAll();      QString displayData;     if (ui->receiveFormatComboBox->currentIndex() == 1) { // HEX format         QString hexData;         for (int i = 0; i < receivedData.size(); ++i) {             hexData += QString("%1 ").arg(static_cast<unsigned char>(receivedData.at(i)), 2, 16, QChar('0')).toUpper();         }         displayData = hexData;     } else { // ASCII format         displayData = QString::fromUtf8(receivedData);     }      if (!ui->stopDisplayCheckBox->isChecked()) {         appendToReceive(displayData);     } }  void Server::on_receiveFormatComboBox_currentIndexChanged(int index) {     Q_UNUSED(index);     // 处理接收数据的格式变化     displayReceivedData(); } 

五、工程文件

本人也是刚开始学习,里面可能存在一些不对的地方,欢迎留言讨论。

链接1为工程文件,链接2为获取本地IP的博客,链接3为封包的博客。

https://download.csdn.net/download/please_take/88691846
https://blog.csdn.net/weixin_34245082/article/details/92270355?spm=1001.2014.3001.5506
https://blog.csdn.net/F_Xiao_bai/article/details/130419561?spm=1001.2014.3001.5506

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!