博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
QT下多线程调用TCP的问题及可能的解决方案
阅读量:4223 次
发布时间:2019-05-26

本文共 4940 字,大约阅读时间需要 16 分钟。

背景:在上一篇博文中结尾时,提到QT下所有IO类都不允许跨线程调用,这极大增加了编程难度。本文接着上一篇,主要讨论当套接字与UI线程不在同一线程时,如何使TCP的收发实时。

1. 能否跨线程调用TCP套接字?

  对于TCP通信,一个常见的操作就是读写分开,即读写分别在不同线程中执行,这样实现实时全双工通信,那么在QT中能否实现读写线程分开呢?理论上将是不可以的,但是实际操作发现能实现(会有错误警告)。

  这涉及到信号和槽的连接方式。通常QT的信号和槽有三种常用的连接方式:

(1) Qt::AutoConnection:QT默认连接方式。当信号接收方与信号发送方在同一线程时,等价于Qt::DirectConnection;否则等价于Qt::QueuedConnection。

(2) Qt::DirectConnection: 当信号被发送后,槽函数立即执行。这对于实时通信意义重大,如UI界面发送TCP消息之后,需要实时等待TCP响应以进行不同的操作,这种连接方式就能保证TCP能立即发送消息。

(3) Qt::QueuedConnection: 当信号发送时,对应的槽函数加入到槽函数所在线程事件处理队列中,等待执行。

对于Qt::DirectConnection连接,有一个默认属性:当信号和槽不在同一个线程时,槽函数不会在槽函数所在线程执行,而是会在在信号发送的线程执行(具体细节见QT官方文档)。这就为不同线程调用TCP套接字提供了可能,以上一篇(链接见开题头)的程序为基础,又添加了TCP发送功能,点击按钮,需要将对应消息实时发送出去,界面如下:

                   

  其他部分不变,在上一篇文章的基础上添加了输入框和发送按钮。由于TCP所在线程主循环一直调用TCP接收消息,所以点击按钮发送消息时,子线程一直忙碌,并不会响应,所以此时我希望UI线程来发送消息。对上一篇文章TcpMoveToThread类构造函数稍加修改:

复制代码

1 TcpMoveToThread::TcpMoveToThread(QObject* parent) 2 { 3     m_tcp.moveToThread(&m_thread); //加入到子线程 4  5     connect(&m_thread,&QThread::started,&m_tcp,&TcpModel::tcpWork); //一旦线程开始,就调用接收Tcp的函数 6     connect(&m_tcp,&TcpModel::dataRecved,this,&TcpMoveToThread::dataChangedSlot); 7     connect(&m_thread,&QThread::finished,&m_tcp,&TcpModel::tcpClose); //线程结束时关闭socket,删除申请内存 8  9     //直接连接槽函数10     connect(this,&TcpMoveToThread::senddataSignal,&m_tcp,&TcpModel::tcpSendMsgSlot,Qt::DirectConnection);11 12     m_thread.start(); //开启子线程13 }

复制代码

红色部分是修改部分,即采用了Qt::DirectConnection。由于信号发送方在UI线程,接收方在TCP子线程,所以此时调用的槽函数不会在子线程中执行,而是直接在UI线程执行,这样收发在不同线程,都能实时响应。这种做法虽然没有影响收发效果,但是每次会提示Socket notifiers cannot be enabled or disabled from another thread,从提示结果就能看出这是跨线程调用TCP造成的。这种提示的后果未知(因为程序仍能正常运行)。

2. 如何优化设计

  虽然1所介绍的方案能实现功能,但是毕竟出现错误提示,不知道会造成何种后果。所以最好还是不采用以上方式。那么对于TCP的收发,如何做到实时响应呢?

  造成1中的程序原因是在TCP子线程一直循环接收TCP数据,这种操作是非常不明智的。为了让TCP子线程空闲,只在收发数据时运行,可以将TCP接收也改为信号和槽的形式。TCP收到消息时,会发送信号QTcpSocket::readyRead,通过该信号与TCP接收绑定,就不用在子线程中循环调用TCP接收函数了。改造后的tcpmodel如下:

复制代码

1 //tcpmodel.h  2   3 #ifndef TCPMODEL_H  4 #define TCPMODEL_H  5 #include 
6 #include
7 #include
8 9 class TcpModel:public QObject 10 { 11 Q_OBJECT 12 public: 13 TcpModel(QObject* parent=nullptr); 14 15 QString sendmsg; 16 17 signals: 18 void dataRecved(QString data); //通知TcpMoveToThread类,数据接收 19 20 public slots: 21 void tcpWork(); 22 void tcpClose(); 23 void tcpSendMsgSlot(QString msg); 24 void tcpRecvSlot(); //接收消息的槽函数 25 26 27 private: 28 QTcpSocket* m_socket; 29 QString msg; 30 }; 31 32 //将TcpModel在QML初始化时移入到子线程 33 class TcpMoveToThread: public QObject 34 { 35 Q_OBJECT 36 Q_PROPERTY(QString m_data MEMBER m_data) 37 public: 38 TcpMoveToThread(QObject* parent=nullptr); 39 ~TcpMoveToThread(); 40 41 signals: 42 void dataChanged(); //用于通知QML应用,数据接收到 43 void senddataSignal(QString msg); //发送数据的信号 44 45 46 public slots: 47 void dataChangedSlot(QString msg); 48 49 50 private: 51 QThread m_thread; 52 TcpModel m_tcp; 53 QString m_data; //保存接收数据 54 }; 55 56 #endif // TCPMODEL_H 57 58 59 //tcpmodel.cpp 60 #include "tcpmodel.h" 61 #include
62 63 TcpModel::TcpModel(QObject* parent) 64 { 65 } 66 67 void TcpModel::tcpWork() 68 { 69 m_socket=new QTcpSocket(); 70 m_socket->connectToHost("127.0.0.1",8000); 71 connect(m_socket,&QTcpSocket::readyRead,this,&TcpModel::tcpRecvSlot); //连接TCP接收槽函数 72 } 73 74 void TcpModel::tcpClose() 75 { 76 m_socket->close(); 77 delete m_socket; 78 } 79 80 void TcpModel::tcpSendMsgSlot(QString msg) 81 { 82 m_socket->write(msg.toLocal8Bit(),msg.toLocal8Bit().length()); 83 m_socket->waitForBytesWritten(); 84 } 85 86 void TcpModel::tcpRecvSlot() 87 { 88 if(m_socket->bytesAvailable()>0) 89 { 90 QByteArray res=m_socket->readAll(); 91 msg=QString::fromLocal8Bit(res.data()); 92 emit dataRecved(msg); //接收完成信号 93 } 94 } 95 96 97 98 TcpMoveToThread::TcpMoveToThread(QObject* parent) 99 {100 m_tcp.moveToThread(&m_thread); //加入到子线程101 102 connect(&m_thread,&QThread::started,&m_tcp,&TcpModel::tcpWork); //一旦线程开始,就调用接收Tcp的函数103 connect(&m_tcp,&TcpModel::dataRecved,this,&TcpMoveToThread::dataChangedSlot);104 connect(&m_thread,&QThread::finished,&m_tcp,&TcpModel::tcpClose); //线程结束时关闭socket,删除申请内存105 106 //直接连接槽函数,功能正常,但提示Socket notifiers cannot be enabled or disabled from another thread107 connect(this,&TcpMoveToThread::senddataSignal,&m_tcp,&TcpModel::tcpSendMsgSlot);108 109 m_thread.start(); //开启子线程110 }111 112 TcpMoveToThread::~TcpMoveToThread()113 {114 m_thread.exit();115 m_thread.wait();116 }117 118 void TcpMoveToThread::dataChangedSlot(QString msg)119 {120 m_data=msg;121 emit dataChanged();122 }

复制代码

主要改动都用红色标出了,特别注意:

第83行:QT的TCP通信默认都是异步的,所以即时调用了write和read函数,也可能不会立即进行读写,表现在程序中就是能立即执行槽函数,但TCP收发有明显延迟(可以将83注释掉,再看现象)。所以为了将异步改为同步,QT的TCP规定了以下函数:

waitForConnected() 等待链接的建立

waitForReadyRead() 等待新数据的到来
waitForBytesWritten() 等待数据写入socket
waitForDisconnected() 等待链接断开

 

转载地址:http://jwemi.baihongyu.com/

你可能感兴趣的文章
解释 Zuul 的 zuul.strip-prefix 属性
查看>>
翻译 AbstractQueuedSynchronizer ( AQS )类注释
查看>>
jdbc中Datetime与java.util.Date的相互转换
查看>>
hibernate中取得connection的方法
查看>>
如何使用log4j输出单个级别的log到指定文件
查看>>
表单元素与提示文字无法对齐的解决方法
查看>>
图片按钮消除边框
查看>>
增加windows下Tomcat运行时的内存
查看>>
tomcat群集中session共享的几个方案
查看>>
查找google谷歌北京IP地址的方法
查看>>
动态代理技术的实现与理解
查看>>
使用Beyond Compare合并代码后出现乱码问题
查看>>
dmp数据文件导入问题
查看>>
使用Beyond Compare对比文件夹
查看>>
深入理解java虚拟机 -- jVM高级特性与最佳实践
查看>>
阿里巴巴 java 开发规约
查看>>
impdp命令出现ora-39070解决方案
查看>>
ora-01756
查看>>
java 核心技术Ⅱ--章四:网络
查看>>
java 核心技术Ⅱ--章五:JDBC数据库编程
查看>>