Qt编写图片及视频TCP/UDP网络传输
一、前言
很多年前就做过类似的项目,无非就是将本地的图片上传到服务器,就这么简单,其实用http的post上传比较简单容易,无需自定义协议,直接设置好二进制数据即可,而采用TCP或者UDP通信的话,必须自定义协议,因为不知道什么时候数据接收完了是完整的图片数据,可能同时在发送很多图片数据,而且还不能区分收到的图片是哪个客户端发来的,TCP长连接的话,还需要有心跳来检测连接,所以必须自定义一套协议来支撑通信,这套协议采用的是上海监管平台的通信协议格式,拓展性比较强,其中头部信息包括了类型+当前完整包的数据长度,这个类型就是通信协议的标识,这样下次来一个其他类型的比如楼宇对讲可以叫IDOOR,服务端根据这个标识就能知道采用何种解析算法来处理后面的数据,而当前完整包的数据长度可以用来处理收到的数据,只有该长度的数据才表示接收完成一个完整的图片数据,再去解码处理。当传输的图片到了一定速度的时候比如一秒钟传输20张图片,其实就相当于传输视频了,一般人的肉眼看到一秒钟20张图片基本上认识就是视频了。
TCP理论上是稳定的连接,不会丢包,也不会随便一个包插入到一个包的中间,肯定能保证一个数据包的完整性,TCP连接也分两种,一种是长连接,一旦连接了就一直通信,主要用在频繁通信的场景中比如实时上传,还有一种叫短连接,客户端发完数据或者服务端接收完数据就立即断开连接,主要用在不频繁的通信场景中比如报警上传,毕竟报警的情况在一天中很少发生,采用短连接为佳,可以省去很多系统的开销,Qt对TCP的通信也是封装的很好用,在一些小并发的就几个几十个连接的项目中,效率还是可以的,据说Qt5的QNetwork组件底层重新改写了,效率比Qt4更高一些,本人也没用去详细的查看对应的源码,只是听说。
Qt的网络通信类,我们平时常用的就是三个:QTcpSocket客户端类、QTcpServer服务端类、QUdpSocket通信类,为啥没有QUdpServer类?其实UDP是无连接的通信,占用资源很小,他既可以是客户端也可以是服务端,如果要作为服务端则指定端口调用bind方法即可。本程序同时支持了TCP模式和UDP模式,实际测试下来,还是建议使用TCP模式,UDP模式由于无连接在短时间内发送大量的数据包发现会丢包,而且包的大小有限制,是65507字节,大约64K,所以UDP模式下实时传输的图片分辨率不能太大,实测640*480的视频文件还是挺好的,720P基本上有点惨,丢包好多,可能后期还需要从协议上改进处理。
本程序和协议约定的图片采用base64编码传输,接收到以后将base64字符串解码出来生成图片,QByteArray内置类toBase64方法转成base64编码的字符串,QByteArray::fromBase64方法将base64字符串还原成数据。在经过多次的实验以后统计的数据显示,编码解码的速度还可以,其中720P图片编码25ms-30ms、解码15ms-20ms,1080P图片编码35ms-40ms、解码25ms-30ms。总体上来说一秒钟传输25-30张图片和解码25-30张图片,还是没有什么问题的,只是走的CPU编码解码,如果开的通道数比较多的话,还是很耗CPU的,但是应付一些简单的应用场景还是如鱼得水毫无压力。
体验地址:https://pan.baidu.com/s/1bbL2ZughZAgfIGrexyN-9g 提取码:zkeh 文件名:bin_video_image.zip。
二、功能特点
- 多线程收发图片数据和解析图片数据,不卡主界面。
- 同时支持TCP和UDP两种模式,封装了TCP模式以及UDP模式的客户端类和服务端类。
- 图片传输客户端同时支持发送到多个服务端,可以作为一个教师机同屏发送到多个学生机的应用场景。
- 同时支持多个客户端同时往服务端发送图片,服务端每个连接都会自动开辟线程收发和解析图片数据。
- 自定义label控件信号槽机制绘制图片,不卡主界面。
- 自带心跳机制判断离线,自动重连服务器,可设置超时时间。
- 每个消息都有唯一的消息标识uuid,服务端收到以后会返回对应的uuid消息表示收到,客户端可以根据此返回消息判断服务端解析成功,不用再发,这样可以确保发出去的数据服务器接收到了并解析成功。
- 每个消息都有唯一的图片标识flag,相当于ID号,根据此标识判断需要解析显示到哪个界面。
- 图片以base64的字符串格式发送,接收端接收到base64字符串的图片数据解码后重新生成图片。
- 所有数据的收发都有信号发出去,方便输出查看。
- 都提供单例类,方便只有一个的时候直接使用无需new。
- 采用自定义的xml协议,可以自由拓展其他属性字段比如带上图片内容等。
三、通信协议
- 采用TCP长连接和UDP协议可选,默认通信端口6000。
- 采用自定义的xml通信协议。
- 所有传输加20个字节头部:IIMAGE:0000000000000,IIMAGE:为固定头部,后面接13个字节的 内容的长度(含20个头部长度) 字符串。
- 下面协议部分省略了头部字节。
- 服务端返回的数据中的uuid是对应接收到的消息的uuid。
- 服务端每次返回的时候都带了当前时间,可用于客户端校时。
客户端发送心跳
<?xml version="1.0" encoding="UTF-8"?>
<ImageClient Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" Flag="SHJC00000001">
<ClientHeart/>
</ImageClient>
服务器收到心跳返回
<?xml version="1.0" encoding="UTF-8"?>
<ImageServer Uuid="8AF12208-0356-434C-8A49-69A2170D9B5A" NowTime="2019-12-05 16:37:47">
Ok
</ImageServer>
客户端发送图片
<?xml version="1.0" encoding="UTF-8"?>
<ImageClient Uuid="66BCB44A-B567-48ED-8889-36B8FA6C4363" Flag="SHJC00000001">
<ClientImage>图片base64编码后的字符串/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAJAAtADASIAAhEBAxEB/8QAHwAAAQUBAQEB...nvWsQRlXA61mTjmtWcdazLgcmrQ0U2plSMKjpDE7UtFFAwxRRRQAUuKWigQlFFFLcD//2Q==</ClientImage>
</ImageClient>
服务端收到图片返回
<?xml version="1.0" encoding="UTF-8"?>
<ImageServer Uuid="66BCB44A-B567-48ED-8889-36B8FA6C4363" NowTime="2019-12-05 16:38:47">
Ack
</ImageServer>
四、效果图
五、核心代码
//图片转base64字符串
QByteArray DeviceFun::getImageData2(const QImage &image)
{
QByteArray imageData;
QBuffer buffer(&imageData);
image.save(&buffer, "jpg");
imageData = imageData.toBase64();
return imageData;
}
//base64字符串转图片
QImage DeviceFun::getImage(const QString &data)
{
QByteArray imageData = QByteArray::fromBase64(data.toLatin1());
QImage image;
image.loadFromData(imageData);
return image;
}
//客户端线程发送图片
void TcpImageClient::run()
{
while(!stopped) {
//这里采用线程去处理,其实完全可以用定时器搞定,毕竟tcp的write是异步的,操作系统自动调度
//为了后期的拓展性,比如需要判断是否发送成功之类的,需要同步处理,所以改成的线程去处理
//图片数据转成base64编码的数据也需要时间的,主要的耗时在转码
//取出数据发送,这里需要加锁,避免正在插入数据
if (isOk && images.count() > 0) {
QMutexLocker locker(&mutexImage);
QImage image = images.takeFirst();
QString imageData = DeviceFun::getImageData(image);
emit readyWrite(imageData);
}
//要稍微休息下,否则CPU会被一直占用
msleep(1);
}
stopped = false;
}
//服务端线程解析图片
void TcpImageSocket::run()
{
while(!stopped) {
//这里采用线程去处理,其实完全可以用定时器搞定,毕竟tcp的write是异步的,操作系统自动调度
//为了后期的拓展性,比如需要判断是否发送成功之类的,需要同步处理,所以改成的线程去处理
//base64编码数据转图片数据也需要时间的,主要的耗时在转码
//取出数据发送,这里需要加锁,避免正在插入数据
if (imageFlags.count() > 0) {
QMutexLocker locker(&mutexImage);
QString imageFlag = imageFlags.takeFirst();
QString imageData = imageDatas.takeFirst();
QImage image = DeviceFun::getImage(imageData);
emit receiveImage(imageFlag, image);
}
//要稍微休息下,否则CPU会被一直占用
msleep(1);
}
stopped = false;
}
//客户端类使用方法
TcpImageClient *client = new TcpImageClient(this);
UdpImageClient *client = new UdpImageClient(this);
connect(widget, SIGNAL(receiveImage(QImage)), client, SLOT(append(QImage)));
client->setServerIP("127.0.0.1");
client->setServerPort(6000);
client->start();
//服务端类使用方法
TcpImageServer *tcpServer = new TcpImageServer(this);
UdpImageServer *tcpServer = new UdpImageServer(this);
connect(tcpServer, SIGNAL(receiveImage(QString, QImage)), this, SLOT(receiveImage(QString, QImage)));
Qt编写图片及视频TCP/UDP网络传输的更多相关文章
- Qt编写安防视频监控系统(界面很漂亮)
一.前言 视频监控系统在整个安防领域,已经做到了烂大街的程序,全国起码几百家公司做过类似的系统,当然这一方面的需求量也是非常旺盛的,各种定制化的需求越来越多,尤其是这几年借着人脸识别的东风,发展更加迅 ...
- Raknet是一个基于UDP网络传输协议的C++网络库(还有一些其它库,比如nanomsg,fastsocket等等)
Raknet是一个基于UDP网络传输协议的C++网络库,允许程序员在他们自己的程序中实现高效的网络传输服务.通常情况下用于游戏,但也可以用于其它项目. Raknet有以下好处: 高性能 在同一台计算机 ...
- TCP/UDP网络编程的基础知识与基本示例(windows和Linux)
一.TCP编程的一般步骤 服务器端: 1.创建一个socket,用函数socket() 2.绑定IP地址.端口等信息到socket上,用函数bind() 3.开启监听,用函数listen() 4.接收 ...
- Qt编写安防视频监控系统18-云台控制
一.前言 云台控制是视频监控系统中必备的一个功能,对球机进行上下左右的移动,还有焦距的控制,其实核心就是控制XYZ三个坐标轴,为了开发这个模块,特意研究了各种云台控制的方法和开源库比如soap,有些厂 ...
- 网络层、传输层、应用层、端口通信协议编程接口 - http,socket,tcp/ip 网络传输与通讯知识总结
引: http://coach.iteye.com/blog/2024511 什么是TCP和UDP,以及二者区别是什么? TCP的全称为传输控制协议.这种协议可以提供面向连接的.可靠的.点到点的通信. ...
- 自测之Lesson15:TCP&UDP网络编程
题目:编写一个TCP通信的程序. 实现代码: #include <stdio.h> #include <sys/socket.h> #include <unistd.h& ...
- Qt编写安防视频监控系统16-设备播放
一.前言 设备播放模块是后面增加的,核心就是通过组合rtsp视频流地址来播放实时视频和历史视频,目前市面上很多厂家比如排第一的海康都是支持直接rtsp通过NVR来播放某个通道视频流和回放某个通道的视频 ...
- Qt编写安防视频监控系统5-视频回放
一.前言 一般视频回放都会采用GB28181国标来处理,这样可以保证兼容国内各大厂家的NVR,毕竟在同一的国家标准下,大家都会统一支持国标的,就不需要根据各个厂家的SDK来做兼容处理,烦得很,厂家越来 ...
- Qt编写安防视频监控系统3-通道交换
一.前言 最开始写通道交换的功能的时候,走了很多弯路,比如最开始用最初级的办法,触发交换的时候,先关闭视频,然后设置新的url重新打开视频,这样处理非常低级而且耗内存还卡还很慢,毕竟重新打开视频都需要 ...
随机推荐
- FreeBSD设置开机同步时间
没有设置开机同步时间的话,重启之后时间不对. 如果装机时没正确设置时区,先设置时区:# tzsetup 用date命令手工设置时间一方面不方便,另一方面也依赖于本地管理员的时钟的正确性,那么网络上不同 ...
- Docker-compose(创建容器)
Docker-compose(创建容器) 本文原始地址:https://sitoi.cn/posts/23955.html 样例 version: "2" services: sp ...
- appium+python自动化63-使用Uiautomator2报错问题解决
前言 appium desktop V1.7.1版本使用命令行版本启动appium后,使用Uiautomator2定位toast信息报错:appium-uiautomator2-server-v0.3 ...
- es6 字符串模板拼接和传统字符串拼接
字符串拼接是在日常开发中必不可少的一个环节. 注意:字符串可以用单引号'',或者""双引号,出于方便大家理解,文章以下内容统一使用单引号''! 如果只是一个字符串和一个变量拼接,使 ...
- 案例实战之如何写一个webpack loader
通过以下几个实例掌握webpack loader的写法 1.写一个多语言替换的loader 在index.js在页面上插入了一个{{title}}文本,我们需要在打包的时候将其替换成对应的多语言 fu ...
- Centos7-ssh免密登录
生成密钥 ssh-keygen 拷贝密钥 ssh-copy-id #目的IP或域名 检查配置 cat /root/.ssh/authorized_keys 登录测试 ssh ip
- 如何使用keil5将stm32的hal库编译成lib文件——F1版本
hal库中keil5中编译的速度是比较慢的,相同情况下,每次都要编译的时候,比标准库是要慢很多的,因此就hal库编译成lib文件是一种加快编译速度的方法,当然也有其自身的缺点.一.步骤1.使用cube ...
- web自动化测试-selenium多表单切换
一.概述 1.在web应用中会经常遇到frame/iframe表单嵌套页面的应用 2.WebDriver只能在一个页面上对元素进行识别与定位 3.对于frame/iframe表单内嵌的页面上元素无法识 ...
- go 学习 (一):环境配置
Go 下载地址:https://golang.google.cn/dl/ 右键我的电脑 --> 左上方 “高级系统设置” ---> 环境变量 --> 第二个菜单栏 “系统变 ...
- Spring之IOC(控制反转)与AOP(面向切面编程)
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.可以认为IoC与JND ...