好不容易
我的服务端和德玉的客户端
终于连上且能正常通信了------登陆请求,界面传送,操控请求,远程操控(鼠标操控,键盘操控)
然而
连上是连上了
可是连上后那通信速度慢的简直是令人蛋疼
在他客户端看到我的服务端几乎就是卡着不动的
这是为什么捏?
这是为什么捏?
这是为什么捏?
想了蛮久还是不知道这是为什么
先从最简单的思路开始分析
一个截屏图片的大小大概是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点忙碌到现在的一个总结吧
也希望给各位一起写这鸟程序的哥们一个启发。
分享到:
相关推荐
浅析以ARM9为核心的管井远程监控系统.pdf
浅析专家系统的用户界面设计
浅析广播电视高山发射台远程智能监控系统设计项目及功能测试.pdf
拦河闸坝是水利枢纽工程中必不可少的主体建筑物之一,它担负着水电厂的发电、流域的防洪、水库的灌溉等重要的任务。随着我国水利水电行业的迅猛发展,以及水电厂“无人...本文就中小型闸坝的自动化监控系统作几点探讨。
煤矿井下环境复杂,干扰问题严重,尤其是电磁干扰一直影响矿井安全生产监控系统的可靠性,严重时会导致监控系统出现假数、错误断电等情况,对井下干扰的分类、干扰的抑制方法进行了简单的探讨。
微机监控系统在大中型泵站中的应用浅析.pdf
安防监控系统防雷设计要点浅析fdfdfdsfdsfdf
摘要:在实践应用中结合...在远程监控系统中,作为一种种分布式控制系统和工业通信协议Modbus协议在工业领域得到很好的应用,本文针对其含义.系统设计构架和系统性能进行详细的阐述. 二.浅析Modbus协议 (1)Mo
地铁综合监控系统发展趋势浅析,城市轨道交通行业分析
浅析电源监控系统中智能消防设备的应用借鉴.pdf
安全监控系统升级改造过程中,通过解决大功率传感器的长距离传输、新系统兼容老系统数据等问题,实现了新老安全监控系统交替期间的不间断传输,实现了矿井安全生产。
#资源达人分享计划#
浅析智能视频监控系统.pdf
在现有的各种监控系统中,一部分采用传统的51单片机或ARM7作为监控系统的微控制器,但这类芯片受到主频等因素的制约,无法对复杂系统进行控制。还有一部分系统采集数据的传输采用CAN,RS-485,RS-232等通信方式,...
浅析对工业控制系统网络安全等级保护测评的研究.docx浅析对工业控制系统网络安全等级保护测评的研究.docx浅析对工业控制系统网络安全等级保护测评的研究.docx浅析对工业控制系统网络安全等级保护测评的研究.docx浅析...
浅析智能电力监控系统在三级医院配电系统中的应用.pdf
浅析智能视频监控系统.rar
近年来电气火灾居高不下,电气火灾监控系统已经逐渐成为建筑消防电气设计中必须考虑的重要部分。本文概述了电气火灾监控系统的组成及原理,并结合工程设计实例给出相关的系统结构方案。最后针对剩余电流式电气火灾...
浅析无线通信技术在水利工程监控系统中的应用.pdf
演播室智能监控系统浅析.pdf