通过前面几篇文章的分析小结
我们已经知道知识纯粹的将图片按质量压缩
使图片文件小于64KB以适应UDP发送是不可行的
当图片质量小于临界点后
质量的降低对图片大小的影响是很小的
要想在保证图片清晰度的情况下用UDP来发送图片
前文已经分析
我们把图片数据分包,用UDP广播分包后的消息
UDP的不可靠性,以及根据概率学知识,大家都知道分的包越多,越危险,我们需要尽量少分包
在接收方,用一个缓冲区来接收数据包。根本数据包内容中的标识来组装包,根据实时性要求,当接收消息超过3秒还未收到兄弟包,在丢弃。
来了
先来看今天实现的思路和代码
先看依据UDP传输需求的数据对象
package cn.javaeye.java_mzd.Monitor.Tools.message;
import cn.javaeye.java_mzd.Monitor.Tools.Constance;
/**
* 用来在UDP中传送的数据对象
* @author Administrator
*
*/
public class UDPImageMessage extends MessageHead {
private int imageCounts;// 标识总的第几个图片
private int totalFlags; // 当前图片由几个包组成
private int flag;// 标识当前数据是第几个包
private byte[] data;// 数据内容
public byte[] pack() throws Exception {
totallen = 13 + 4 + 4 + 4 + data.length;
type = Constance.MESSAGE_TYPE_UDPIMAGEMESSAGE;
// 状态等通过setter方法设置
// 总长
dataOutputStream.writeInt(totallen);
// 类型
dataOutputStream.writeByte(type);
// senderID
dataOutputStream.writeInt(senderID);
// reciverID
dataOutputStream.writeInt(reciverID);
dataOutputStream.writeInt(imageCounts);
dataOutputStream.writeInt(totalFlags);
dataOutputStream.writeInt(flag);
dataOutputStream.write(data);
dataOutputStream.flush();
data = byteArrayOutputStream.toByteArray();
return data;
}
}
再来看服务器端发送UDP数据包的代码
package cn.javaeye.java_mzd.Monitor.Server.New;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import cn.javaeye.java_mzd.Monitor.Server.Tradition.ServerTools;
import cn.javaeye.java_mzd.Monitor.Tools.Log;
import cn.javaeye.java_mzd.Monitor.Tools.message.UDPImageMessage;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class CastServerScreen implements Runnable {
int imageCounts;// 用来标识总的发送图片数序列
public CastServerScreen(int imageCounts){
this.imageCounts=imageCounts;
}
// 节约内存,把需要不断生成的下列对象设为属性
UDPImageMessage message = null;//分包时的UDPImageMessage对象
UDPImageMessage[] messages = null;//分包得到的UDPImageMessage数组对象
BufferedImage bufferedImage = null;//截屏得到的BufferedImage对象
DatagramPacket _datapacke = null; //用来发送的数据包对象
byte[] data=null;//用于打包图片的数组对象
@Override
public void run() {
castScreenImage();
}
/**
* 不断在设定好的组播地址中发送广播
* @param screenImageMessage
*/
public void castScreenImage() {
bufferedImage=ServerTools.shotScreen();//截屏
messages=pack(bufferedImageTobytes(bufferedImage, 0.5f));//将截屏图像按0.5的质量压缩,并且分包
Log.recordTime(imageCounts+"----开始发送--");
try {
java.net.InetAddress castIP = InetAddress.getByName("225.0.0.1");// 设置组播IP
int port = 9999;// 设置组端口
// 开始发送分包后的UDPImageMessage数组
for(int i=0;i<messages.length;i++){
data=messages[i].pack();
_datapacke = new DatagramPacket(data, data.length, castIP, port);
MulticastSocket castSocket = new MulticastSocket();
castSocket.send(_datapacke);
}
} catch (Exception e) {
e.printStackTrace();
}
Log.recordTime(imageCounts+"----完成发送--");
}
/**
* 将字节数组分包成适合UDP发送的UDPImageMessage数组
* @param data 需要打包的byte[]数组
* @return 打包后得到的UDPImageMessage数组
*/
private UDPImageMessage[] pack(byte[] data) {
Log.recordTime(imageCounts+"----开始分包--");
int t = data.length;
int buffer = 65000;// 窗口大小,
byte[] packdata = null;
int totalFlags = (t / buffer) + 1;// 分块数组大小
UDPImageMessage[] messages = new UDPImageMessage[totalFlags];
int flag = 0;
while (t > 0) {
if (t > buffer) {
packdata = new byte[buffer];
for (int i = 0; i < packdata.length; i++) {
packdata[i] = data[i + flag * buffer];
}
message = new UDPImageMessage();
message.setImageCounts(imageCounts);
message.setTotalFlags(totalFlags);
message.setFlag(flag);
message.setData(packdata);
messages[flag] = message;
t = t - buffer;
flag++;
}
if (t < buffer) {
packdata = new byte[t];
for (int i = 0; i < packdata.length; i++) {
packdata[i] = data[i + flag * buffer];
}
message = new UDPImageMessage();
message.setImageCounts(imageCounts);
message.setTotalFlags(totalFlags);
message.setFlag(flag);
message.setData(packdata);
messages[flag] = message;
t = 0;
flag++;
}
}
Log.recordTime(imageCounts+"----完成分包--");
return messages;
}
/**
* 通过 com.sun.image.codec.jpeg.JPEGCodec提供的编码器来实现图像压缩
* @param image 截屏得到的 BufferedImage
* @param quality 压缩质量
* @return 压缩后得到字节数组
*/
private byte[] bufferedImageTobytes(BufferedImage image, float quality) {
Log.recordTime(imageCounts+"----开始压缩--");
// 如果图片空,返回空
if (image == null) {
return null;
}
// 开始开始,写入byte[]
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 取得内存输出流
// 设置压缩参数
JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);
param.setQuality(quality, false);
// 设置编码器
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(
byteArrayOutputStream, param);
try {
encoder.encode(image);
} catch (Exception ef) {
ef.printStackTrace();
}
Log.recordTime(imageCounts+"----结束压缩--");
return byteArrayOutputStream.toByteArray();
}
}
通过以上代码,实现了将一个图片按质量压缩,并且将图片信息的字节数组分包,以便用UDP发送
现在来测试下看看效果
0----开始压缩--16点57分11秒536 0----结束压缩--16点57分11秒588 0----开始分包--16点57分11秒588 0----完成分包--16点57分11秒596 0----开始发送--16点57分11秒596 1----开始压缩--16点57分11秒746 1----结束压缩--16点57分11秒796 1----开始分包--16点57分11秒798 1----完成分包--16点57分11秒798 1----开始发送--16点57分11秒798 2----开始压缩--16点57分11秒928 2----结束压缩--16点57分11秒978 2----开始分包--16点57分11秒978 2----完成分包--16点57分11秒981 2----开始发送--16点57分11秒981 3----开始压缩--16点57分12秒153 3----结束压缩--16点57分12秒208 3----开始分包--16点57分12秒208 3----完成分包--16点57分12秒208 3----开始发送--16点57分12秒208 4----开始压缩--16点57分12秒306 4----结束压缩--16点57分12秒356 4----开始分包--16点57分12秒356 4----完成分包--16点57分12秒356 4----开始发送--16点57分12秒356 0----完成发送--16点57分15秒136 5----开始压缩--16点57分15秒217 5----结束压缩--16点57分15秒267 5----开始分包--16点57分15秒267 5----完成分包--16点57分15秒267 5----开始发送--16点57分15秒267 1----完成发送--16点57分18秒224 |
截屏、打包压缩、分包都很快,在100MS内都可以完成
可是可以发现,发送的时间却需要很久
一个图片开始发送后,要等好久才能发送出去
多测试几次,发现每次发送的时间差还很不一样,差异很大
这又是为什么呢?
发送的代码明明和以前一样
没什么改动
为什么会这样呢?
把打印消息再细分,每个分包数据数组的每个数组包发送时都打印出来
0----开始压缩--17点4分23秒43 0----结束压缩--17点4分23秒98 0----开始分包--17点4分23秒98 0----完成分包--17点4分23秒105 0----开始发送--17点4分23秒105 0----分包数据块开始打包--17点4分23秒105 0----分包数据块完成打包--17点4分23秒108 0----分包数据块开始打包--17点4分23秒110 0----分包数据块完成打包--17点4分23秒110 1----开始压缩--17点4分23秒225 1----结束压缩--17点4分23秒283 1----开始分包--17点4分23秒283 1----完成分包--17点4分23秒285 1----开始发送--17点4分23秒285 1----分包数据块开始打包--17点4分23秒285 1----分包数据块完成打包--17点4分23秒285 2----开始压缩--17点4分23秒423 2----结束压缩--17点4分23秒475 2----开始分包--17点4分23秒475 2----完成分包--17点4分23秒475 2----开始发送--17点4分23秒475 2----分包数据块开始打包--17点4分23秒475 2----分包数据块完成打包--17点4分23秒475 3----开始压缩--17点4分23秒623 3----结束压缩--17点4分23秒675 3----开始分包--17点4分23秒675 3----完成分包--17点4分23秒675 3----开始发送--17点4分23秒675 3----分包数据块开始打包--17点4分23秒675 3----分包数据块完成打包--17点4分23秒675 4----开始压缩--17点4分23秒790 4----结束压缩--17点4分23秒843 4----开始分包--17点4分23秒843 4----完成分包--17点4分23秒843 4----开始发送--17点4分23秒845 4----分包数据块开始打包--17点4分23秒845 4----分包数据块完成打包--17点4分23秒845 0----分包数据块开始打包--17点4分24秒240 0----分包数据块完成打包--17点4分24秒240 1----分包数据块开始打包--17点4分24秒806 1----分包数据块完成打包--17点4分24秒809 2----分包数据块开始打包--17点4分25秒389 2----分包数据块完成打包--17点4分25秒391 3----分包数据块开始打包--17点4分25秒997 3----分包数据块完成打包--17点4分26秒0 4----分包数据块开始打包--17点4分26秒567 4----分包数据块完成打包--17点4分26秒567 0----完成发送--17点4分26秒621 5----开始压缩--17点4分26秒698 5----结束压缩--17点4分26秒748 5----开始分包--17点4分26秒748 5----完成分包--17点4分26秒748 5----开始发送--17点4分26秒748 5----分包数据块开始打包--17点4分26秒748 5----分包数据块完成打包--17点4分26秒748 1----分包数据块开始打包--17点4分27秒198 1----分包数据块完成打包--17点4分27秒198 2----分包数据块开始打包--17点4分27秒781 2----分包数据块完成打包--17点4分27秒781 3----分包数据块开始打包--17点4分28秒364 3----分包数据块完成打包--17点4分28秒364 4----分包数据块开始打包--17点4分28秒932 4----分包数据块完成打包--17点4分28秒934 5----分包数据块开始打包--17点4分29秒528 5----分包数据块完成打包--17点4分29秒528 1----完成发送--17点4分29秒580 6----开始压缩--17点4分29秒628 2----完成发送--17点4分29秒633 7----开始压缩--17点4分29秒665 3----完成发送--17点4分29秒673 6----结束压缩--17点4分29秒713 6----开始分包--17点4分29秒713 6----完成分包--17点4分29秒713 6----开始发送--17点4分29秒713 6----分包数据块开始打包--17点4分29秒713 6----分包数据块完成打包--17点4分29秒713 |
通过以上信息
我们就可以很清晰的发现了
其实每个分包的打包封装也是很快的------2MS
只所以发送的时间长,是等待CPU处理的时间长
每个图片的处理位置都是很凌乱的
1处理下处理2,2处理下处理3,3处理下可能就到4,4可能又到2
为什么会这样呢?
其实这就要联系到底层一点的东西了
CPU时钟的分配
这个咱们就不展开讲了(我也讲不清楚)
大家都知道CPU时钟在每个线程间分配是随机的
我们这里用的是线程池中的多个线程来按时间间隔发送图片
每个线程又调用了很多方法
每执行完一个,该方法对应的一个线程就停歇一次
然后别的线程的别的方法调用可能就把线程抢占了
对应这个CPU时钟的抢占
是没办法控制的
把打印的消息在细化一点
每个图片包发送前,发送后的时间,发送这个包的打包时间,我们统统打印出来
同时,为了不出现发送紊乱的情况
我们在发送UDPImageMessage数组前加上一个支持并发编程的锁(java.util.concurrent.locks.Lock)
再次打印
0----开始压缩--19点40分46秒266 0----结束压缩--19点40分46秒323 0----开始分包--19点40分46秒323 0----完成分包--19点40分46秒332 0----开始发送--19点40分46秒332 0----分包数据块开始打包--19点40分46秒336 0----分包数据块完成打包--19点40分46秒336 0----开始发送----i-----0-------19点40分46秒338 0----结束发送----i-----0-------19点40分46秒338 0----分包数据块开始打包--19点40分46秒338 0----分包数据块完成打包--19点40分46秒341 0----开始发送----i-----1-------19点40分46秒341 1----开始压缩--19点40分46秒436 1----结束压缩--19点40分46秒486 1----开始分包--19点40分46秒486 1----完成分包--19点40分46秒488 1----开始发送--19点40分46秒488 2----开始压缩--19点40分46秒636 2----结束压缩--19点40分46秒683 2----开始分包--19点40分46秒683 2----完成分包--19点40分46秒686 2----开始发送--19点40分46秒686 3----开始压缩--19点40分46秒846 3----结束压缩--19点40分46秒896 3----开始分包--19点40分46秒896 3----完成分包--19点40分46秒896 3----开始发送--19点40分46秒898 4----开始压缩--19点40分47秒8 4----结束压缩--19点40分47秒58 4----开始分包--19点40分47秒58 4----完成分包--19点40分47秒61 4----开始发送--19点40分47秒61 0----结束发送----i-----1-------19点40分47秒191 1----分包数据块开始打包--19点40分47秒191 0----完成发送--19点40分47秒191 1----分包数据块完成打包--19点40分47秒191 1----开始发送----i-----0-------19点40分47秒191 1----结束发送----i-----0-------19点40分47秒236 1----分包数据块开始打包--19点40分47秒236 1----分包数据块完成打包--19点40分47秒236 1----开始发送----i-----1-------19点40分47秒236 5----开始压缩--19点40分47秒266 5----结束压缩--19点40分47秒316 5----开始分包--19点40分47秒316 5----完成分包--19点40分47秒318 5----开始发送--19点40分47秒318 1----结束发送----i-----1-------19点40分48秒81 2----分包数据块开始打包--19点40分48秒81 1----完成发送--19点40分48秒81 2----分包数据块完成打包--19点40分48秒81 2----开始发送----i-----0-------19点40分48秒83 2----结束发送----i-----0-------19点40分48秒91 2----分包数据块开始打包--19点40分48秒91 2----分包数据块完成打包--19点40分48秒91 2----开始发送----i-----1-------19点40分48秒91 6----开始压缩--19点40分48秒119 6----结束压缩--19点40分48秒179 6----开始分包--19点40分48秒179 6----完成分包--19点40分48秒179 6----开始发送--19点40分48秒179 2----结束发送----i-----1-------19点40分49秒109 2----完成发送--19点40分49秒109 3----分包数据块开始打包--19点40分49秒109 3----分包数据块完成打包--19点40分49秒170 3----开始发送----i-----0-------19点40分49秒170 7----开始压缩--19点40分49秒198 3----结束发送----i-----0-------19点40分49秒228 3----分包数据块开始打包--19点40分49秒228 3----分包数据块完成打包--19点40分49秒230 3----开始发送----i-----1-------19点40分49秒230 7----结束压缩--19点40分49秒248 7----开始分包--19点40分49秒248 7----完成分包--19点40分49秒250 7----开始发送--19点40分49秒250 3----结束发送----i-----1-------19点40分50秒195 3----完成发送--19点40分50秒195 4----分包数据块开始打包--19点40分50秒195 4----分包数据块完成打包--19点40分50秒195 4----开始发送----i-----0-------19点40分50秒195 8----开始压缩--19点40分50秒239 4----结束发送----i-----0-------19点40分50秒246 4----分包数据块开始打包--19点40分50秒246 4----分包数据块完成打包--19点40分50秒246 4----开始发送----i-----1-------19点40分50秒246 8----结束压缩--19点40分50秒289 8----开始分包--19点40分50秒289 8----完成分包--19点40分50秒291 8----开始发送--19点40分50秒291 4----结束发送----i-----1-------19点40分51秒264 4----完成发送--19点40分51秒266 5----分包数据块开始打包--19点40分51秒266 5----分包数据块完成打包--19点40分51秒327 5----开始发送----i-----0-------19点40分51秒327 9----开始压缩--19点40分51秒357 5----结束发送----i-----0-------19点40分51秒385 5----分包数据块开始打包--19点40分51秒385 5----分包数据块完成打包--19点40分51秒387 5----开始发送----i-----1-------19点40分51秒387 9----结束压缩--19点40分51秒410 9----开始分包--19点40分51秒410 9----完成分包--19点40分51秒410 9----开始发送--19点40分51秒410 5----结束发送----i-----1-------19点40分52秒337 5----完成发送--19点40分52秒337 6----分包数据块开始打包--19点40分52秒337 6----分包数据块完成打包--19点40分52秒340 6----开始发送----i-----0-------19点40分52秒340 10----开始压缩--19点40分52秒381 6----结束发送----i-----0-------19点40分52秒406 6----分包数据块开始打包--19点40分52秒406 6----分包数据块完成打包--19点40分52秒406 6----开始发送----i-----1-------19点40分52秒406 10----结束压缩--19点40分52秒436 10----开始分包--19点40分52秒436 10----完成分包--19点40分52秒436 10----开始发送--19点40分52秒436 6----结束发送----i-----1-------19点40分53秒448 6----完成发送--19点40分53秒448 |
我们发现
时间的消耗
还是主要在抢占CPU线程资源上
因为不一定什么时候才能轮到某个图像对应的线程来发
所以
发送等待时间可能长,可能短
这就给系统造成了很大不可靠性
而且等待时间超过3秒后
就已经对实时性控制失去了意义
我们能不能追根溯源
回到前面讨论的线程去
能不能就不开过多的线程来抢占CPU时钟
只开一个,这样就每次只会发一个了呢?
其实,这事不可行的
因为除了我们的发送图片的各个线程在抢占CPU时钟外
系统的其他线程也在抢占
把传输线程减少,只会减少整个程序对CPU的占有率
只会使每个图片发送时间变得更长
那
我们是不是开的同时发送图片的线程越多
程序抢占的CPU时钟越多,这样就越好呢?
答案也是否定的
开的线程越多
程序是占有的时钟越多了
但是针对某一个线程
可能它占得CPU时钟的概率就更小了
那么
每一个图片可能发出去的时间差就变的更大了
关于此问题的解决
恳请各位高手指点
分享到:
相关推荐
多线程的UDP通讯程序,基于socket的,网络编程实验
NI veristand UDP通讯custom-device
UDP转发脚本udp-forwarding--udp-forward-master.zip
这是Windows下,网络编程的例子! 具体的是udp广播实例,有一个客户端接收,一个服务端发送。
基于Verilog的UDP代码,支持GMII、RGMII、SGMII等接口,FPGA开发支持1G、10G、25G速率。内含多种开发板上的实现例子,如ML605、KC705、VCU108、VCU118、ExaNIC_X10、ExaNIC_X25、HXT100G等等。是FPGA开发UDP以太网的...
C语言实现,TCP/IP 服务器与客户端,UDP 服务器与客户端
udp控制舵机udp-controller-serivo-master.zip
udp 广播 通信,客户端和服务器, matlab 编程
通过多线程的方式实现了UDP收发数据 可以快速学习UDP通讯原理应进行实际应用
TCP-UDP-SOCK-TOOL[TCP-UDP Socket调试工具集合].7z TCP-UDP-SOCK-TOOL[TCP-UDP Socket调试工具集合].7z
一个高速udp接收程序,多线程数据保存,项目需要20M接收不丢包,测试在20M速度下可正常接收不丢包。
UDP-Socket-DELPHI编程源代码,UDP-Socket-DELPHI编程源代码
对UDP广播收发数据的Vb6 源码示例,使用socket控件
头歌UDP Ping程序实现-客户端创建UDP套接字头歌UDP Ping程序实现-客户端创建UDP套接字头歌UDP Ping程序实现-客户端创建UDP套接字头歌UDP Ping程序实现-客户端创建UDP套接字头歌UDP Ping程序实现-客户端创建UDP套接...
VC++6.0 UDP demo 实例 简单的udp广播实例
LWIP-udp-DHCP-DNS
解压后可用
UDP-TCP-Nodejs 这是客户端和服务器udp,tcp和p2p之间的进程之间的通信代码。 获取代码 # Clone this repository $ git clone https://github.com/Josehpequeno/UDP-TCP-Nodejs.git # Access the project folder ...
1.用户可以使用杜邦线根据自己的情况设置和连接引脚 1. Re:C#委托+回调详解 3. ESP8266刷AT固件与node 3.把模块用网线和路由器或者交换机
网上下的Linux下udp多线程的一个例子 网上下的Linux下udp多线程的一个例子 网上下的Linux下udp多线程的一个例子