网络通信/QTcpSocket/实现一个可在子线程中发送和接收数据的TCP客户端

这篇具有很好参考价值的文章主要介绍了网络通信/QTcpSocket/实现一个可在子线程中发送和接收数据的TCP客户端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

近来一直接使用WinSocket做网络编程,有很长一段时间不再使用Qt框架下的相关网路通信类。有不少之前积压的问题直到现在也没怎么弄清楚,在CSDN中乱七八糟的存了好几篇草稿,亟待整理。最近要写一个简单地相机升级程序,于是重操旧业。

历史

网络通信中,尤其是在收发工作较为耗时或交互频率较高的时候,为了使得通信过程不造成UI的卡顿现象,一般要求通信工作在次线程(子线程)中完成。在Windows编程中,我们可以使用Select模式等实现这一需求。在Qt网络编程框架下,也做过些尝试。如 《网络通信/QTcpSocket/QObject:Cannot create children for a parent that is in a different thread.》 文章中提到的方案(临时记为PlanA),它将Qt套接字对象移动到次线程,并在主线程中直接调用套接字接口,此时存在 “以其他线程对象为父对象,在本线程创建子对象” 的告警。
草稿中还记录了另一个方案(临时记为PlanB)
如常见的Qt多线程编程,定义一个workker类对象,将其移动到次线程中,由其全权负责对m_socket套接字对象的操作,包括使用套接字进行连接、断开、数据发送等操作。此方案依然存在PlanA中的问题,因为此时套接字对象没有进行过moveToThread操作,其还是归属于创建它的主线程,但相关函数调用线程却为wirker所在的次线程。
通过分析以前的失败经验,似乎得出了一个结论:
套接字的相关接口只能在套接字对象所属的线程内调用(如果套接字对象没有执行过moveToThread操作,那么套接字对象的所属线程就是创建它的线程)。因此,如果想支持在次线程中执行连接/断开服务、数据收/发过程,则必须的要将套接字对象本身进行moveToThread操作,且要将其他线程对该对象的操作转换到moveToThread后的线程内。

一种可行的实现

如下方案实现了,发送和接收操作同时运行在一个次线程内。其实通常情况下的交互过程,不会在同一时间段内双向高速通信,在一个时间段内一般只有一方在高速发送数据,或者多设备发送然后由集中控制设备接收处理。因此像WinSocket编程Select模式下实现在一个线程内执行收发操作,是很常见的方案。需要注意的是,速率要求更高的场景,从本质就不适合使用Qt的网络通信封装。

//.h

#pragma once

#include <QTcpSocket>

//该对象最终运行在次线程中
class TcpClient : public QTcpSocket
{
    Q_OBJECT
public:
    TcpClient(QObject *parent = NULL);
    ~TcpClient();
public:
    //
    void ClientConnectToHost(const QString &address, quint16 port);
    //
    void ClientSendingData(const QByteArray &c_btaData);
    //
    bool IsOnline();
signals:
    //转换来自主线程的链接操作
    void SignalConnectToHost(const QString & address, quint16 port);
signals:
    //转换来自主线程的发送操作
    void SignalSendingData(const QByteArray c_btaData);
signals:
    //在次线程中缓冲并滑动解析TCP流后/按约定格式再发布
    void SignalPublishFormatRecvData(const QString c_btaData);
private:
    //标记连接情况
    bool m_bOnLine = false;
    //缓冲收到的流数据
    QByteArray m_btaReceiveFromService;
};

//.cpp

#include <QThread>
#include <QDebug>
#include <QHostAddress>
#include "tcp_client.h"

TcpClient::TcpClient(QObject *parent)
    : QTcpSocket(parent)
{
    //自动连接在信号发射时被识别为队列连接/信号在主线程发射
    connect(this, &TcpClient::SignalConnectToHost, this, [&](const QString & address, quint16 port) {
        //test record# in child thread id 20588
        qDebug("SlotConnectToHost ThreadID:%d", QThread::currentThreadId());
        //
        this->connectToHost(QHostAddress(address), port, QIODevice::ReadWrite);
    }, Qt::AutoConnection);

    //连接了TCP服务端
    QObject::connect(this, &QAbstractSocket::connected, this, [&]() {
        //test record# in child thread id 20588
        qDebug("SlotHasConnected ThreadID:%d", QThread::currentThreadId());
        //
        m_bOnLine = true;
    }, Qt::DirectConnection);

    //断开了TCP服务端
    QObject::connect(this, &QAbstractSocket::disconnected, this, [&]() {
        //test record# in child thread id 20588
        qDebug("SlotHasDisconnected ThreadID:%d", QThread::currentThreadId());
        //
        m_bOnLine = false;
    }, Qt::DirectConnection);

    //收到了TCP服务的数据
    QObject::connect(this, &QIODevice::readyRead, this, [&]() {
        //test record# in child thread id 20588
        qDebug("SlotIODeviceReadyRead ThreadID:%d", QThread::currentThreadId());
        //读取全部数据
        m_btaReceiveFromService.append(this->readAll());
        //
        int iFindPos = m_btaReceiveFromService.indexOf("\r\n");
        //检查分隔符
        while (-1 != iFindPos)
        {
            //分割数据流
            QString strPublish = m_btaReceiveFromService.left(iFindPos);
            //发布解析后的格式数据
            emit SignalPublishFormatRecvData(strPublish);
            //
            m_btaReceiveFromService.remove(0, iFindPos + strlen("\r\n"));
            //
            iFindPos = m_btaReceiveFromService.indexOf("\r\n");
        }
    }, Qt::DirectConnection);

    //执行数据发送过程
    QObject::connect(this, &TcpClient::SignalSendingData, this, [&](const QByteArray c_btaData) {
        //test record# in child thread id 20588
        qDebug("SlotSendingData ThreadID:%d", QThread::currentThreadId());
        //
        this->write(c_btaData);
    }, Qt::AutoConnection);
}

//
TcpClient::~TcpClient()
{
}

//跨线程转换
void TcpClient::ClientConnectToHost(const QString & address, quint16 port)
{
    emit SignalConnectToHost(address, port);
}

//跨线程转换
void TcpClient::ClientSendingData(const QByteArray & c_btaData)
{
    emit SignalSendingData(c_btaData);
}

//是否在线
bool TcpClient::IsOnline()
{
    return m_bOnLine;
}

//main /using of my tcp client

UpdateCamera::UpdateCamera(QWidget *parent) : QMainWindow(parent)
{
    //创建TCP客户端
    m_pmyTcpSocket = new TcpClient();
    //
    m_pThreadSending = new QThread();
    //
    m_pmyTcpSocket->moveToThread(m_pThreadSending);
    //
    m_pThreadSending->start();

    //连接到相机的TCP服务
    connect(ui.pushButton_connect, &QPushButton::clicked, [&]() {
    	...
        m_pmyTcpSocket->ClientConnectToHost(strIPUsing, SER_PORT);
    });
    
    //文件发送
    connect(ui.pushButton_file_sending, &QPushButton::clicked, [&]() {
  		...
        //执行客户端文件发送过程
        m_pmyTcpSocket->ClientSendingData(DataOfBin);
    });

    //接收服务端发送的数据 /从子线程到主线程的队列连接
    connect(m_pTcpClient, &TcpClient::SignalPublishFormatRecvData, this, [&](const QString c_btaData) {
        ui.textEdit->append(c_btaData);
        ui.textEdit->moveCursor(QTextCursor::End);
        if (ui.textEdit->toPlainText().size() > 2 * 1024 * 1024)
            ui.textEdit->clear();
    }, Qt::AutoConnection);
}

一些总结

在实现和测试上述TCP客户端的过程中,也验证和消除了一些 “老问题”。
1、由QIODevice::readyRead信号的DirectConnection连接的lambda槽函数执行结果,可得出:如果一个Tcp对象被归属到了子线程X中,那么readyRead信号最终将从此子线程X发出。
2.、同上,connected信号、disconnected信号等其发射线程,都是套接字对象的所在线程。

其他需要注意的是:
1、当connect内部使用lambda表达式做槽函数时,注意选择有Qt::ConnectionType 参数的那个函数版本,否则将默认为直接连接。

//默认为直接连接
connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
//可以配置连接方式 //Qt::UniqueConnections do not work for lambdas
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)

2、默认的连接方式 Qt::AutoConnection 在connect后生效的时刻是emit发射的时候,而不是执行connect 语句的时候。因此先执行moveToThread还是先执行connect过程是无关紧要的。具体可参见帮助文档中提及的:If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
3、至此,还没有读过Qt网络通信框架的源码,因此对于如下问题,还是无法清晰理解,如:Qt是如何对WinSocket进行封装的,Qt网络通信采用了哪种IO模型,QIODevice的架构是怎样的,readyRead信号是在什么情景下发出的,QThread线程是如何对接QIODevice上的?同一个Qt套接字对象到底能否在两个不同的子线程中进行工作?文章来源地址https://www.toymoban.com/news/detail-479975.html

到了这里,关于网络通信/QTcpSocket/实现一个可在子线程中发送和接收数据的TCP客户端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux网络编程:socket & pthread_create()多线程 实现clients/server通信

    UNIX网络编程:socket fork()多进程 实现clients/server通信 随笔介绍了通过fork()多进程实现了服务器与多客户端通信。但除了多进程能实现之外,多线程也是一种实现方式。 重要的是,多进程和多线程是涉及操作系统层次。随笔不仅要利用pthread_create()实现多线程编程,也要理解线

    2024年02月05日
    浏览(49)
  • 网络通信(13)-C#TCP服务器和客户端同时在一个进程实现的实例

    有时项目需求中需要服务器和客户端同时在一个进程实现,一边需要现场接收多个客户端的数据,一边需要将数据汇总后发送给远程服务器。下面通过实例演示此项需求。 C#TCP服务器和客户端同时在一个进程实现的实例如下: 界面设计 UI文件代码

    2024年01月22日
    浏览(65)
  • unity Sockets通信 使用UDP协议,设置客户端电脑网络配置,使用新线程获取数据,解决卡顿问题,

    今天调试和服务器连接,发现始终获取不到服务器的数据, 电脑和服务器都在同一局域网,仍然获取不到, 下面是电脑环境配置, 第一步: 设置网络为专用网络,然后点击配置防火墙和安全设置,关闭防火墙 (点击所连接的wifi的属性) 第二步:设置出站 入站规则 点击高

    2024年02月07日
    浏览(59)
  • C++实现socket网络通信

    🍺SOCKET网络通信系列文章链接如下:🍺 🎈【小沐学python】(一)Python简介和安装🎈 🎈Python实现socket网络通信🎈 🎈C++实现socket网络通信🎈 🎈Android实现socket网络通信🎈 🎈nodejs实现socket网络通信🎈 《斗诗篇》 陈献章: 窗外竹青青,窗间人独坐。 究竟竹与人,原来无两

    2023年04月09日
    浏览(38)
  • OpenSSL实现SSL网络通信

    Certainly! Here are the C language programs for a simple OpenSSL client and server that can establish a secure communication channel between them: l inux环境下 OpenSSL Server Program (server.c): OpenSSL Client Program (client.c): To compile and run the programs, you’ll need to make sure you have the OpenSSL library installed on your system. Use the foll

    2024年02月06日
    浏览(47)
  • Linux对网络通信的实现

    1、OP_WRITE触发条件:当操作系统写缓冲区有空闲时就绪。一般情况下写缓冲区都有空闲空间,小块数据直接写入即可,没必要注册该操作类型,否则该条件不断就绪浪费cpu;但如果是写密集型的任务,比如文件下载等,缓冲很可能满,注册该操作类型很有必要,同时注意写完

    2024年02月08日
    浏览(32)
  • Unity实现网络通信(UDP)

    UDP通信特点:         无连接,多对多         不可靠         面向数据报         效率高 UDP中的分包与黏包 分包:一段数据被分为两段或多段传输,在UDP通信方式中,因为UDP的不可靠性无法保证有序传输,因此尽量避免UDP自动分包。         其中一种方式是保证消

    2024年02月04日
    浏览(40)
  • Qt实现TCP网络通信

    在标准C++中没有提供专门用于套接字通信的类,所以只能使用操作系统提供的基于C语言的API函数,基于这些C的API函数我们也可以封装自己的C++类。或者我们可以使用Qt框架,它提供了用于套接字通信的类(TCP、UDP)这样我们就可以直接调用相关API即可。 使用Qt提供的类进行基于

    2024年04月17日
    浏览(41)
  • Java 网络编程详解:实现网络通信的核心技术

    网络编程是指利用计算机网络进行数据交换和通信的过程。它涉及到在不同主机之间传输数据,并允许不同设备之间进行连接和通信。网络编程不仅限于互联网,也可以包括局域网或广域网等各种网络环境。 在当今的互联网时代,几乎所有的应用都需要在不同设备之间进行数

    2024年02月11日
    浏览(52)
  • 【JavaEE】_基于UDP实现网络通信

    目录 1. 服务器 1.1 实现逻辑 1.2 代码 1.3 部分代码解释 2. 客户端 2.1 实现逻辑 2.2 代码 2.3 客户端部分代码解释 3. 程序运行结果 4. 服务器客户端交互逻辑 普通服务器:收到请求,根据请求计算响应,返回响应; 回显服务器:忽略计算,直接将收到的请求作为响应返回; (

    2024年01月21日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包