使用 Qt 获取 UDP 数据并显示成图片

一个项目,要接收 UDP 数据包,解析并获取其中的数据,主要根据解析出来的行号和序号将数据拼接起来,然后将拼接起来的数据(最重要的数据是 R、G、B 三个通道的像素值)显示在窗口中。考虑到每秒钟要接收的数据包的数量较大,Python 的处理速度可能没有那么快,而且之前对 Qt 也比较熟悉了,所以用Qt 作为客户端接收处理数据包,用近期学习的 Python 模拟发送数据包。

数据格式

在 TCP/IP 协议中,UDP 数据包的大小是由限制的,因此用 UDP 传输数据时,还要在 UDP 层上再封装一层自定义的协议。这个自定义的协议比较简单,每个 UDP 包的大小为 1432 个字节,分为几个部分:

部分 起始字节 字节长度 说明
Start 0 4 包头部的 Magic Number,设为 0x53746172
PartialCnt 4 1 分包总数,一个字节(0-255)以内
PartialIdx 5 1 分包序号
SampleLine 6 1 采样率
RGB 7 1 rgb 通道标识符
LineIdx 8 4 行号,每一行可以包含 RGB 三个通道的数据,每个通道由多个分包组成
ValidDataLen 12 4 数据部分有效字节数
LineBytes 16 4 每行数据包含的字节总数
Reserve 20 128 保留部分
Data 148 1280 数据部分
end 1428 4 包尾部的 Magic Number,设为 0x54456e64

上述表格描述的就是一个完整的 UDP 包。这里的一个 UDP 数据包包含的是 RGB 某个通道的某一部分的数据。换种说法:

  • 一行数据

    • R 通道数据(若干个分包组成)
    • G 通道数据(若干个分包组成)
    • B 通道数据(若干个分包组成)

所以要生成/解析 UDP 包,最重要的是 PartialCnt、PartialIdx、RGB、LineIdx、Data 这几个部分。清楚了自定义协议就可以开始编写模拟包的生成和相应的接收逻辑了。

使用 Python 模拟 UDP 发包

由于本地开发的时候缺少必要的硬件环境,为了方便开发,用 Python 编写一个简单的 UDPServer,发送模拟生成的数据包。根据上述协议,可以写出如下的 CameraData 类来表示 UDP 数据包:

# -*- coding: utf-8 -*-
DATA_START_MAGIC = bytearray(4)
DATA_START_MAGIC[0] = 0x53  # S
DATA_START_MAGIC[1] = 0x74  # t
DATA_START_MAGIC[2] = 0x61  # a
DATA_START_MAGIC[3] = 0x72  # r

DATA_END_MAGIC = bytearray(4)
DATA_END_MAGIC[0] = 0x54 # T
DATA_END_MAGIC[1] = 0x45 # E
DATA_END_MAGIC[2] = 0x6e # n
DATA_END_MAGIC[3] = 0x64 # d

slice_start_magic = slice(0, 4)
slice_partial_cnt =  4
slice_partial_idx =  5
slice_sample_line =  6
slice_rgb_extern =  7
slice_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 np
class CameraData(object):
    def __init__(self):
        # self.new()
        # self.rawdata = rawdata
        self.dataLow = 10
        self.dataHigh = 20
        self.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) >> 8
        self.rawdata[start+1] = (0x00ff0000 & line) >> 16
        self.rawdata[start+0] = (0xff000000 & line) >> 24

    def setValidDataLen(self, len):
        start = slice_valid_data_len.start
        self.rawdata[start+3] = 0x000000ff & len
        self.rawdata[start+2] = (0x0000ff00 & len) >> 8
        self.rawdata[start+1] = (0x00ff0000 & len) >> 16
        self.rawdata[start+0] = (0xff000000 & len) >> 24

    def setLineBytes(self, len):
        start = slice_line_bytes.start
        self.rawdata[start+3] = 0x000000ff & len
        self.rawdata[start+2] = (0x0000ff00 & len) >> 8
        self.rawdata[start+1] = (0x00ff0000 & len) >> 16
        self.rawdata[start+0] = (0xff000000 & len) >> 24

    def 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):
        """构造新的数据对象
        """
        self.rawdata = bytearray(1432)
        self.rawdata[slice_start_magic] = DATA_START_MAGIC
        self.rawdata[slice_partial_cnt] = 0x02
        self.rawdata[slice_partial_idx] = 0x00
        self.rawdata[slice_sample_line] = 0x03
        self.rawdata[slice_rgb_extern] = 0x01

        self.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 '<[email protected]{} hex len: {}>'.format(hex(id(self)), len(self.rawdata))

CameraData 中的 rawdata 是一个 bytearray 对象,它将会被 UdpServer 通过网络接口发送出去。设置 4 个字节大小的整数时(如写 LineIdx 行号),不能直接将数值赋到 rawdata 中,要将其中的 4 个字节分别赋值到对应的地址上才行。

CameraData 中的 randomData 方法是模拟随机数据,更好的做法不是完全随机给每个像素点赋值,而是有规律的变化,这样在接收数据出现问题、分析问题的时候可以直观地看到哪里有问题。

然后我们需要定义一个 UdpServer,用它来将数据对象中包含的信息发送出去。

import socket
class UdpServer( object ):
    """该类功能是处理底层的 UDP 数据包发送和接收,利用队列缓存所有数据
    """

    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 block

    def send_msg( self, msg ):
        """发送消息,
        @param msg 字典对象,发送 msg 的 rawdata 字段
        """
        self._sock.sendto( msg.rawdata, ('192.168.8.1', DATA_PORT))

这个 UdpServer 非常简单,因为后续会通过这个 UdpServer 不停的发包,但是每次发包必须等待发送端成功将 UDP 包发送出去,这里不要将 socket 对象设置成非阻塞的,否则程序运行时会出现错误提示(尽管可以忽略掉这个错误提示,但是没必要设置成非阻塞的,阻塞模式完全足够了)。

github 中可以找到完整的 Python 文件,里面定义了其他类,如 DataSenderRGBSenderDataSender 是在一个线程里面发送 RGB 三个通道的值,RGBSender 的一个对象只会发送 RGB 三个通道中的某一个的值。

小结和注意事项

在本地测试的时候,为了方便在任务管理器中看到网络占用率,最初是在 VirtualBox 的 ubuntu 虚拟机上运行这个 Python 程序的,但是受到虚拟机的资源分配和电脑性能影响,调用 singleMain 函数时每秒钟最多只能产生 50MB 的数据量。但是在本地非虚拟机环境运行的时候最多可以达到 80MB 的数据量。所以尽可能地使用本地环境运行该 Python 程序可以最大限度的生成数据包。

如果让 RGB 三个通道分别在三个不同的进程中执行发送过程(注释掉 singleMain 的调用,换用 multiSend 方法),那么每秒钟的数据量可到 200MB,不过 80MB 的数据量已经足够多了(接近千兆网卡的上限了,网络利用率过高的话通过网线传输时会出现严重丢包的情况),不需要使用 multiSend 方法加大数据量。

在 singleMain 方法中,不直接执行 dataSender.serve(),而是在新进程中执行,可以更好的利用多核优势,发送数据更快:

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

实际开发过程并不是这么顺利,因为一开始并不知道在大量数据发送的时候,发送端能否有效地将数据发送出去,实际上是边编写 Python 的模拟发送数据程序,边编写 Qt 获取数据的程序,根据出现的问题逐步解决发送端和接收端的问题的。

编写 Qt 获取数据包的代码及简单的 GUI

Qt 这边作为客户端,只需要将接收到的数据包保存下来,获取其中的有效数据,再将 RGB 数据赋到 QImage 对应的像素上显示出来即可。GUI 部分比较简单,使用 QWidget 中的 label 控件,将 QImage 转换成 QPixmap,显示到 label 上就好了。初始化后的窗口如图:

比较麻烦的是接收数据和拼接。同样地,为了方便表示和解析每个 UDP 包,我们构造一些类来存储这些信息(现在想想似乎直接用结构体表示会更简单)。

定义数据实体

我们在 Qt 中定义 CameraData 类来表示数据包实体:

/**
 * @brief The CameraData class
 * 对应从下位机接收到的字节数组的类,原始数据包,需要经过处理后变成一行数据
 */
class CameraData : public DataObj
{
    Q_OBJECT
public:
    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();
    // 获取保留区域的数据
    QByteArray getReserved();
    // 设置原始数据
    void setRawData(const QByteArray &value);
    void setRawData(const char *data);
    // 获取数据区域内的所有数据,默认获取有效数据
    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 interface
    void 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 类继承自 DataObj 类,而 DataObj 类又继承自 QObject,这样方便进行内存管理和对象上的操作。DataObj 是为了方便复用对象而定义的基类,详细代码可参考 github 上的完整代码

C++ 部分的 CameraData 类与 Python 中定义的 CameraData 类是对应的,不过 C++ 部分的 CameraData 类只需要调用 CameraData::setRawData 传入一个 QByteArray 对象后就可以自动将其中包含的数据解析出来,并且它只提供获取数据的接口而不提供修改数据的接口。

另外我们还需要定义一个类 PreProcessData,来表示一行数据:

/**
 * @brief The PreProcessData class
 * 预处理数据
 */
class PreProcessData: public DataObj
{
    Q_OBJECT

public:
    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 个 CameraData 构成一行的单通道数据,有序存放 RGB 通道数据
     * 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;
};

目前的协议中,每 2 个数据包(对应 2 个 CameraData 对象)构成某一行的单通道数据,所以 PreProcessData 中至少会包含 6 个 CameraData 对象,处理完 CameraData 对象后,只需要存储 Data 部分即可,所以这里没有用 QList 列表,而是直接使用 QByteArrayList 来存储数据。当三个通道的数据都准备好后,PreProcessData::isReady 就会返回 true,表示该行数据已经准备好,可以显示在窗口中。

在子线程中执行接收 UDP 包和处理过程

我们定义一个 Controller 类用来操作数据接收对象和子线程。用 Qt 的事件槽机制和 QObject::moveToThread 实现多线程非常方便,不重写 QThread 的 run 方法就可以让对象的方法在子线程中执行。

class Controller : public QObject
{
    Q_OBJECT
public:
    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 对象会被实例化,在子线程中接收 UDP 数据包(因为发送和接收数据的端口是不同的,操作和数据是分离的)。这里将 DataProcessor 通过 getDataProcessor 暴露给上层应用,以便上层应用连接信号槽接收图像。仅到接收数据,就用到了三个线程:分别是 GUI 线程,用于接收 UDP 包的 recvThread 线程和处理 UDP 的 recvProcessThread。

为什么接收 UDP 包和处理 UDP 包不是放在一个线程中执行呢?因为这里的数据量实在太多,最开始实现的时候这两个逻辑代码确实是在同一个线程中执行,然而由于处理数据的代码执行起来也要消耗时间,将会导致无法接收其他的 UDP 包,这样的话就会导致比较严重的丢包。为了保证接收端不会丢包,只好将处理逻辑放在其他的线程中执行。

Qt 接收 UDP 包

将接收数据和处理数据放在不同的线程中执行,确实可以解决丢包问题了,但是会出现新的问题:接收到的包如果不能够及时处理完,并且释放掉相应的资源,那么可能会出现程序将数据缓存下来但无法处理,程序占用的内存越来越大,导致程序运行起来越来越慢。

在编写程序时误以为是 Qt 的事件循环机制过慢导致程序处理不了那么多数据(实际上它的速度足够处理这些数据),因此将程序中使用的 QUdpSocket 对象换成了 [Windows 平台的 Socket 通信代码][winsock demo],并将其改写成类方便调用。实际上是在 QThread 子线程中无限循环地运行 recvfrom(clientSocket, recvedData.data(), recvbuflen, 0, &fromaddr, &addrLen); 这样的接收数据包函数,跳过了 Qt 事件循环机制,然后当接收到包之后再通过回调函数通知数据处理线程进行处理。

但当我写这篇博客,重新用正常的代码进行测试时,发现即便使用 QUdpSocket::readyRead 信号来接收 UDP 数据,只要数据处理进程不堆积数据,就不会出现占用内存越来越多的情况。换句话说,不是 Qt 无法处理实时性的数据,而是自己编写的代码里面有问题。

回想最开始写的程序,在处理 QByteArray 表示的原始数据时,会为每一个接收到的数据包分配地址,而且分配的地址位于堆中。而实际上在堆 heap 中分配回收内存地址相较于在栈 stack 中是慢得多的。为每个到来的数据用 new 构造一个新的 CameraData 对象,然后在处理完后将这个 CameraData delete 掉其实是很慢的,如果你这样做了,并且你在 CameraData 的析构函数中加上 qDebug 语句打印 "CameraData is deleting...",你会发现,当发送方(我们的 Python 模拟发送程序)停止发送数据包后很长一段时间内,Qt 程序在一直打印着 "CameraData is deleting"。

而我最开始就是这么做的,所以发生了 Qt 程序随着数据接收的变多,占用的内存越来越大的情况。当然,这不排除 qDebug 语句输出到控制台上也会占用很多时间。如果每秒钟要调用上万次 qDebug() << "CameraData is deleting",那么建议你使用一个计数变量控制 qDebug 的调用次数,因为这条语句的调用也会让数据处理变得缓慢。

处理接收到的 UDP 包

为了让接收端不丢包,需要快速的处理接收到的 UDP 包,并且在处理的代码中不要调用耗时的函数或者 new 操作。为了避免重复调用 new 和 delete 操作符,我们需要构建一个对象池,以便复用池中的对象,减少 new 操作。池的定义比较简单,封装一个 QList 容器类就好了,为了简化和复用池的代码,我用到了 c++ 的 template 特性,但是这个 DataObjPool 中的容器只能是 DataObj 的子类:

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);
};

当然你也可以直接编写两个类 RawDataObjPoolLineDataPool,把池的操作分别复制到两个类中,使用模板特化的好处是改动的时候不需要改动两个类了。前面说过,DataObj 类继承自 QObject,就是为了简化在对象池中进行的操作。DataObjPool 会在构造时在内存中预分配一定数量的对象,以 RawDataObjPool 为例,构造时传入 size 参数,便会预先在内存中创建 size 个 CameraData,在程序运行过程中,这些对象都会被我们这个 Qt 程序循环利用,直到关闭程序才会释放掉这些 CameraData(如果操作系统的内存不足,过多的对象占用的内存还是会被释放)。

对象池的主要接口有两个:getAvailablerelease 分别用于获取可用的对象或释放掉池中的对象,注意这里的释放是让对象池对该对象进行标记,以便重复使用,而不是释放掉该对象占用的内存空间或 delete 掉。当对象池中无可用对象时,可以根据需要释放掉超时的对象或者释放掉全部对象。

使用对象池减少 new 操作符的使用后,处理数据的子线程的速度明显加快。正常情况下就可以看到如下的图片:

这里数据显示的部分还有待完善,因为发送端的发送数据大小不够凑成一行,所以图片的右侧部分是空白的。

数据的复制

这里说一下数据的复制,从 Socket 接口中传上来的数据,我们用 QByteArray 对象保存了底层的数据,即便在 UDP 数据包中含有很多个 \x00 这样的数据,QByteArray 也会正确识别出字符串的结束位置。

在设置 CameraData::setRawData(const QByteArray &value) 函数中,尽量避免手动调用 memcpy(data.data(), value, value.size()); 这个底层 API,因为你不知道它会将 QByteArray 对象 CameraData.data 中的 char * data() 指针指向哪个位置。

我在 CameraData.cpp 文件中将它注释掉了,因为在程序运行和调试时它给我带来了巨大的困惑:经常出现 invalid address specified to rtlvalidateheap 这种类型的错误。经过很长时间的排查后发现注释掉这行代码,程序就能一直稳定运行。

总结

  1. 在 c++ 程序中要使用大量可重用的对象时,尽量避免频繁地使用 new 操作符新建对象,使用对象池来获取对象,这样可以加快程序的运行速度。
  2. Qt 的事件循环机制实际上运行地足够快,是可以处理实时性的数据的,在程序出现问题时,还是应该多找找自己编写的代码中的问题。
  3. 对于 memcpy 这类的底层 API,不熟悉的话尽量少用,否则出现问题很难 debug。


完整的项目代码可以在 github 中找到。

参考

原文地址:https://www.cnblogs.com/brifuture/p/10321707.html

时间: 2024-08-14 00:18:45

使用 Qt 获取 UDP 数据并显示成图片的相关文章

android 从服务器获取新闻数据并显示在客户端

新闻客户端案例 第一次进入新闻客户端需要请求服务器获取新闻数据,做listview的展示, 为了第二次再次打开新闻客户端时能快速显示新闻,需要将数据缓存到数据库中,下次打开可以直接去数据库中获取新闻直接做展示. 总体步骤: 1.写布局listview ok 2.找到listview,设置条目的点击事件. ok 3.获取数据提供给listview做展示. 3.1:获取本地数据库缓存的新闻数据,让listview显示.如果没有网络不至于显示空界面. 3.2:请求服务器获取新闻数据,是一个json字符

kinect获取深度数据并显示

在上述深度帧获取的基础上,利用unity的Mesh组件,将深度帧显示出来. 工具为Unity5.6.Kinect开发包KinectForWindows_UnityPro_2.0.1410 首先讲一个Mesh的应用 Mesh有多种方式实现,这里只用最简单的,通过设定顶点组成三角形集合的方式,主要工作是设定三个属性: ①  vertices,顶点集合,Vector3类型,一般为所要显示的像素坐标集合,这里为深度帧每个像素的坐标值,其中z为深度值. 注意:Unity中顶点数量不能超过65000个. ②

创建一个Java Web项目,获取POST数据并显示

新建一个新的Java Web工程项目 打开IntelliJ IDEA 新建一个工程,选择选择Java Enterprise,设置Tomcat的安装目录,点击下一步. 选中Create project from template,点击下一步: 创建工程成功,可以看到目录结构是如下图一样的: 第一个程序,HelloWorld程序 JSP代码要使用<%%>包起来,因此HelloWorld输出到网页上是这么写的: 变量的使用 Java Web中的变量是这么声明和使用的. 构建一个登录页面 接受表单参数

从数据库读取数据后显示成html标签

也许很多人从数据库中读的数据是不需要数据成html标签的,但是也许有一天你们会发现当我们需要输出成html标签时编译器却自动帮我们输出成字符串了这是我们可以这样来 方法1: 最常用的方法,使用JS或JQ JQ: 1 $("#div").html("<span>我是HTML代码</span>"); JS: 1 var dobj=document.getElementById("div"); 2 dobj.innerHTML

Unity3D中C#获取游戏时间并显示成秒表格式

using UnityEngine; using System.Collections; using UnityEngine.UI; public class Timer : MonoBehaviour { int hour; int minute; int second; int millisecond; // 已经花费的时间 float timeSpend = 0.0f; // 显示时间区域的文本 Text text_timeSpend; // Use this for initializa

Qt之界面数据存储与获取(使用setUserData()和userData())

在GUI开发中,往往需要在界面中存储一些有用的数据,这些数据可以来配置文件.注册表.数据库.或者是server. 无论来自哪里,这些数据对于用户来说都是至关重要的,它们在交互过程中大部分都会被用到,例如:单击一个用户头像,显示该用户的详细信息(等级.昵称.姓名.个人说明). 简述 常见接口 数据源 setData和data 单独存储 整体存储 setItemData和itemData setUserData和userData 自定义数据 常见接口 Qt中,可以通过绝大部分已有的接口来存数数据.获

UDP(socket)接和数据案例封装成C++代码

 配置QT下的pro文件 TEMPLATE = app CONFIG += console CONFIG -= app_bundle CONFIG -= qt   LIBS += -lWs2_32   ##标示使用window下的Ws2_32.lib,-l表示要链接后面的库 #-lWs2_32,link Ws2_32.lib   SOURCES += main.cpp \     udp.cpp   HEADERS += \     udp.h 编写udp.h文件 #ifndef UDP_H

echart动态获取数据不显示

当时写完echart,然后当模拟数据动态显示的时候,把里面的data直接替换成获取回来的数据不显示,后来发现是当页面初始加载的时候就已经取数据了且只取一次,所以就用了一个watch监视,监视变化需要在重新渲染,但是发现一直在发送请求,所以后来就在获取数据之后再创建echart示例就好了呀,代码如下 drawLine() { getLine(this.lineQuery).then(response => { // 基于准备好的dom,初始化echarts实例,发送请求回来再创建实例 this.l

如何不显示地图就获取位置数据?

使用“同步加载插件的方式”,引用各类插件,就可以不创建地图,直接获取地图数据. 以下用IP定位做为例子,详细讲述“如何不显示地图就获取当前位置”. 引入城市定位插件,更多插件与使用方法请见插件类总览 <script type="text/javascript" src="http://webapi.amap.com/maps?v=1.3&key=您的Key&plugin=AMap.CitySearch"></script> 实