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