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