亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Qt網(wǎng)絡(luò)編程之TCP通信及常見問題

 更新時間:2022年08月22日 09:19:34   作者:Shark-Ele  
這篇文章主要為大家詳細(xì)介紹了Qt網(wǎng)絡(luò)編程之TCP通信及常見問題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下

本文為作者在開發(fā)項目時對Qt的TCP通信部分的總結(jié),主要包含TCP服務(wù)器收發(fā)數(shù)據(jù)的demo,解決TCP拆包和黏包問題的解決方案,以及對接收到的QByteArray數(shù)據(jù)的轉(zhuǎn)換。

簡介

TCP(Transmission Control Protocol,傳輸控制協(xié)議)是面向連接的協(xié)議,也就是說,在收發(fā)數(shù)據(jù)前,必須和對方建立可靠的連接,也就是我們常聽到的三次握手。TCP的目的是實現(xiàn)快速、安全的信息傳遞,因此在協(xié)議中針對針對數(shù)據(jù)安全做了很多處理,很適合應(yīng)用在一些對安全性要求高的場合。而UDP是非連接的協(xié)議,在形式上有點類似串口,適合傳輸語音、視頻等流量大的任務(wù)。

一、Qt中TCP通信基本用法

TCP 通信必須先建立 TCP 連接,通信端分為客戶端和服務(wù)端。服務(wù)端通過監(jiān)聽某個端口來監(jiān)聽是否有客戶端連接到來,如果有連接到來,則建立新的 socket 連接;客戶端通過 ip 和port 連接服務(wù)端,當(dāng)成功建立連接之后,就可進(jìn)行數(shù)據(jù)的收發(fā)了。

由于這一塊網(wǎng)上的資料很豐富,我就不做過多介紹,我是參考的正點原子 Qt 開發(fā)教程,這里將我的TCP服務(wù)器源碼貼出來,大家有需要的在此基礎(chǔ)上進(jìn)行修改。后面我主要介紹一下我在開發(fā)過程中實際遇到的問題與解決方案,僅供參考。

1. 在 .pro文件中添加 network

QT += core gui network

2. 封裝好的 mytcpserver.h

#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QTcpServer>
#include <QTcpSocket>
#include <QObject>

class TcpServer : public QObject
{
? ? Q_OBJECT
public:
? ? explicit TcpServer(QObject *parent = nullptr);

private:
? ? QTcpServer *tcpServer_6010; //TCP服務(wù)器(6010端口)
? ? QTcpSocket *tcpSocket_6010; //通信套接字(6010端口)
? ? QTcpServer *tcpServer_6030; //TCP服務(wù)器(6030端口)
? ? QTcpSocket *tcpSocket_6030; //通信套接字(6030端口)

public slots:
? ? void startListen(); ? ? ? ? ? ? ? ? ? ? ? ? //開始監(jiān)聽槽函數(shù)
? ? void stopListen(); ? ? ? ? ? ? ? ? ? ? ? ? ?//停止監(jiān)聽槽函數(shù)
? ? void clientConnected_6010(); ? ? ? ? ? ? ? ?//客戶端連接處理槽函數(shù)
? ? void clientConnected_6030(); ? ? ? ? ? ? ? ?//客戶端連接處理槽函數(shù)
? ? void receiveMessages_6010(); ? ? ? ? ? ? ? ?//接收消息(6010端口)
? ? void sendMessages_6030(QByteArray); ? ? ? ? //發(fā)送消息(6030端口)


signals:
? ? void signal_clientConnected_6010(); ? ? ? ? //客戶端連接成功信號(6010端口)
? ? void signal_clientConnected_6030(); ? ? ? ? //客戶端連接成功信號(6030端口)
? ? void signal_receiveMsg_6010(QByteArray); ? ?//傳輸TCP接收數(shù)據(jù)的信號
};

#endif // MYTCPSERVER_H

3. 封裝好的 mytcpserver.cpp

#include "mytcpserver.h"
#include <QDebug>

TcpServer::TcpServer(QObject *parent) : QObject(parent)
{
? ? tcpServer_6010 = new QTcpServer(this); ? ? ?//實例化TCP服務(wù)器(6010端口)
? ? tcpSocket_6010 = new QTcpSocket(this); ? ? ?//實例化TCP服務(wù)器(6010端口)
? ? tcpServer_6030 = new QTcpServer(this); ? ? ?//實例化TCP服務(wù)器(6030端口)
? ? tcpSocket_6030 = new QTcpSocket(this); ? ? ?//實例化TCP套接字(6030端口)

? ? connect(tcpServer_6010, SIGNAL(newConnection()), this, SLOT(clientConnected_6010()));
? ? connect(tcpServer_6030, SIGNAL(newConnection()), this, SLOT(clientConnected_6030()));
}

void TcpServer::clientConnected_6010()
{
? ? tcpSocket_6010 = tcpServer_6010->nextPendingConnection(); ? //獲取客戶套接字

? ? emit signal_clientConnected_6010(); ? ? ? ? ? ? ? ? ? ? ? ? //端口6010連接成功信號
? ? connect(tcpSocket_6010, SIGNAL(readyRead()), this, SLOT(receiveMessages_6010()));
}

void TcpServer::clientConnected_6030()
{
? ? tcpSocket_6030 = tcpServer_6030->nextPendingConnection(); ? //獲取客戶套接字

? ? emit signal_clientConnected_6030(); ? ? ? ? ? ? ? ? ? ? ? ? //端口6030連接成功信號
}

void TcpServer::startListen()
{
? ? tcpServer_6030->listen(QHostAddress("192.168.116.250"), 6030);
? ? tcpServer_6010->listen(QHostAddress("192.168.116.250"), 6010);
}

void TcpServer::stopListen()
{
? ? tcpServer_6010->close(); ? ? ? ? ? ? ? ? ? ?//關(guān)閉監(jiān)聽(6010)
? ? tcpServer_6030->close(); ? ? ? ? ? ? ? ? ? ?//關(guān)閉監(jiān)聽(6030)

? ? if(tcpSocket_6010->state() == tcpSocket_6010->ConnectedState)
? ? ? ? tcpSocket_6010->disconnectFromHost(); ? ?//斷開連接(6010)
? ? if(tcpSocket_6030->state() == tcpSocket_6030->ConnectedState)
? ? ? ? tcpSocket_6030->disconnectFromHost(); ? ?//斷開連接(6030)
}

/* 分包接收數(shù)據(jù),合成發(fā)送*/
void TcpServer::receiveMessages_6010()
{
? ? static uint receiveLen=0;
? ? static QByteArray receiveData; ? ? ?//TCP接收到的完整數(shù)據(jù)

? ? QByteArray receiveBuf = tcpSocket_6010->readAll(); ? ? ? ? ? ? ?//讀取TCP接收緩沖區(qū)的所有數(shù)據(jù)(不定長)
? ? uint messageLen = receiveBuf.size();

? ? receiveLen += messageLen; ? ? ? ? ? ? ? ? ? ? ? //計算一包數(shù)據(jù)的長度(16006)

? ? if(receiveLen < 16006) ? ? ? ? ? ? ? ? ? ? ? ? ?//還沒收滿
? ? {
? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? }
? ? else if(receiveLen == 16006) ? ? ? ? ? ? ? ? ? ?//剛好收滿
? ? {
? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? ? ? emit signal_receiveMsg_6010(receiveData); ? //發(fā)送傳輸數(shù)據(jù)的信號

? ? ? ? receiveLen=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //清空數(shù)據(jù)長度
? ? ? ? receiveData.clear(); ? ? ? ? ? ? ? ? ? ? ? ?//清空數(shù)據(jù)(clear會將receiveData長度變?yōu)?)
? ? }
? ? else if(receiveLen > 16006) ? ? ? ? ? ? ? ? ? ? //長度超過16006發(fā)生粘包
? ? {
? ? ? ? while(receiveLen > 16006)
? ? ? ? {
? ? ? ? ? ? qDebug()<<receiveBuf.size()<<endl;
? ? ? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? ? ? ? ? receiveBuf = receiveData.right(16007); ? ? ?//將超出16006范圍的數(shù)據(jù)放入receiveBuf數(shù)組中
? ? ? ? ? ? receiveData.truncate(16006); ? ? ? ? ? ? ? ?//將接收數(shù)組大于16006部分刪除
? ? ? ? ? ? emit signal_receiveMsg_6010(receiveData); ? //發(fā)送傳輸數(shù)據(jù)的信號

? ? ? ? ? ? receiveLen = receiveLen-16006; ? ? ? ? ? ? ?//更新接收數(shù)組長度
? ? ? ? }
? ? }
}

/* 服務(wù)端發(fā)送消息 */
void TcpServer::sendMessages_6030(QByteArray sendData)
{
? ? if(NULL == tcpSocket_6030) ? //TCP未連接,退出
? ? ? ? return;

? ? if(tcpSocket_6030->state() == tcpSocket_6030->ConnectedState) ? //TCP建立連接
? ? ? ? tcpSocket_6030->write(sendData); ? ? ? ? ? ? ? ? ? ? ? ? ? ?//發(fā)送消息
}

這里我需要使用了兩個端口,6030端口用作發(fā)送指令,6030端口用作接收數(shù)據(jù)。我的項目中傳輸?shù)臄?shù)據(jù)量較大,一包幾萬字節(jié),所以接收數(shù)據(jù)的 receiveMessages_6010() 函數(shù)已做了對黏包問題的處理。大家可以根據(jù)自己的需求做相應(yīng)的修改。

二、TCP黏包解決方法

1. 問題描述

TCP客戶端使用的是STM32開發(fā)的8通道高速數(shù)據(jù)采集卡,客戶端每100ms發(fā)送一次數(shù)據(jù),每次為16006字節(jié)的數(shù)據(jù)長度。由于TCP傳輸數(shù)據(jù)時,為了達(dá)到最佳傳輸效能,數(shù)據(jù)包的最大長度需要由MSS限定(MSS就是TCP數(shù)據(jù)包每次能夠傳輸?shù)淖畲髷?shù)據(jù)分段),超過這個長度會進(jìn)行自動拆包。也就是說雖然客戶端一次發(fā)送16006字節(jié)數(shù)據(jù),但是實際TCP傳輸時會將16006字節(jié)劃分為若干小包。我使用wireshark軟件抓包時可以看到,數(shù)據(jù)被拆分成長度為1440的數(shù)據(jù)包(不滿1440則單獨發(fā)送)。

2. TCP拆包和黏包現(xiàn)象

我們來看一下數(shù)據(jù)經(jīng)過TCP傳輸時可能出現(xiàn)的幾種情況:

接收端正常收到兩個數(shù)據(jù)包,即沒有發(fā)生拆包和粘包的現(xiàn)象。

接收端只收到一個數(shù)據(jù)包,由于TCP是不會出現(xiàn)丟包的,所以這一個數(shù)據(jù)包中包含了發(fā)送端發(fā)送的兩個數(shù)據(jù)包的信息,這種現(xiàn)象即為粘包。這種情況由于接收端不知道這兩個數(shù)據(jù)包的界限,所以對于接收端來說很難處理。

這種情況有兩種表現(xiàn)形式,如下圖。接收端收到了兩個數(shù)據(jù)包,但是這兩個數(shù)據(jù)包要么是不完整的,要么就是多出來一塊,這種情況即發(fā)生了拆包和粘包。這兩種情況如果不加特殊處理,對于接收端同樣是不好處理的。

3. 解決方法

在使用Qt編寫TCP服務(wù)器端程序時,Qt提供的TCP接收函數(shù) readAll() 并非一次讀取客戶端全部數(shù)據(jù),也不是讀取客戶端的每小包數(shù)據(jù),而是讀取TCP服務(wù)器的接收緩沖區(qū)的全部數(shù)據(jù),這里算是Qt的一個坑,因為乍一看 readAll() 不就是讀取全部數(shù)據(jù)嘛,而官方文檔又沒有給出具體解釋。

qDebug()<<tcpSocket_6010->byteAvailable()<<endl;?? ?//打印當(dāng)前緩沖區(qū)中的數(shù)據(jù)長度
QByteArray receiveBuf = tcpSocket_6010->readAll();?? ?//讀取緩沖區(qū)中的所有數(shù)據(jù)
qDebug()<<tcpSocket_6010->byteAvailable()<<endl;?? ?//此時打印結(jié)果為0

其實仔細(xì)想一下,被拆包的每包數(shù)據(jù)都被封裝成相同的格式進(jìn)行傳輸,TCP協(xié)議并沒有提供任何標(biāo)識,接收端也壓根無法自動判別哪些包屬于完整的一包數(shù)據(jù)。

知道了接收函數(shù) readAll() 的原理,再加上我們已知客戶端發(fā)送的每包數(shù)據(jù)長度為 16006 字節(jié),那么我們不就可以手動計算接收數(shù)據(jù)的長度,然后將這些數(shù)據(jù)拼接合成嘛。確實應(yīng)該這么做,但是別忘了TCP還有黏包的問題,也就是TCP傳輸?shù)臄?shù)據(jù)包可能出現(xiàn)粘合在一起的現(xiàn)象,本次要傳輸?shù)臄?shù)據(jù)和下一次傳輸?shù)臄?shù)據(jù)被粘合在一起,那么我們按長度累加計算接收到的數(shù)據(jù)長度可能無法獲取我們想要的結(jié)果。

我的解決方法如下:

/* 分包接收數(shù)據(jù),合成發(fā)送*/
void TcpServer::receiveMessages_6010()
{
? ? static uint receiveLen=0;?? ? ? ??? ?//累加接收數(shù)據(jù)的長度
? ? static QByteArray receiveData; ? ? ?//TCP接收到的完整數(shù)據(jù)

? ? QByteArray receiveBuf = tcpSocket_6010->readAll();//讀取TCP接收緩沖區(qū)的所有數(shù)據(jù)(不定長)
? ? uint messageLen = receiveBuf.size();?? ??? ?//每次從緩沖區(qū)讀取的數(shù)據(jù)長度

? ? receiveLen += messageLen; ? ? ? ? ? ? ? ? ? ? ? //計算一包數(shù)據(jù)的長度(16006)

? ? if(receiveLen < 16006) ? ? ? ? ? ? ? ? ? ? ? ? ?//還沒收滿
? ? {
? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? }
? ? else if(receiveLen == 16006) ? ? ? ? ? ? ? ? ? ?//剛好收滿
? ? {
? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? ? ? emit signal_receiveMsg_6010(receiveData); ? //發(fā)送傳輸數(shù)據(jù)的信號

? ? ? ? receiveLen=0; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //清空數(shù)據(jù)長度
? ? ? ? receiveData.clear(); ? ? ? ? ? ? ? ? ? ? ? ?//清空數(shù)據(jù)(clear會將receiveData長度變?yōu)?)
? ? }
? ? else if(receiveLen > 16006) ? ? ? ? ? ? ? ? ? ? //長度超過16006發(fā)生粘包
? ? {
? ? ? ? while(receiveLen > 16006)
? ? ? ? {
? ? ? ? ? ? qDebug()<<receiveBuf.size()<<endl;
? ? ? ? ? ? receiveData.append(receiveBuf); ? ? ? ? ? ? //每接收一次數(shù)據(jù)就追加到接收數(shù)組中
? ? ? ? ? ? receiveBuf = receiveData.right(16007); ? ? ?//將超出16006范圍的數(shù)據(jù)放入receiveBuf數(shù)組中
? ? ? ? ? ? receiveData.truncate(16006); ? ? ? ? ? ? ? ?//將接收數(shù)組大于16006部分刪除
? ? ? ? ? ? emit signal_receiveMsg_6010(receiveData); ? //發(fā)送傳輸數(shù)據(jù)的信號

? ? ? ? ? ? receiveLen = receiveLen-16006; ? ? ? ? ? ? ?//更新接收數(shù)組長度
? ? ? ? }
? ? }
}

大體思路就是收滿16006字節(jié)的數(shù)據(jù)就將數(shù)據(jù)發(fā)送出去,如果發(fā)生黏包,數(shù)據(jù)長度超過16006就對數(shù)據(jù)進(jìn)行裁剪,多出來的部分作為下一包數(shù)據(jù)的開頭。經(jīng)過測試,該方法能夠完美解決在傳輸大量數(shù)據(jù)時,TCP拆包和黏包導(dǎo)致的數(shù)據(jù)無法解析的問題,讀者可參考此方法自行修改。

三、TCP接收到的QByteArray類型數(shù)據(jù)的轉(zhuǎn)換

上述通過 readAll() 函數(shù)接收到的數(shù)據(jù)為 QByteArray 類型,這是一個Qt 自己定義的一種類似于 String 的處理字符串的類,這個類也提供了很多成員函數(shù),方便我們對數(shù)據(jù)進(jìn)行轉(zhuǎn)化。

如果你不需要對接收到的數(shù)據(jù)進(jìn)行運算,只是想打印數(shù)據(jù),那么可以直接使用 QByteArray 類型。但是如果你需要對數(shù)據(jù)進(jìn)做加減乘除,那使用 QByteArray 就不合適了,需要轉(zhuǎn)換成基本數(shù)據(jù)類型。需要注意上圖中 QByteArray 類提供的幾個成員函數(shù),比如 toHex() ,它的返回值依然是 QByteArray,也就是說它是將原始的 QByteArray 轉(zhuǎn)換成十六進(jìn)制的 QByteArray,比如 “255”->“FF”,本質(zhì)上還是字符串。大家在使用官方提供的成員函數(shù)時,一定要看一下函數(shù)的返回值,不要想當(dāng)然了。

我在客戶端發(fā)送的每個數(shù)據(jù)為兩個字節(jié),如 0xFFFF,使用 readAll() 接收到的 QByteArray 類型的數(shù)據(jù)也只是按字節(jié)接收,它并不知道我們一個數(shù)據(jù)占幾個字節(jié),所以實際上 receiveData[0] = 255, receiveData[1] = 255。由于我沒有找到現(xiàn)成的可供直接使用的處理函數(shù),所以就手動實現(xiàn)了一下:

/* cacheBuf為合成后的uint數(shù)組, msg為待處理的QByteArray數(shù)據(jù) */
for(uint i=0; i<1000; i++) ?//高低位兩字節(jié)合成為一個uint
{
? ? ? cacheBuf[i] = msg[16*i+4] & 0x000000FF; ? ? ? ? ? //低位
? ? ? cacheBuf[i] |= ((msg[16*i+5] << 8) & 0x0000FF00);?? ?//高位
}

總結(jié)

即使我在stm32單片機上發(fā)送的是int類型的數(shù)據(jù),但是在Qt上通過 toInt() 函數(shù)時接收不到我想要的數(shù)據(jù)的,因為32位的單片機中int占兩個字節(jié),而Qt中的C++的int類型占4個字節(jié),那么我使用 toInt() 函數(shù)來接收數(shù)據(jù)時,程序就會以4個字節(jié)為一個數(shù)來接收。這告訴我們,在不同平臺之間傳輸數(shù)據(jù)的時候,要考慮同種類型的數(shù)據(jù),它們的寬度是否一致。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 基于opencv實現(xiàn)視頻中的顏色識別功能

    基于opencv實現(xiàn)視頻中的顏色識別功能

    這篇文章主要介紹了基于opencv實現(xiàn)視頻中的顏色識別功能,文章詳細(xì)介紹了顏色識別的原理及opencv中的顏色模型,基于c++代碼實現(xiàn)顏色識別功能,需要的朋友可以參考下
    2022-07-07
  • C語言實現(xiàn)動態(tài)開辟存儲楊輝三角

    C語言實現(xiàn)動態(tài)開辟存儲楊輝三角

    這篇文章主要介紹了如何利用C語言實現(xiàn)動態(tài)開辟存儲楊輝三角,可以靈活的開辟空間,充分的利用空間。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下
    2022-03-03
  • 使用C++調(diào)用Python代碼的方法詳解

    使用C++調(diào)用Python代碼的方法詳解

    這篇文章主要介紹了使用C++調(diào)用Python代碼并給大家介紹了.py和.pyc的區(qū)別,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • C++圖解單向鏈表類模板和iterator迭代器類模版詳解

    C++圖解單向鏈表類模板和iterator迭代器類模版詳解

    這篇文章主要為大家詳細(xì)介紹了C++圖解單向鏈表類模板和iterator迭代器類模版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • OpenCV透視變換應(yīng)用之書本視圖矯正+廣告屏幕切換

    OpenCV透視變換應(yīng)用之書本視圖矯正+廣告屏幕切換

    透視變換是指利用透視中心、像點、目標(biāo)點三點共線的條件,按透視旋轉(zhuǎn)定律使承影面繞跡線旋轉(zhuǎn)某一角度,破壞原有的投影光線束,仍能保持承影面上投影幾何圖形不變的變換。本文將為大家介紹兩個OpenCV透視變換應(yīng)用,需要的可以參考一下
    2022-08-08
  • C++中函數(shù)模板的用法詳細(xì)解析

    C++中函數(shù)模板的用法詳細(xì)解析

    所謂函數(shù)模板實際上是建立一個通用函數(shù),其涵涵素類型額形參類型不具體指定,用一個虛擬的類型來代表,這個通用函數(shù)就稱為函數(shù)模板
    2013-10-10
  • C++基于EasyX庫實現(xiàn)拼圖小游戲

    C++基于EasyX庫實現(xiàn)拼圖小游戲

    這篇文章主要為大家詳細(xì)介紹了C++基于EasyX庫實現(xiàn)拼圖小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 淺析c與c++中struct的區(qū)別

    淺析c與c++中struct的區(qū)別

    c與c++中struct的區(qū)別你是否了解,下面小編就詳細(xì)的為大家介紹一下
    2013-07-07
  • C語言實現(xiàn)推箱子游戲

    C語言實現(xiàn)推箱子游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)推箱子游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • C?C++輸入輸出基礎(chǔ)教程示例詳解

    C?C++輸入輸出基礎(chǔ)教程示例詳解

    當(dāng)我們在網(wǎng)站做題的時候經(jīng)常會遇到各種要求的輸入輸出,而且會有時間超限等多個問題,這時我們就要優(yōu)化我們的輸入輸出或者規(guī)范我們的輸入輸出格式,下面介紹C和C++中的輸入輸出問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2023-11-11

最新評論