天天看点

如何正确使用QTcpSocket的readyRead信号?

一、问题描述:

你之所以会来看我这篇文章,大概是遇到了一下几个问题:

1、使用QTcpSocket时,readyread函数没有触发,或者触发了,但是触发次数不是自己想象的那样。

2、readyread槽函数中,接收到的数据不对。

我们先看一下Qt官方文档的说法:

官方文档中对readyread函数解释很简短:

    This signal is emitted once every time new data is available for reading from the device's current read channel. It will only be emitted again once new data is available, such as when a new payload of network data has arrived on your network socket, or when a new block of data has been appended to your device.

//让我来解释给你这句话的意思,解释完你就会有新的认识。//

       每一次,在当前用来存放读数据的位置中(也就是可读缓冲区),有可读数据可用,都会发送这个信号。当新的数据到来的时候,这个信号还会再发送且仅发送一次。比如:新的网络数据到达你的网络socket,或者新的数据块添加到你的设备。

       我们一句一句话来理解,首先是:“每一次,在当前用来存放读数据的位置中(也就是可读缓冲区),有可读数据可用,都会发送这个信号”。这句话的意思是,当你的QTcpSokcet第一次接收到数据,也就是socket的缓冲区从没有数据变为有数据时,触发一次。

       接下来非常关键的一句话是:“当新的数据到来的时候,这个信号还会再发送且仅发送一次。”很多朋友对新的数据到来的错误理解是,发送端对应的QTcpSokcet写一次,也就是write函数调用一次,那么接收方就会有新的数据到达,于是readyread信号被触发一次。这个理解是错误的。经过测试,我们发现事实是这样的:发送和接收的次数是没有一一对应关系的。发送端write函数调用一次,假如这一次write了2M的数据,那么接收方的readyread信号就往往会触发两次以上。反过来,如过发送端write函数被调用两次或两次以上,每次发送的数据量很小,比如两三个字节,那么接收方的readyread信号也有可能只触发一次。

        这是什么回事呢?文档明明说有新的数据来,就会帮我触发一次。其实啊,这里说新的数据来,不是说从发送端有新的数据来到你的主机,而是数据从Tcp/ip协议栈到达接收端的Qt应用程序,也就是系统io缓冲区到达Qt应用程序,数据从系统到达Qt应用程序一次,readyread信号就会触发一次。

        还有一个非常要注意的词就是“only once”,仅仅一次。什么意思呢?其实是这样的,第一次数据来的时候,触发一次readyread信号,但如果此时你的readyread槽函数还没有及时执行,或者从根本上说QTcpsocket对象的byteAvailable函数返回值还不为0,而新的数据又来了而且来了很多次,那么,这些所有的都将会只再触发一次readyread信号。如果此时你的readyread槽函数执行了,那么这时候来的新的数据就会触发第三个readyread信号。也就是说,还没有响应的readyread信号最多只有两个。想想也是啊,如果我发送端一直发送数据,你的系统就一直接收并将数据发送给你的应用,然后每得到一个字节都触发一次readyRead信号,触发到成千上万个,那击崩一个服务器不就很简单了?

       最后一个关键词,那就是“数据”。通过上面的介绍,你可能也意识到了,既然发送和接收没有一一对应关系,那么我接收到的东西到底是什么样子的?这时候你就需要对数据流有一定的认识。数据从另一端以流的方式流进了你的机子。数据到了的时候,系统就会通过数据报的端口来识别这个数据是要发送给这台机子的哪个应用程序。注意TCP和udp的数据是有区别的,tcp数据包没有边界,udp有。也就是说对于tcp,数据是连在一起的分不开的,最小粒度是1字节。发送端的应用程序write函数一次写了多少数据,接收方的系统是不知道的。那么接收方系统到底接收到多少数据才发送给qt应用程序呢?这个算法是这样的:超时和超过缓冲。超时:无论来了多少数据,超过这个时间,系统就会发送当前接收到的数据给qt应用程序。超过缓冲,在未超时的情况下,系统缓冲区满了,系统就会将数据发送给qt应用程序。系统TCP/IP缓冲最大是65536个字节。

        根据以上机制,在readyread槽函数中,调用QTcpSocket的byteAvailable函数返回的值,是不可预知的,取决于系统发给应用时,发送的数量,这个数量的最大值就是65536字节。这个byteAvailable函数返回的值,就是本次readyread槽函数中所能读到的网络数据量。如果在你正在读取数据时,又有新的数据到来,byteAvailable返回的值是不会被影响的。你读了多少数据,byteAvailable就会减去多少数据。如果用的是readAll函数就是读完,byteAvailable就会返回0。注意:网上流传这很多用流的方式接收数据,在readyread槽函数还没有结束之前,QTcpSocket缓存数据是不会清除掉的,所以byteAvailable返回值从槽函数开始到结束都不会变。(流其实就是序列化,流进和流出数据类型要保持一致,谨慎使用。)readyRead信号这样的触发机制,就可能会存在黏包问题,关于黏包问题的解释和解决方案网上已经有很多很优秀的文章了,我就不赘述了。

       最后,要是还没有解决你的readyread函数还是没有触发,看看是不是防火墙有没有禁掉你的qt应用,有没有关掉VPN。

继续阅读