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 文件,里面定义了其他类,如 DataSender、RGBSender。DataSender 是在一个线程里面发送 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 获取数据包的代码及简单的 GUIQt 这边作为客户端,只需要将接收到的数据包保存下来,获取其中的有效数据,再将 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; };