C++ Qt开发:QTcpSocket网络通信组件

Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用QTcpSocket组件实现基于TCP的网络通信功能。

QTcpSocketQTcpServer是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向连接的协议,它提供可靠的、双向的、面向字节流的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的连接。

以下是QTcpSocket类的一些常用函数:

函数描述
QTcpSocket()构造函数,创建一个新的QTcpSocket对象。
~QTcpSocket()析构函数,释放QTcpSocket对象及其资源。
void connectToHost(const QString &hostName, quint16 port)尝试与指定主机名和端口建立连接。
void disconnectFromHost()断开与主机的连接。
QAbstractSocket::SocketState state() const返回套接字的当前状态。
QHostAddress peerAddress() const返回与套接字连接的远程主机的地址。
quint16 peerPort() const返回与套接字连接的远程主机的端口。
QAbstractSocket::SocketError error() const返回套接字的当前错误代码。
qint64 write(const char *data, qint64 maxSize)将数据写入套接字,返回实际写入的字节数。
qint64 read(char *data, qint64 maxSize)从套接字读取数据,返回实际读取的字节数。
void readyRead()当套接字有可供读取的新数据时发出信号。
void bytesWritten(qint64 bytes)当套接字已经写入指定字节数的数据时发出信号。
void error(QAbstractSocket::SocketError socketError)当套接字发生错误时发出信号。

以下是QTcpServer类的一些常用函数及其简要解释:

函数描述
QTcpServer()构造函数,创建一个新的QTcpServer对象。
~QTcpServer()析构函数,释放QTcpServer对象及其资源。
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)开始监听指定的地址和端口。
void close()停止监听并关闭服务器。
bool isListening() const返回服务器是否正在监听连接。
QList<QTcpSocket*> pendingConnections()返回等待处理的挂起连接的列表。
virtual void incomingConnection(qintptr socketDescriptor)当有新连接时调用,可以在子类中实现以处理新连接。
void maxPendingConnections() const返回允许的最大挂起连接数。
void setMaxPendingConnections(int numConnections)设置允许的最大挂起连接数。
QNetworkProxy proxy() const返回服务器的代理设置。
void setProxy(const QNetworkProxy &networkProxy)设置服务器的代理设置。
QAbstractSocket::SocketError serverError() const返回服务器的当前错误代码。
QString errorString() const返回服务器的错误消息字符串。
void pauseAccepting()暂停接受新连接,但保持现有连接。
void resumeAccepting()恢复接受新连接。
void close()关闭服务器。

如上这些只是常用函数的简要描述,详细的函数说明和用法可以参考Qt官方文档或相关文档。

1.1 通信的流程

1.1.1 服务端流程

在使用TCP通信时同样需要导入Qt+=network模块,并在头文件中引入QTcpServerQTcpSocket两个模块,当有了模块的支持,接着就是侦听套接字,此处可通过调用server.listen来实现侦听,此函数原型如下;

bool QTcpServer::listen(
    const QHostAddress &address = QHostAddress::Any, 
    quint16 port = 0
);

这个函数用于开始在指定的地址和端口上监听连接。它的参数包括:

  • address:一个QHostAddress对象,指定要监听的主机地址。默认为QHostAddress::Any,表示监听所有可用的网络接口。
  • port:一个quint16类型的端口号,指定要监听的端口。如果设置为0,系统将选择一个可用的未使用端口。

函数返回一个bool值,表示是否成功开始监听。如果成功返回true,否则返回false,并且可以通过调用errorString()获取错误消息。

紧随套接字侦听其后,通过使用一个waitForNewConnection等待新的连接到达。它的原型如下:

bool QTcpServer::waitForNewConnection(
    int msec = 0, 
    bool *timedOut = nullptr
);

该函数在服务器接受新连接之前会一直阻塞。参数包括:

  • msec:等待连接的超时时间(以毫秒为单位)。如果设置为0(默认值),则表示无限期等待,直到有新连接到达。
  • timedOut:一个可选的布尔指针,用于指示等待是否超时。如果传递了此参数,并且等待时间达到了指定的超时时间,*timedOut将被设置为true,否则为false。如果不关心超时,可以将此参数设置为nullptr

函数返回一个布尔值,表示是否成功等待新连接。如果在超时时间内有新连接到达,返回true,否则返回false。如果等待超时,可以通过检查timedOut参数来确定。如果函数返回false,可以通过调用errorString()获取错误消息。

套接字的接收会使用nextPendingConnection()函数来实现,nextPendingConnectionQTcpServer 类的成员函数,用于获取下一个已接受的连接的套接字(QTcpSocket)。它的原型如下:

QTcpSocket *QTcpServer::nextPendingConnection();

函数返回一个指向新连接套接字的指针。如果没有已接受的连接,则返回 nullptr

使用这个函数,你可以在服务器接受连接之后获取相应的套接字,以便进行数据传输和通信。一般来说,在收到 newConnection 信号后,你可以调用这个函数来获取新连接的套接字。

当有了套接字以后,就可以通过QTcpServer指针判断对应的套接字状态,一般套接字的状态被定义在QAbstractSocket类内。以下是QAbstractSocket类中定义的一些状态及其对应的标志:

状态标志描述
UnconnectedState未连接状态,套接字没有连接到远程主机。
HostLookupState正在查找主机地址状态,套接字正在解析主机名。
ConnectingState连接中状态,套接字正在尝试与远程主机建立连接。
ConnectedState已连接状态,套接字已经成功连接到远程主机。
BoundState已绑定状态,套接字已经与地址和端口绑定。
ClosingState关闭中状态,套接字正在关闭连接。
ListeningState监听中状态,用于QTcpServer,表示服务器正在监听连接。

这些状态反映了套接字在不同阶段的连接和通信状态。在实际使用中,可以通过调用state()函数获取当前套接字的状态,并根据需要处理相应的状态。例如,可以使用信号和槽机制来捕获状态变化,以便在连接建立或断开时执行相应的操作。

当套接字被连接后则可以通过socket->write()方法向上线客户端发送一个字符串,此处我们以发送lyshark为例,发送时需要向write()中传入两个参数。其原型如下:

qint64 QTcpSocket::write(const char *data, qint64 maxSize);

该函数接受两个参数:

  • data:指向要写入套接字的数据的指针。
  • maxSize:要写入的数据的最大字节数。

函数返回实际写入的字节数,如果发生错误,则返回 -1。在写入数据之后,可以使用 bytesWritten 信号来获取写入的字节数。此外,你也可以使用 waitForBytesWritten 函数来阻塞等待直到所有数据都被写入。

至此服务端代码可总结为如下案例;

#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <iostream>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTcpServer server;

    server.listen(QHostAddress::Any,9000);
    server.waitForNewConnection(100000);

    QTcpSocket *socket;

    socket = server.nextPendingConnection();
    if(socket->state() && QAbstractSocket::ConnectedState)
    {
        QByteArray bytes = QString("lyshark").toUtf8();
        socket->write(bytes.data(),bytes.length());
    }

    socket->close();
    server.close();
    return a.exec();
}
1.1.2 客户端流程

客户端的流程与服务端基本保持一致,唯一的区别在于将server.listen更换为socket.connectToHost连接到对应的主机,QTcpSocketconnectToHost 函数的原型如下:

void QTcpSocket::connectToHost(
const QString &hostName, 
quint16 port, 
OpenMode openMode = ReadWrite
);
  • hostName:远程主机的主机名或IP地址。
  • port:要连接的端口号。
  • openMode:套接字的打开模式,默认为 ReadWrite

函数用于初始化与指定远程主机和端口的连接。在实际使用中,你可以通过调用这个函数来发起与目标主机的连接尝试。

读取数据时可以使用readAll函数来实现,socket.readAll()QTcpSocket 类的成员函数,用于读取所有可用的数据并返回一个 QByteArray 对象。其函数函数原型如下:

QByteArray QTcpSocket::readAll();

该函数返回一个包含从套接字中读取的所有数据的 QByteArray 对象。通常,你可以通过这个函数来获取已经到达的所有数据,然后对这些数据进行进一步的处理。其客户端功能如下所示;

#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <iostream>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTcpSocket socket;
    socket.connectToHost(QHostAddress::LocalHost,9000);

    if(socket.state() && QAbstractSocket::ConnectedState)
    {
        socket.waitForReadyRead(10000);

        QByteArray ref = socket.readAll();

        QString ref_string;

        ref_string.prepend(ref);

        std::cout << ref_string.toStdString() << std::endl;
    }

    socket.close();
    return a.exec();
}

1.2 图形化应用

1.2.1 服务端流程

与命令行版本的网络通信不同,图形化部分需要使用信号与槽函数进行绑定,所有的通信流程都是基于信号的,对于服务端而言我们需要导入QTcpServerQtNetworkQTcpSocket模块,并新增四个槽函数分别对应四个信号;

信号槽函数描述
connected()onClientConnected()tcpSocket 成功连接到远程主机时触发,执行 onClientConnected() 函数。
disconnected()onClientDisconnected()tcpSocket 断开连接时触发,执行 onClientDisconnected() 函数。
stateChanged(QAbstractSocket::SocketState)onSocketStateChange(QAbstractSocket::SocketState)tcpSocket 的状态发生变化时触发,执行 onSocketStateChange() 函数,传递新的状态。
readyRead()onSocketReadyRead()tcpSocket 有可读取的新数据时触发,执行 onSocketReadyRead() 函数。

在程序入口处我们通过new QTcpServer(this)新建TCP套接字类,并通过connect()连接到初始化槽函数上,当程序运行后会首先触发newConnection信号,执行onNewConnection槽函数。

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 新建TCP套接字类
    tcpServer=new QTcpServer(this);

    // 连接信号初始化其他信号
    connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
}

而在槽函数onNewConnection中,通过nextPendingConnection新建一个套接字,并绑定其他四个槽函数,这里的槽函数功能各不相同,将其对应的信号绑定到对应槽函数上即可;

// 初始化信号槽函数
void MainWindow::onNewConnection()
{
    // 创建新套接字
    tcpSocket = tcpServer->nextPendingConnection();

    // 连接触发信号
    connect(tcpSocket, SIGNAL(connected()),this, SLOT(onClientConnected()));
    onClientConnected();

    // 关闭触发信号
    connect(tcpSocket, SIGNAL(disconnected()),this, SLOT(onClientDisconnected()));

    // 状态改变触发信号
    connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
    onSocketStateChange(tcpSocket->state());

    // 读入数据触发信号
    connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}

当读者点击侦听时则直接调用tcpServer->listen实现对本地IP的8888端口的侦听功能,停止侦听则是调用tcpServer->close函数实现,如下所示;

// 开始侦听
void MainWindow::on_pushButton_2_clicked()
{
    // 此处指定绑定本机的8888端口
    tcpServer->listen(QHostAddress::LocalHost,8888);
    ui->plainTextEdit->appendPlainText("[+] 开始监听");
    ui->plainTextEdit->appendPlainText(" 服务器地址:" + tcpServer->serverAddress().toString() +
                                       " 服务器端口:"+QString::number(tcpServer->serverPort())
                                       );
}

// 停止侦听
void MainWindow::on_pushButton_3_clicked()
{
    if (tcpServer->isListening())
    {
        tcpServer->close();
    }
}

对于读取数据可以通过canReadLine()函数判断行,并通过tcpClient->readLine()逐行读入数据,相对应的发送数据可通过调用tcpSocket->write函数实现,在发送之前需要将其转换为QByteArray类型的字符串格式,如下所示;

// 读取数据
void MainWindow::onSocketReadyRead()
{
    while(tcpSocket->canReadLine())
        ui->plainTextEdit->appendPlainText("[接收] | " + tcpSocket->readLine());
}

// 发送数据
void MainWindow::on_pushButton_clicked()
{
    QString  msg=ui->lineEdit->text();
    ui->plainTextEdit->appendPlainText("[发送] | " + msg);

    QByteArray str=msg.toUtf8();
    str.append('\n');
    tcpSocket->write(str);
}

运行服务端程序,并点击侦听按钮此时将会在本地的8888端口上启用侦听,如下图所示;

1.2.2 客户端流程

对于客户端而言同样需要绑定四个信号并对应到特定的槽函数上,其初始化部分与服务端保持一致,唯一不同的是客户端使用connectToHost函数链接到服务端上,断开连接时使用的是disconnectFromHost函数,如下所示;

// 连接服务器时触发
void MainWindow::on_pushButton_2_clicked()
{
    // 连接到8888端口
    tcpClient->connectToHost(QHostAddress::LocalHost,8888);
}

// 断开时触发
void MainWindow::on_pushButton_3_clicked()
{
    if (tcpClient->state()==QAbstractSocket::ConnectedState)
        tcpClient->disconnectFromHost();
}

此处的读取数据与服务端保持一致,发送数据时则是通过tcpClient->write(str)函数直接传递给客户端,代码如下所示;

// 读取数据时触发
void MainWindow::onSocketReadyRead()
{
    while(tcpClient->canReadLine())
    {
        ui->plainTextEdit->appendPlainText("[接收] | " + tcpClient->readLine());
    }
}

// 发送消息时触发
void MainWindow::on_pushButton_clicked()
{
    QString msg=ui->lineEdit->text();
    ui->plainTextEdit->appendPlainText("[发送] | " + msg);
    QByteArray str=msg.toUtf8();
    str.append('\n');
    tcpClient->write(str);
}

运行后,服务端启用侦听等待客户端连接,客户端连接后,双方则可以实现数据的收发功能,由于采用了信号机制,两者的收发并不会阻断可同时进行,如下图所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/465618.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter-自定义图片3D画廊

效果 需求 3D画廊效果 设计内容 StackGestureDetectorTransformPositioned数学三角函数 代码实现 具体代码大概300行 import dart:math;import package:flutter/material.dart; import package:flutter_xy/widgets/xy_app_bar.dart;import ../../r.dart;class ImageSwitc…

[errcode] => 47003 [errmsg] => argument invalid! data.thing2.value invalid

[errcode] > 47003 [errmsg] > argument invalid! data.thing2.value invalid rid: 65f79ad9-09ea6af5-285a03af 检查了好大一圈&#xff0c;经过测试&#xff0c;原因是公众号模板消息接口中的字段不能超过20个汉字&#xff0c;包括标点符号。 虽然接口文档中参数说明…

基于深度学习的口罩人脸识别研究进展

MTCNN模型训练输入的所有图像都是正样本&#xff08;戴口罩的照片&#xff09;&#xff0c;没有负样本作为模型输入。在后续的识别任务模块中&#xff0c;导入MTCNN模型检测结果&#xff0c;对特征点进行编码比较进行识别。 基于MTCNN的口罩人脸识别框架可分为四个阶段&#xf…

数据结构试卷第九套

1.时间复杂度 2.树&#xff0c;森林&#xff0c;二叉树的转换 2.1树转二叉树 给所有的兄弟节点之间加一条连线&#xff1b;去线&#xff0c;只保留当前根节点与第一个叶子节点的连线&#xff0c;删除它与其他节点之间的连线&#xff1b;然后根据左孩子右兄弟进行调整&#xf…

C#装箱和拆箱

一&#xff0c;装箱 装箱是指将值类型转化为引用类型。 代码如下&#xff1a; 装箱的内部过程 当值类型需要被装箱为引用类型时&#xff0c;CLR&#xff08;Common Language Runtime&#xff09;会为值类型分配内存&#xff0c;在堆上创建一个新的对象。值类型的数据会被复…

Adobe Illustrator 2024 v28.3 (macOS, Windows) - 矢量绘图

Adobe Illustrator 2024 v28.3 (macOS, Windows) - 矢量绘图 Acrobat、After Effects、Animate、Audition、Bridge、Character Animator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、Lightroom Classic、Media Encoder、Photoshop、Premiere Pro、Adobe XD 请访…

【机器学习】详细解析Sklearn中的StandardScaler---原理、应用、源码与注意事项

【机器学习】详细解析Sklearn中的StandardScaler—原理、应用、源码与注意事项 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x…

QT UI窗口常见操作

MainWidget::MainWidget(QWidget *parent): QWidget(parent), ui(new Ui::MainWidget) {ui->setupUi(this);// 设置主窗口背景颜色QPalette plt;plt.setColor(QPalette::Window,QColor(180,220,130));this->setPalette(plt);// 禁止窗口最大化按钮setWindowFlags(windowF…

基于多源遥感图像多级协同融合的舰船识别算法

源自&#xff1a;电子工程与电子技术 作者&#xff1a;张亚丽 冯伟 全英汇 邢孟道 “人工智能技术与咨询” 发布 摘 要 针对极化合成孔径雷达(polarimetric synthetic aperture radar, PolSAR)图像存在斑点噪声严重、可视性差、直接影响目标识别精度的问题, 提出一种基…

SpringBoot实现邮件发送

一.准备 引入starter <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId> </dependency>二.邮件发送需要的配置 因为各大邮件都有其对应安全系统&#xff0c;不是项目中想用就…

【晴问算法】入门篇—贪心算法—区间不相交问题

题目描述 给定n个开区间&#xff0c;从中选择尽可能多的开区间&#xff0c;使得这些开区间两两没有交集。 输入描述 输出描述 输出一个整数&#xff0c;表示最多选择的开区间个数。 样例1输入 4 1 3 2 4 3 5 6 7 输出 3 解释 最多选择(1,3)、(3,5)、(6,7)三个区间&#xff0c;它…

【IEDM2023】背势垒电荷运动诱导GaN HEMT随时间的非稳态击穿

分享一篇2023年IEDM上GaN HEMT&#xff08;高电子迁移率晶体管&#xff09;的研究论文&#xff0c;标题为“Charge Movement in Back Barrier Induced Time-Dependent On-State Breakdown of GaN HEMT”。论文讨论了在GaN HEMT中&#xff0c;由于背栅&#xff08;Back Barrier&…

【数据库】数据库基本知识

1.数据库的四个基本概念 1.1 数据&#xff1a;描述事务的符号记录 1.2 数据库&#xff1a;概括的说&#xff0c;数据库数据具有永久存储、有组织的、可共享的大量数据的集合&#xff0c;数据库中的数据按一定的数据模型组织、描述和储存&#xff0c;具有较小的冗余度、较高的…

ubuntu16.04上pycharm卡住关不了

在使用pycharm的过程中&#xff0c;突然卡住&#xff0c;黑屏&#xff0c;手动界面关闭失败&#xff0c;可尝试以下方法解决。 输入以下命令&#xff0c;查看所有和pycharm有关的进程 ps -ef | grep pycharm得到以下结果 根据相应的PID&#xff0c;输入以下命令&#xff0c;强…

Java基础入门day16

day16 回顾二分查找 思路&#xff1a; 对于一个已经排好序的数组&#xff0c;在该数组中查找指定的元素&#xff0c;将要查找的元素与排好序之后的数组中的中间数值进行比对 如果一致&#xff0c;则直接返回&#xff0c;一次性可以得到要查找元素的下标 如果要查找的元素比中间…

Perl下载器:一步步教你抓取Amazon网站数据

引言&#xff1a;掌握数据&#xff0c;掌握未来 在这个信息爆炸的时代&#xff0c;数据就是新石油。但如何有效地获取和利用这些数据呢&#xff1f;爬虫技术是关键。今天&#xff0c;我们将深入探讨如何使用Perl语言编写一个下载器&#xff0c;以Amazon网站为例&#xff0c;教…

.Net使用ElasticSearch

文章目录 前言主体内容一.Kibana中ElasticSearch的基础操作1.GET&#xff08;查询&#xff09;1.POST&#xff08;新增&#xff09;1.PUT&#xff08;修改&#xff09;1.DELET&#xff08;删除&#xff09; 二.在.Net中&#xff0c;对ElasticSearch进行基础操作1.DotNet连接Ela…

【爬虫逆向】Python逆向采集猫眼电影票房数据

进行数据抓包&#xff0c;因为这个网站有数据加密 !pip install jsonpathCollecting jsonpathDownloading jsonpath-0.82.2.tar.gz (10 kB)Preparing metadata (setup.py) ... done Building wheels for collected packages: jsonpathBuilding wheel for jsonpath (setup.py) .…

Acwing-基础算法课笔记之动态规划(区间DP)

Acwing-基础算法课笔记之动态规划&#xff08;区间DP&#xff09; 一、石子合并1、定义2、闫氏DP分析法3、模拟过程4、代码示例 一、石子合并 1、定义 设有 N N N堆石子排成一排&#xff0c;其编号为 1 1 1&#xff0c; 2 2 2&#xff0c; 3 3 3&#xff0c;…&#xff0c; N…

SAP前台处理:销售业务集成<VA03/VL03N/VLPOD/VF03) 02/02

接上一章节&#xff1a; 下方&#xff1a;按照行项目显示&#xff1a;物料信息、数量信息、地点信息&#xff08;工厂、移动类型、库位&#xff09;、批次信息、科目分配信息&#xff08;科目分配对象&#xff09; 点击FI凭证&#xff0c;跳转到成本结转凭证或发出商品凭证界…