免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
使用 Qt 獲取 UDP 數(shù)據(jù)并顯示成圖片

一個(gè)項(xiàng)目,要接收 UDP 數(shù)據(jù)包,解析并獲取其中的數(shù)據(jù),主要根據(jù)解析出來(lái)的行號(hào)和序號(hào)將數(shù)據(jù)拼接起來(lái),然后將拼接起來(lái)的數(shù)據(jù)(最重要的數(shù)據(jù)是 R、G、B 三個(gè)通道的像素值)顯示在窗口中??紤]到每秒鐘要接收的數(shù)據(jù)包的數(shù)量較大,Python 的處理速度可能沒(méi)有那么快,而且之前對(duì) Qt 也比較熟悉了,所以用Qt 作為客戶端接收處理數(shù)據(jù)包,用近期學(xué)習(xí)的 Python 模擬發(fā)送數(shù)據(jù)包。

數(shù)據(jù)格式

在 TCP/IP 協(xié)議中,UDP 數(shù)據(jù)包的大小是由限制的,因此用 UDP 傳輸數(shù)據(jù)時(shí),還要在 UDP 層上再封裝一層自定義的協(xié)議。這個(gè)自定義的協(xié)議比較簡(jiǎn)單,每個(gè) UDP 包的大小為 1432 個(gè)字節(jié),分為幾個(gè)部分:

部分起始字節(jié)字節(jié)長(zhǎng)度說(shuō)明
Start04包頭部的 Magic Number,設(shè)為 0x53746172
PartialCnt41分包總數(shù),一個(gè)字節(jié)(0-255)以內(nèi)
PartialIdx51分包序號(hào)
SampleLine61采樣率
RGB71rgb 通道標(biāo)識(shí)符
LineIdx84行號(hào),每一行可以包含 RGB 三個(gè)通道的數(shù)據(jù),每個(gè)通道由多個(gè)分包組成
ValidDataLen124數(shù)據(jù)部分有效字節(jié)數(shù)
LineBytes164每行數(shù)據(jù)包含的字節(jié)總數(shù)
Reserve20128保留部分
Data1481280數(shù)據(jù)部分
end14284包尾部的 Magic Number,設(shè)為 0x54456e64

上述表格描述的就是一個(gè)完整的 UDP 包。這里的一個(gè) UDP 數(shù)據(jù)包包含的是 RGB 某個(gè)通道的某一部分的數(shù)據(jù)。換種說(shuō)法:

  • 一行數(shù)據(jù)

    • R 通道數(shù)據(jù)(若干個(gè)分包組成)

    • G 通道數(shù)據(jù)(若干個(gè)分包組成)

    • B 通道數(shù)據(jù)(若干個(gè)分包組成)

所以要生成/解析 UDP 包,最重要的是 PartialCnt、PartialIdx、RGB、LineIdx、Data 這幾個(gè)部分。清楚了自定義協(xié)議就可以開(kāi)始編寫(xiě)模擬包的生成和相應(yīng)的接收邏輯了。

使用 Python 模擬 UDP 發(fā)包

由于本地開(kāi)發(fā)的時(shí)候缺少必要的硬件環(huán)境,為了方便開(kāi)發(fā),用 Python 編寫(xiě)一個(gè)簡(jiǎn)單的 UDPServer,發(fā)送模擬生成的數(shù)據(jù)包。根據(jù)上述協(xié)議,可以寫(xiě)出如下的 CameraData 類(lèi)來(lái)表示 UDP 數(shù)據(jù)包:

# -*- coding: utf-8 -*-DATA_START_MAGIC = bytearray(4)DATA_START_MAGIC[0] = 0x53  # SDATA_START_MAGIC[1] = 0x74  # tDATA_START_MAGIC[2] = 0x61  # aDATA_START_MAGIC[3] = 0x72  # rDATA_END_MAGIC = bytearray(4)DATA_END_MAGIC[0] = 0x54 # TDATA_END_MAGIC[1] = 0x45 # EDATA_END_MAGIC[2] = 0x6e # nDATA_END_MAGIC[3] = 0x64 # dslice_start_magic = slice(0, 4)slice_partial_cnt =  4slice_partial_idx =  5slice_sample_line =  6slice_rgb_extern =  7slice_line_idx = slice(8, 12)slice_valid_data_len = slice(12, 16)slice_line_bytes = slice(16, 20)slice_resv = slice(20, 148)slice_data = slice(148, 1428)slice_end_magic = slice(1428, 1432)import numpy as npclass CameraData(object):def __init__(self):# self.new()# self.rawdata = rawdataself.dataLow = 10self.dataHigh = 20self.new()    def genRandomByte(self, by=4):r = bytearray(by)        for i in range(by):            r[i] = np.random.randint(0, 255)    def setPackageIdx(self, i = 0):self.rawdata[slice_partial_idx] = i    def setRGB(self, c = 1):self.rawdata[slice_rgb_extern] = c    def setLineIdx(self, line):start = slice_line_idx.start        self.rawdata[start+3] = 0x000000ff & line        self.rawdata[start+2] = (0x0000ff00 & line) >> 8self.rawdata[start+1] = (0x00ff0000 & line) >> 16self.rawdata[start+0] = (0xff000000 & line) >> 24def setValidDataLen(self, len):start = slice_valid_data_len.start        self.rawdata[start+3] = 0x000000ff & len        self.rawdata[start+2] = (0x0000ff00 & len) >> 8self.rawdata[start+1] = (0x00ff0000 & len) >> 16self.rawdata[start+0] = (0xff000000 & len) >> 24def setLineBytes(self, len):start = slice_line_bytes.start        self.rawdata[start+3] = 0x000000ff & len        self.rawdata[start+2] = (0x0000ff00 & len) >> 8self.rawdata[start+1] = (0x00ff0000 & len) >> 16self.rawdata[start+0] = (0xff000000 & len) >> 24def randomData(self):size = slice_data.stop - slice_data.start        arr = np.random.randint(self.dataLow, self.dataHigh, size, dtype=np.uint8)        self.rawdata[slice_data] = bytearray(arr)    def new(self):"""構(gòu)造新的數(shù)據(jù)對(duì)象        """self.rawdata = bytearray(1432)        self.rawdata[slice_start_magic] = DATA_START_MAGIC        self.rawdata[slice_partial_cnt] = 0x02self.rawdata[slice_partial_idx] = 0x00self.rawdata[slice_sample_line] = 0x03self.rawdata[slice_rgb_extern] = 0x01self.setLineIdx(0x00)        self.setValidDataLen(1280)        self.setLineBytes(1432)        self.randomData()        self.rawdata[slice_end_magic] = DATA_END_MAGIC    def hex(self):return self.rawdata.hex()    def __repr__(self):return '<CameraData@{} hex len: {}>'.format(hex(id(self)), len(self.rawdata))

CameraData 中的 rawdata 是一個(gè) bytearray 對(duì)象,它將會(huì)被 UdpServer 通過(guò)網(wǎng)絡(luò)接口發(fā)送出去。設(shè)置 4 個(gè)字節(jié)大小的整數(shù)時(shí)(如寫(xiě) LineIdx 行號(hào)),不能直接將數(shù)值賦到 rawdata 中,要將其中的 4 個(gè)字節(jié)分別賦值到對(duì)應(yīng)的地址上才行。

CameraData 中的 randomData 方法是模擬隨機(jī)數(shù)據(jù),更好的做法不是完全隨機(jī)給每個(gè)像素點(diǎn)賦值,而是有規(guī)律的變化,這樣在接收數(shù)據(jù)出現(xiàn)問(wèn)題、分析問(wèn)題的時(shí)候可以直觀地看到哪里有問(wèn)題。

然后我們需要定義一個(gè) UdpServer,用它來(lái)將數(shù)據(jù)對(duì)象中包含的信息發(fā)送出去。

import socketclass UdpServer( object ):"""該類(lèi)功能是處理底層的 UDP 數(shù)據(jù)包發(fā)送和接收,利用隊(duì)列緩存所有數(shù)據(jù)    """def __init__(self, *args, **kwargs):self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)        # self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)self._sock.bind( ('', DATA_PORT+11 ) )        self._sock.settimeout( None )  # never timeout# self._sock.setblocking( 0 ) # none blockdef send_msg( self, msg ):"""發(fā)送消息,        @param msg 字典對(duì)象,發(fā)送 msg 的 rawdata 字段        """self._sock.sendto( msg.rawdata, ('192.168.8.1', DATA_PORT))

這個(gè) UdpServer 非常簡(jiǎn)單,因?yàn)楹罄m(xù)會(huì)通過(guò)這個(gè) UdpServer 不停的發(fā)包,但是每次發(fā)包必須等待發(fā)送端成功將 UDP 包發(fā)送出去,這里不要將 socket 對(duì)象設(shè)置成非阻塞的,否則程序運(yùn)行時(shí)會(huì)出現(xiàn)錯(cuò)誤提示(盡管可以忽略掉這個(gè)錯(cuò)誤提示,但是沒(méi)必要設(shè)置成非阻塞的,阻塞模式完全足夠了)。

在 github 中可以找到完整的 Python 文件,里面定義了其他類(lèi),如 DataSender、RGBSenderDataSender 是在一個(gè)線程里面發(fā)送 RGB 三個(gè)通道的值,RGBSender 的一個(gè)對(duì)象只會(huì)發(fā)送 RGB 三個(gè)通道中的某一個(gè)的值。

小結(jié)和注意事項(xiàng)

在本地測(cè)試的時(shí)候,為了方便在任務(wù)管理器中看到網(wǎng)絡(luò)占用率,最初是在 VirtualBox 的 ubuntu 虛擬機(jī)上運(yùn)行這個(gè) Python 程序的,但是受到虛擬機(jī)的資源分配和電腦性能影響,調(diào)用 singleMain 函數(shù)時(shí)每秒鐘最多只能產(chǎn)生 50MB 的數(shù)據(jù)量。但是在本地非虛擬機(jī)環(huán)境運(yùn)行的時(shí)候最多可以達(dá)到 80MB 的數(shù)據(jù)量。所以盡可能地使用本地環(huán)境運(yùn)行該 Python 程序可以最大限度的生成數(shù)據(jù)包。

如果讓 RGB 三個(gè)通道分別在三個(gè)不同的進(jìn)程中執(zhí)行發(fā)送過(guò)程(注釋掉 singleMain 的調(diào)用,換用 multiSend 方法),那么每秒鐘的數(shù)據(jù)量可到 200MB,不過(guò) 80MB 的數(shù)據(jù)量已經(jīng)足夠多了(接近千兆網(wǎng)卡的上限了,網(wǎng)絡(luò)利用率過(guò)高的話通過(guò)網(wǎng)線傳輸時(shí)會(huì)出現(xiàn)嚴(yán)重丟包的情況),不需要使用 multiSend 方法加大數(shù)據(jù)量。

在 singleMain 方法中,不直接執(zhí)行 dataSender.serve(),而是在新進(jìn)程中執(zhí)行,可以更好的利用多核優(yōu)勢(shì),發(fā)送數(shù)據(jù)更快:

# singleMain()dataSender = DataSender()    # dataSender.serve()p = Process(target=dataSender.serve)    p.start()

實(shí)際開(kāi)發(fā)過(guò)程并不是這么順利,因?yàn)橐婚_(kāi)始并不知道在大量數(shù)據(jù)發(fā)送的時(shí)候,發(fā)送端能否有效地將數(shù)據(jù)發(fā)送出去,實(shí)際上是邊編寫(xiě) Python 的模擬發(fā)送數(shù)據(jù)程序,邊編寫(xiě) Qt 獲取數(shù)據(jù)的程序,根據(jù)出現(xiàn)的問(wèn)題逐步解決發(fā)送端和接收端的問(wèn)題的。

編寫(xiě) Qt 獲取數(shù)據(jù)包的代碼及簡(jiǎn)單的 GUI

Qt 這邊作為客戶端,只需要將接收到的數(shù)據(jù)包保存下來(lái),獲取其中的有效數(shù)據(jù),再將 RGB 數(shù)據(jù)賦到 QImage 對(duì)應(yīng)的像素上顯示出來(lái)即可。GUI 部分比較簡(jiǎn)單,使用 QWidget 中的 label 控件,將 QImage 轉(zhuǎn)換成 QPixmap,顯示到 label 上就好了。初始化后的窗口如圖:

比較麻煩的是接收數(shù)據(jù)和拼接。同樣地,為了方便表示和解析每個(gè) UDP 包,我們構(gòu)造一些類(lèi)來(lái)存儲(chǔ)這些信息(現(xiàn)在想想似乎直接用結(jié)構(gòu)體表示會(huì)更簡(jiǎn)單)。

定義數(shù)據(jù)實(shí)體

我們?cè)?Qt 中定義 CameraData 類(lèi)來(lái)表示數(shù)據(jù)包實(shí)體:

/** * @brief The CameraData class * 對(duì)應(yīng)從下位機(jī)接收到的字節(jié)數(shù)組的類(lèi),原始數(shù)據(jù)包,需要經(jīng)過(guò)處理后變成一行數(shù)據(jù) */class CameraData : public DataObj{    Q_OBJECTpublic:    enum RGBType {        R = 1,        G = 2,        B = 3,        UNKOWN = 0};    static const QByteArray DATA_START_MAGIC;    static const QByteArray DATA_END_MAGIC;    static const int PacketSize;    explicit CameraData(QObject *parent = 0);    ~CameraData();    bool isPackageValid();    // 獲取保留區(qū)域的數(shù)據(jù)QByteArray getReserved();    // 設(shè)置原始數(shù)據(jù)void setRawData(const QByteArray &value);    void setRawData(const char *data);    // 獲取數(shù)據(jù)區(qū)域內(nèi)的所有數(shù)據(jù),默認(rèn)獲取有效數(shù)據(jù)QByteArray getData(bool valid = true);    int getPackageCntInLine();    int getPackageIdxInLine();    int getSampleDiffLine();    int getRGBExtern();    RGBType getRGBType();    int getLineIdx();    int getValidDataLen();    int getLineBytes();    int sliceToInt(int start, int len = 4);    // DataObj interfacevoid reset();signals:public slots:private:    inline QByteArray slice(int start, int len = -1);    inline QByteArray getStartMagic();    inline QByteArray getEndMagic();    QByteArray data;    int packageCntInLine = -1;    int packegeIdxInLine = -1;    int lineIdx = -1;    int lineBytes = -1;    int rgbType = -1;};

CameraData 類(lèi)繼承自 DataObj 類(lèi),而 DataObj 類(lèi)又繼承自 QObject,這樣方便進(jìn)行內(nèi)存管理和對(duì)象上的操作。DataObj 是為了方便復(fù)用對(duì)象而定義的基類(lèi),詳細(xì)代碼可參考 github 上的完整代碼。

C++ 部分的 CameraData 類(lèi)與 Python 中定義的 CameraData 類(lèi)是對(duì)應(yīng)的,不過(guò) C++ 部分的 CameraData 類(lèi)只需要調(diào)用 CameraData::setRawData 傳入一個(gè) QByteArray 對(duì)象后就可以自動(dòng)將其中包含的數(shù)據(jù)解析出來(lái),并且它只提供獲取數(shù)據(jù)的接口而不提供修改數(shù)據(jù)的接口。

另外我們還需要定義一個(gè)類(lèi) PreProcessData,來(lái)表示一行數(shù)據(jù):

/** * @brief The PreProcessData class * 預(yù)處理數(shù)據(jù) */class PreProcessData: public DataObj{    Q_OBJECTpublic:    static const int PacketSize;    static const int PacketPerLine;    explicit PreProcessData(QObject *parent = 0, int line = -1);    void put(CameraData *cd);    bool isReady();    void reset();    int line() const;    void setLine(int line);    const QByteArrayList &getDataList() const;    QByteArray repr();private:    /**     * @brief cameraData     * 每 2 個(gè) CameraData 構(gòu)成一行的單通道數(shù)據(jù),有序存放 RGB 通道數(shù)據(jù)     * 0-1 存放 R,2-3 存放 G, 4-5 存放 B     */QByteArrayList dataList;    int m_line;    int m_readyCount = 0;    int m_duplicateCount = 0;    bool *dataPlaced = 0;};

目前的協(xié)議中,每 2 個(gè)數(shù)據(jù)包(對(duì)應(yīng) 2 個(gè) CameraData 對(duì)象)構(gòu)成某一行的單通道數(shù)據(jù),所以 PreProcessData 中至少會(huì)包含 6 個(gè) CameraData 對(duì)象,處理完 CameraData 對(duì)象后,只需要存儲(chǔ) Data 部分即可,所以這里沒(méi)有用 QList 列表,而是直接使用 QByteArrayList 來(lái)存儲(chǔ)數(shù)據(jù)。當(dāng)三個(gè)通道的數(shù)據(jù)都準(zhǔn)備好后,PreProcessData::isReady 就會(huì)返回 true,表示該行數(shù)據(jù)已經(jīng)準(zhǔn)備好,可以顯示在窗口中。

在子線程中執(zhí)行接收 UDP 包和處理過(guò)程

我們定義一個(gè) Controller 類(lèi)用來(lái)操作數(shù)據(jù)接收對(duì)象和子線程。用 Qt 的事件槽機(jī)制和 QObject::moveToThread 實(shí)現(xiàn)多線程非常方便,不重寫(xiě) QThread 的 run 方法就可以讓對(duì)象的方法在子線程中執(zhí)行。

class Controller : public QObject{    Q_OBJECTpublic:    explicit Controller(QObject *parent = 0);    ~Controller();    static const int DataPort;    static const int CONTROL_PORT;    static const QStringList BOARD_IP;    void start();    void stop();    DataProcessor *getDataProcessor() const;signals:public slots:private:    CameraDataReceiver *cdr;    QThread recvThread;    QThread recvProcessThread;    QByteArrayList rawdataList;    DataProcessor *dp = 0;    QTimer *statsTimer;    int statsInterval;};

其中 CameraDataReceiver 對(duì)象會(huì)被實(shí)例化,在子線程中接收 UDP 數(shù)據(jù)包(因?yàn)榘l(fā)送和接收數(shù)據(jù)的端口是不同的,操作和數(shù)據(jù)是分離的)。這里將 DataProcessor 通過(guò) getDataProcessor 暴露給上層應(yīng)用,以便上層應(yīng)用連接信號(hào)槽接收?qǐng)D像。僅到接收數(shù)據(jù),就用到了三個(gè)線程:分別是 GUI 線程,用于接收 UDP 包的 recvThread 線程和處理 UDP 的 recvProcessThread。

為什么接收 UDP 包和處理 UDP 包不是放在一個(gè)線程中執(zhí)行呢?因?yàn)檫@里的數(shù)據(jù)量實(shí)在太多,最開(kāi)始實(shí)現(xiàn)的時(shí)候這兩個(gè)邏輯代碼確實(shí)是在同一個(gè)線程中執(zhí)行,然而由于處理數(shù)據(jù)的代碼執(zhí)行起來(lái)也要消耗時(shí)間,將會(huì)導(dǎo)致無(wú)法接收其他的 UDP 包,這樣的話就會(huì)導(dǎo)致比較嚴(yán)重的丟包。為了保證接收端不會(huì)丟包,只好將處理邏輯放在其他的線程中執(zhí)行。

Qt 接收 UDP 包

將接收數(shù)據(jù)和處理數(shù)據(jù)放在不同的線程中執(zhí)行,確實(shí)可以解決丟包問(wèn)題了,但是會(huì)出現(xiàn)新的問(wèn)題:接收到的包如果不能夠及時(shí)處理完,并且釋放掉相應(yīng)的資源,那么可能會(huì)出現(xiàn)程序?qū)?shù)據(jù)緩存下來(lái)但無(wú)法處理,程序占用的內(nèi)存越來(lái)越大,導(dǎo)致程序運(yùn)行起來(lái)越來(lái)越慢。

在編寫(xiě)程序時(shí)誤以為是 Qt 的事件循環(huán)機(jī)制過(guò)慢導(dǎo)致程序處理不了那么多數(shù)據(jù)(實(shí)際上它的速度足夠處理這些數(shù)據(jù)),因此將程序中使用的 QUdpSocket 對(duì)象換成了 [Windows 平臺(tái)的 Socket 通信代碼][winsock demo],并將其改寫(xiě)成類(lèi)方便調(diào)用。實(shí)際上是在 QThread 子線程中無(wú)限循環(huán)地運(yùn)行 recvfrom(clientSocket, recvedData.data(), recvbuflen, 0, &fromaddr, &addrLen); 這樣的接收數(shù)據(jù)包函數(shù),跳過(guò)了 Qt 事件循環(huán)機(jī)制,然后當(dāng)接收到包之后再通過(guò)回調(diào)函數(shù)通知數(shù)據(jù)處理線程進(jìn)行處理。

但當(dāng)我寫(xiě)這篇博客,重新用正常的代碼進(jìn)行測(cè)試時(shí),發(fā)現(xiàn)即便使用 QUdpSocket::readyRead 信號(hào)來(lái)接收 UDP 數(shù)據(jù),只要數(shù)據(jù)處理進(jìn)程不堆積數(shù)據(jù),就不會(huì)出現(xiàn)占用內(nèi)存越來(lái)越多的情況。換句話說(shuō),不是 Qt 無(wú)法處理實(shí)時(shí)性的數(shù)據(jù),而是自己編寫(xiě)的代碼里面有問(wèn)題。

回想最開(kāi)始寫(xiě)的程序,在處理 QByteArray 表示的原始數(shù)據(jù)時(shí),會(huì)為每一個(gè)接收到的數(shù)據(jù)包分配地址,而且分配的地址位于堆中。而實(shí)際上在堆 heap 中分配回收內(nèi)存地址相較于在棧 stack 中是慢得多的。為每個(gè)到來(lái)的數(shù)據(jù)用 new 構(gòu)造一個(gè)新的 CameraData 對(duì)象,然后在處理完后將這個(gè) CameraData delete 掉其實(shí)是很慢的,如果你這樣做了,并且你在 CameraData 的析構(gòu)函數(shù)中加上 qDebug 語(yǔ)句打印 "CameraData is deleting...",你會(huì)發(fā)現(xiàn),當(dāng)發(fā)送方(我們的 Python 模擬發(fā)送程序)停止發(fā)送數(shù)據(jù)包后很長(zhǎng)一段時(shí)間內(nèi),Qt 程序在一直打印著 "CameraData is deleting"。

而我最開(kāi)始就是這么做的,所以發(fā)生了 Qt 程序隨著數(shù)據(jù)接收的變多,占用的內(nèi)存越來(lái)越大的情況。當(dāng)然,這不排除 qDebug 語(yǔ)句輸出到控制臺(tái)上也會(huì)占用很多時(shí)間。如果每秒鐘要調(diào)用上萬(wàn)次 qDebug() << "CameraData is deleting",那么建議你使用一個(gè)計(jì)數(shù)變量控制 qDebug 的調(diào)用次數(shù),因?yàn)檫@條語(yǔ)句的調(diào)用也會(huì)讓數(shù)據(jù)處理變得緩慢。

處理接收到的 UDP 包

為了讓接收端不丟包,需要快速的處理接收到的 UDP 包,并且在處理的代碼中不要調(diào)用耗時(shí)的函數(shù)或者 new 操作。為了避免重復(fù)調(diào)用 new 和 delete 操作符,我們需要構(gòu)建一個(gè)對(duì)象池,以便復(fù)用池中的對(duì)象,減少 new 操作。池的定義比較簡(jiǎn)單,封裝一個(gè) QList 容器類(lèi)就好了,為了簡(jiǎn)化和復(fù)用池的代碼,我用到了 c++ 的 template 特性,但是這個(gè) DataObjPool 中的容器只能是 DataObj 的子類(lèi):

template<class T>class DataObjPool{public:    virtual ~DataObjPool() {        qDeleteAll(pool);        numAvailable = 0;    }    T *getAvailable() {        if( numAvailable == 0 ) {            return 0;        }        for(int i = 0; i < pool.size(); i++) {            T *item = pool[i];            if(item->isValid()) {                item->setValid(false);                numAvailable -= 1;                return item;            }        }        return 0;    }    T *get(int id) {        return pool[id];    }    inline bool release(T *dobj) {        dobj->reset();        numAvailable += 1;        return true;    }    int releaseTimeout(int now, int timeout = 100) {        int releaseCount = 0;        for(int i = 0; i < pool.size(); i++) {            T *item = pool[i];            if(now > item->getGenerateMs() + timeout) {                item->reset();                numAvailable += 1;                releaseCount += 1;            }        }        return releaseCount;    }    void releaseAll() {        for(int i = 0; i < pool.size(); i++) {            T *item = pool[i];            if(item->isValid()) {                continue;            }            item->reset();            numAvailable += 1;        }    }    int getNumAvailable() const {        return numAvailable;    }    template<class T2>  operator DataObjPool<T2>();protected:    DataObjPool(int size = 100);private:    QList<T *> pool;    int numAvailable = 0;};class RawDataObjPool: public DataObjPool<CameraData>{public:    RawDataObjPool(int size = 100);};class LineDataPool : public DataObjPool<PreProcessData>{public:    LineDataPool(int size = 100);};

當(dāng)然你也可以直接編寫(xiě)兩個(gè)類(lèi) RawDataObjPool 和 LineDataPool,把池的操作分別復(fù)制到兩個(gè)類(lèi)中,使用模板特化的好處是改動(dòng)的時(shí)候不需要改動(dòng)兩個(gè)類(lèi)了。前面說(shuō)過(guò),DataObj 類(lèi)繼承自 QObject,就是為了簡(jiǎn)化在對(duì)象池中進(jìn)行的操作。DataObjPool 會(huì)在構(gòu)造時(shí)在內(nèi)存中預(yù)分配一定數(shù)量的對(duì)象,以 RawDataObjPool 為例,構(gòu)造時(shí)傳入 size 參數(shù),便會(huì)預(yù)先在內(nèi)存中創(chuàng)建 size 個(gè) CameraData,在程序運(yùn)行過(guò)程中,這些對(duì)象都會(huì)被我們這個(gè) Qt 程序循環(huán)利用,直到關(guān)閉程序才會(huì)釋放掉這些 CameraData(如果操作系統(tǒng)的內(nèi)存不足,過(guò)多的對(duì)象占用的內(nèi)存還是會(huì)被釋放)。

對(duì)象池的主要接口有兩個(gè):getAvailable 和 release 分別用于獲取可用的對(duì)象或釋放掉池中的對(duì)象,注意這里的釋放是讓對(duì)象池對(duì)該對(duì)象進(jìn)行標(biāo)記,以便重復(fù)使用,而不是釋放掉該對(duì)象占用的內(nèi)存空間或 delete 掉。當(dāng)對(duì)象池中無(wú)可用對(duì)象時(shí),可以根據(jù)需要釋放掉超時(shí)的對(duì)象或者釋放掉全部對(duì)象。

使用對(duì)象池減少 new 操作符的使用后,處理數(shù)據(jù)的子線程的速度明顯加快。正常情況下就可以看到如下的圖片:

這里數(shù)據(jù)顯示的部分還有待完善,因?yàn)榘l(fā)送端的發(fā)送數(shù)據(jù)大小不夠湊成一行,所以圖片的右側(cè)部分是空白的。

數(shù)據(jù)的復(fù)制

這里說(shuō)一下數(shù)據(jù)的復(fù)制,從 Socket 接口中傳上來(lái)的數(shù)據(jù),我們用 QByteArray 對(duì)象保存了底層的數(shù)據(jù),即便在 UDP 數(shù)據(jù)包中含有很多個(gè) \x00 這樣的數(shù)據(jù),QByteArray 也會(huì)正確識(shí)別出字符串的結(jié)束位置。

在設(shè)置 CameraData::setRawData(const QByteArray &value) 函數(shù)中,盡量避免手動(dòng)調(diào)用 memcpy(data.data(), value, value.size()); 這個(gè)底層 API,因?yàn)槟悴恢浪鼤?huì)將 QByteArray 對(duì)象 CameraData.data 中的 char * data() 指針指向哪個(gè)位置。

我在 CameraData.cpp 文件中將它注釋掉了,因?yàn)樵诔绦蜻\(yùn)行和調(diào)試時(shí)它給我?guī)?lái)了巨大的困惑:經(jīng)常出現(xiàn) invalid address specified to rtlvalidateheap 這種類(lèi)型的錯(cuò)誤。經(jīng)過(guò)很長(zhǎng)時(shí)間的排查后發(fā)現(xiàn)注釋掉這行代碼,程序就能一直穩(wěn)定運(yùn)行。

總結(jié)

  1. 在 c++ 程序中要使用大量可重用的對(duì)象時(shí),盡量避免頻繁地使用 new 操作符新建對(duì)象,使用對(duì)象池來(lái)獲取對(duì)象,這樣可以加快程序的運(yùn)行速度。

  2. Qt 的事件循環(huán)機(jī)制實(shí)際上運(yùn)行地足夠快,是可以處理實(shí)時(shí)性的數(shù)據(jù)的,在程序出現(xiàn)問(wèn)題時(shí),還是應(yīng)該多找找自己編寫(xiě)的代碼中的問(wèn)題。

  3. 對(duì)于 memcpy 這類(lèi)的底層 API,不熟悉的話盡量少用,否則出現(xiàn)問(wèn)題很難 debug。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Qt學(xué)習(xí) 之 Socket通信(世界上最簡(jiǎn)單的例子了)
PyQt/Qt中使用image
QTcpSocket 發(fā)送和接收數(shù)據(jù)的幾種方法
人人網(wǎng)中間層-系統(tǒng)架構(gòu)
前端通過(guò)ajax獲取base64轉(zhuǎn)blob下載PDF方法
云風(fēng)的BLOG: Protocol Buffers for C
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服