`
java_mzd
  • 浏览: 580487 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

UDP广播图片---多线程CPU时钟小探讨

阅读更多

通过前面几篇文章的分析小结

我们已经知道知识纯粹的将图片按质量压缩

使图片文件小于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时钟的概率就更小了

那么

每一个图片可能发出去的时间差就变的更大了

 

 

关于此问题的解决

恳请各位高手指点 

分享到:
评论
1 楼 hold_on 2010-10-15  
我觉得应该给 众线程设置个优先级 
发送线程优先级最高,
打包、分包、压缩等优先级次之,

这个样只有要发送线程被启动,
尽可能快的执行发送线程,
(一般优先级越高的线程,系统分配给它的时间片越多)

相关推荐

Global site tag (gtag.js) - Google Analytics