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

远程监控系统中控制端界面响应慢浅析

阅读更多
 好不容易

我的服务端和德玉的客户端

终于连上且能正常通信了------登陆请求,界面传送,操控请求,远程操控(鼠标操控,键盘操控)

 

然而

连上是连上了

可是连上后那通信速度慢的简直是令人蛋疼

在他客户端看到我的服务端几乎就是卡着不动的

 

这是为什么捏?

这是为什么捏?

这是为什么捏?

想了蛮久还是不知道这是为什么

 

先从最简单的思路开始分析

一个截屏图片的大小大概是20KB

就算一秒发了50张图片

不经过任何压缩

带宽也只要有8MBPS即可

 

而我们是同一局域网的内网

路由提供的网内带宽远远高于这个

 

好吧,就算我们不是同一内网

用的是电信的ADSL

带宽是2MBPS

那一秒还能传10多张图片呢

怎么会这么卡呢?

 

显然

不是很多人和我前期认为的网络通信出了问题

流量太大,需要进行压缩

(当然,后期进一步优化的时候可以考虑这个,但是目前这卡的蛋疼的屏幕几乎不动的情况应该不是这个造成的)

 

今天上课讨论到了软件性能的测试

要测试一个软件性能

我们首先应当明白,影响性能的瓶颈是出在什么地方

 

在此例中,大部分觉得应该是图片太大

网络传不过去

 

我想知道的是

从服务器产生一个图片到传送给客户机再到客户机显示图片

这中间究竟那一块耗的时间比较多呢?

我们用下列方法来进行分析

一个图片传送的典型流图如下
我们需要做的是
记录以下每个过程发生前以及发生后的系统时间
通过分析系统时间来分析通信中究竟哪块是最耗时间的         


                         ------------------------------------------
                           截屏   
                        ------------------------------------------
                            截屏图像打包
                        ------------------------------------------
                            发送
                        -----------------------------------------
                            接收
                        ------------------------------------------
                            解包
                        ------------------------------------------
                             显示
                        ------------------------------------------

 要实现以上功能,用实现一个工具类,用来输出相应信息对应的系统时间

 

/**
 * 性能测试的输出类
 * @author mzd
 */
public class Log {
	/**
	 * 输出消息INFO及当前系统时间
	 * @param INFO
	 */
	public static void recordTime(String INFO) {
		System.out.println(INFO + getCurrentTime());
	}

	/**
	 * 自己定义格式,得到当前系统时间
	 * @return
	 */
	public static String getCurrentTime() {
		Calendar c = new GregorianCalendar();
		int hour = c.get(Calendar.HOUR_OF_DAY);
		int min = c.get(Calendar.MINUTE);
		int second = c.get(Calendar.SECOND);
		int millsecond = c.get(Calendar.MILLISECOND);
		String time = hour + "点" + min + "分" + second + "秒" + millsecond;
		return time;
	}
}

 

 然后

服务器端,只要在截屏,将截屏图片打包前,将截屏图像打包后,将截屏图像发送前,将截屏图像发送后分别调用该工具类的recordTime()方法

客户端,接收消息前,接收第一个消息时,接收完本次全部消息时,消息解封成图片前,消息解封成图片后分别调用工具类的recordTime()方法

就可以在每次调用方法的时间记录下我们需要记录的信息

即可根据信息分析各个流程的时间

 

下面随机截取连续3组我们取得的消息,以便分析(实际取得消息多大数千组)

 

服务器端 客户端
截屏0点36分11秒433
开始发送截屏图像0点36分11秒433
开始截屏图片打包0点36分11秒433
截屏图片打包完成0点36分11秒810
结束发送截屏图像0点36分12秒0
准备开始读取ImageMessage---->0点36分16秒941
读取了一个  int ---->0点36分17秒606
读完了 byte []---->0点36分17秒783
准备  byte[] to Image ---->0点36分17秒783
转换图片完成, 准备显示在界面上---->0点36分17秒841
显示完成了。。。。0点36分17秒841
截屏0点36分12秒230
开始发送截屏图像0点36分12秒230
开始截屏图片打包0点36分12秒230
截屏图片打包完成0点36分12秒745
结束发送截屏图像0点36分12秒931
准备开始读取ImageMessage---->0点36分17秒841
读取了一个  int ---->0点36分18秒548
读完了 byte []---->0点36分18秒741
准备  byte[] to Image ---->0点36分18秒741
转换图片完成, 准备显示在界面上---->0点36分18秒801
显示完成了。。。。0点36分18秒801
截屏0点36分13秒166
开始发送截屏图像0点36分13秒166
开始截屏图片打包0点36分13秒166
截屏图片打包完成0点36分13秒666
结束发送截屏图像0点36分13秒856
准备开始读取ImageMessage---->0点36分18秒801
读取了一个  int ---->0点36分19秒500
读完了 byte []---->0点36分19秒682
准备  byte[] to Image ---->0点36分19秒682
转换图片完成, 准备显示在界面上---->0点36分19秒780
显示完成了。。。。0点36分19秒780

 

虽然我们两天机器上的时间不同,不能直接进行加减运算得到两台机器的世界差

但是这是我们连续测的3组数据

所以每组数据件相互对比分析还是可以得到很多问题的

 

首先我们看服务器端

在一次发送消息的过程中

服务器从截屏到开始发送截屏图像时间消耗几乎是0

从发送截屏图像到开始图片打包时间消耗也几乎是0

但是,从图片打包到图片打包完成时间消耗高达500+MS

而从打包完成到发送完成时间消耗大约在200MS

 

这样,每发送一组图片

我们就消耗了700+MS

 

再看客户端

我们惊奇的发现

客户端每次读完消息

到再读下一个消息(中间的read阻塞)时间长度几乎达到了700MS

而从开始读取一个消息到读取完一组消息的时间消耗为不到200MS

也就是说,

从客户端看

其实大部分时间

网络还是空闲的

 

我们再回头来看我们起初关于图片传输是怎么设想的

我们设想的是通过一个线程来单独实现

我们希望一秒钟大概可以传50个图片

 

等等,

好了

问题的关键点终于出来了

(我也废话老半天了)

我们希望的是一秒钟能传送50个图片

我们的实现方式是在服务器启动一个线程

没20MS截屏一次向客户端发送一张图片

以为这样就能做到1秒发50次了

 

其实我们忘了

在一个独立的线程内

方法的执行还是串行的,并不是多线程间的并行关系

(此处注意多线程间,和独立单线程内)

也就是说

/**
 * 此线程用来不断向连入的客户机发送服务器屏幕图像
 * @author mzd
 */
public class CastScreenThread extends Thread {
	// 节约内存,把需要不断生成的下列对象设为属性
	ScreenImageMessage _screenImageMessage = null;
	BufferedImage _screenImage = null;
	ArrayList<OnlineClientInfo> _clientsList = null; //在线用户队列
	OnlineClientInfo clientInfo = null;

	public void run() {
		_clientsList = OnlineClientTableModel.getOnlineClients();
		// 当有用户连入的时候,一直广播
		while (_clientsList.size()>0) {
			try {
				castScreenImage();
				Thread.sleep(20);
			} catch (Exception ef) {
				ef.printStackTrace();
			}
		}
	}

	/**
	 * 向队列中连入用户不断发送屏幕图像
	 * @param screenImageMessage
	 */
	public void castScreenImage() {
		_screenImageMessage = this.getScreenImageMessage();	
		for (int i = 0; i < _clientsList.size(); i++) {
			clientInfo = _clientsList.get(i);
			try {
				//判断用户是否有监控权
				if (clientInfo.isMonitor()) {
					//为用监控权的用户发送图像
					Log.recordTime("开始发送截屏图像");
					clientInfo.getThread().sendMessage(_screenImageMessage);
					Log.recordTime("结束发送截屏图像");
				}
			} catch (Exception e) {
				System.out.println("写出屏幕图片消息出错" + e.getMessage());
				e.printStackTrace();
			}
		}
	}

	private ScreenImageMessage getScreenImageMessage() {
		_screenImage = ServerTools.shotScreen();
		Log.recordTime("截屏");
		_screenImageMessage = new ScreenImageMessage();
		_screenImageMessage.setBufferedImage(_screenImage);
		_screenImageMessage.setReciverID(0);
		_screenImageMessage.setSenderID(0);
		return _screenImageMessage;
	}
}

 

该段代码起到的作用

只是我们每发送完一次图片后(发送时间7-900MS),

线程休眠20MS,再发送下一个图片(发送时间7-900MS),

发送没个图片中间需要的那7-900MS是我们没有考虑的

 此例只是启动了一个线程来发送完一个图片休眠20MS再发送下一个图片

 

 

 

并没有实现我们本来想要实现的

每20MS就先客户端发送一张图片

让客户端实时监控服务端界面

 

 

好了,废话又完了一段落

 

接下来,进入本次正题之二

 

问题的解决

 

要现实我们想要的每20MS就向客户端发送一个图片

其实很简单

我们只要每隔20MS就截屏一次,然后启动一个线程用来向控制端发送图片信息

即可完成实时监控

 

 

然后又回到了开篇的老话题

软件的性能

其实创建线程是一个开销很大的动作

如果一直创建的话……………………

为了效率和软件的性能起见

咱们就用用那传说中的线程池来实现

 

好了

实现思路说完了

咱不废话,直接看代码

/**
 * 此线程用来不断向连入的客户机发送服务器屏幕图像
 * @author mzd
 */
public class CastScreenThread extends Thread {

	public void run() {
		//在线用户队列
		ArrayList<OnlineClientInfo>	_clientsList = OnlineClientTableModel.getOnlineClients();
		// 线程池对象,初始化定义为20
		ExecutorService exec = Executors.newFixedThreadPool(20);
		// 当有用户连入的时候,一直广播
		while (_clientsList.size() > 0) {
			try {
				//每20秒生成一个发送服务器图片线程,向客户机发送用户图片
				ServerScreenImageSender sender = new ServerScreenImageSender();
				exec.execute(sender);
				Thread.sleep(20);
			} catch (Exception ef) {
				ef.printStackTrace();
			}
		}
	}
}

 

/**
 * 此线程用来不断向连入的客户机发送服务器屏幕图像
 * @author mzd
 */
public class CastScreenThread extends Thread {
	ServerScreenImageSender sender;

	public void run() {
		// 线程池对象,初始化定义为20
		ExecutorService exec = Executors.newFixedThreadPool(5);
		// 当有用户连入的时候,一直广播
		while (true) {
			try {
				//每20秒生成一个发送服务器图片线程,向客户机发送用户图片
				sender = new ServerScreenImageSender();
				exec.execute(sender);
				Thread.sleep(20);
			} catch (Exception ef) {
				ef.printStackTrace();
			}
		}
	}
}

 

/**
 * 向客户端发送一张屏幕图片的线程
 * @author mzd
 */
public class ServerScreenImageSender implements Runnable {
	// 节约内存,把需要不断生成的下列对象设为属性
	ScreenImageMessage _screenImageMessage = null;
	BufferedImage _screenImage = null;	
	OnlineClientInfo clientInfo = null;
	ArrayList<OnlineClientInfo> _clientsList = null; //在线用户队列

	@Override
	public void run() {
		_clientsList = OnlineClientTableModel.getOnlineClients();
		castScreenImage();
	}

	/**
	 * 向队列中连入用户不断发送屏幕图像
	 * @param screenImageMessage
	 */
	public void castScreenImage() {
		_screenImageMessage = this.getScreenImageMessage();	
		for (int i = 0; i < _clientsList.size(); i++) {
			clientInfo = _clientsList.get(i);
			try {
				//判断用户是否有监控权
				if (clientInfo.isMonitor()) {
					//为用监控权的用户发送图像
					Log.recordTime("开始发送截屏图像");
					clientInfo.getThread().sendMessage(_screenImageMessage);
					Log.recordTime("结束发送截屏图像");
				}
			} catch (Exception e) {
				System.out.println("写出屏幕图片消息出错" + e.getMessage());
				e.printStackTrace();
			}
		}
	}
	
	private ScreenImageMessage getScreenImageMessage() {
		_screenImage = ServerTools.shotScreen();
		Log.recordTime("截屏");
		_screenImageMessage = new ScreenImageMessage();
		_screenImageMessage.setBufferedImage(_screenImage);
		_screenImageMessage.setReciverID(0);
		_screenImageMessage.setSenderID(0);
		return _screenImageMessage;
	}
}

 

 

代码出来了

问题就解决了嘛?

不要急

我还有笔账要跟大家算

 

可还记得前面说的

每个消息打包后开始发送,到发送完

需要200MS

而我们这里每隔20MS就开启一个线程用来传图片

这会发生什么神奇的事呢?

直接给大家看报的错吧

sun.awt.image.ImageFormatException: Invalid JPEG file structure: two SOI markers
	at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
	at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:119)
	at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:246)
	at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:172)
	at sun.awt.image.ImageFetcher.run(ImageFetcher.java:136)
Exception in thread "Thread-2" java.lang.NegativeArraySizeException
	at net.remote.client.main.RemoteThread.readMessage(RemoteThread.java:79)
	at net.remote.client.main.RemoteThread.process(RemoteThread.java:49)
	at net.remote.client.main.RemoteThread.run(RemoteThread.java:30)

 

这个直接导致了读图片的格式不正确

Exception in thread "Thread-2" java.lang.NegativeArraySizeException
	at net.remote.client.main.RemoteThread.readMessage(RemoteThread.java:79)
	at net.remote.client.main.RemoteThread.process(RemoteThread.java:49)
	at net.remote.client.main.RemoteThread.run(RemoteThread.java:30)

 

上面两个是在客户端抱的错

java.net.SocketException: Software caused connection abort: socket write error
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:124)
	at cn.javaeye.java_mzd.Monitor.Server.ClientThread.sendMessage(ClientThread.java:321)
	at cn.javaeye.java_mzd.Monitor.Server.ServerScreenImageSender.castScreenImage(ServerScreenImageSender.java:38)
	at cn.javaeye.java_mzd.Monitor.Server.ServerScreenImageSender.run(ServerScreenImageSender.java:22)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)结束发送截屏图像2点25分50秒250

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)

 

这是我服务器端的错

 

试了几次

得到几个不同的错误

这问题出的。。。。。。

 

 

这么神奇的事是为什么呢?

其实我们都明白是协议出问题了

只是

明明协议前边都是好的

现在就出问题了呢?

 道理很简单

看下图

           SendMessage时 线程间出现强占OutputStream的情况

         Thread 1                           Thread 2
               Out(m1)                              Out(j1)
               Out(m2)                              Out(j2)
               Out(m3)                              Out(j3)

依据协议规定
本来m1-m2-m3组合起来才有意义
可是我们开的线程一多
又因为线程运行的不确定性(cpu分配)
很多可能第一次我们写的是m1-m2-m3
第二次就是m1-m2-j1
第三次可能就是m1-j1-j2
……………………
出现这种情况都是我们无法解决的
协议就出错了
对方根本无法识别我们的消息


 

 

 

解决这个问题更是简单的一行代码都不需要

(我们需要在sendMessage方法或者outputStream前面加上一把锁(synchronized)来锁住他们,让他们同时只能被一个线程所使用即可)

这个半行代码的事,我就不代劳贴出来了。。

 

 

好了

该说的差不多都说了

也都该完了

再连

哈哈哈~~~~

果然界面放映速度快多了

哦也~~

 

 

如果你觉得我哦也了

问题就说完了

那你就错了

 

你要相信我的啰嗦程度

 

现在我们再次回到前面说到的压缩问题

我仗着自己学过几年数学

再次给大家来算一笔账

假如

仅仅是假如我们1秒钟真的能发出50张图片

我们假如一张截屏图片的大小是20KB(够小了吧?)

那么一秒钟产生的流量都是20*50*8=8MKPS

知道咱电信的ADSL是多少吗?

 2MBPS还是理想状态

给你个8MBPS的你都用不了啊(理想状态*80%)

 

所以

压缩

还是必须滴

 

要压缩

就必须在发送前压缩

接受后再解压

(这不是废话吗?)

 

现在

咱再简单算一个帐

假如我们在广域网上使用咱的远程监控

双方带宽都为2MBPS

就算我们把图片从20KB压缩成了10KB(这压缩率够了不?)

那么我们1second也就能发个25张左右

但是我们20MS发一张

这就50张了

也就算说

咱还是老实点

把那发图片的时间调长吧

短了没用

图片怎么也发布出去

还降低桌面显示的实时性

(明白? 可能过了10分钟了,还在发前面第5分钟的图片呢)

再细我也就不想算了

我反正先从20MS改成50MS吧

 

依据前面的软件性能测试数据

我们能知道

在我们现有的网络中(用无线网卡接入寝室无线路由)

我们在网络中发送完一个图片信息的时间消耗是200MS左右

也就是说

我们1second只能发5张左右的图片

那么大小应该是800MBPS

这我们的网络还是能很轻松承当的

 

压缩后数据啥的具体的

等哥把什么ZIPINPUT什么的看了再研究吧。

 

今天废话至此

算是自己从晚上7点忙碌到现在的一个总结吧

也希望给各位一起写这鸟程序的哥们一个启发。

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics