昨天的简单的计算已经告诉了我们
用TCP/IP来发送服务器桌面信息
服务器端发出一张图片大概需要的时间是200MS
(所谓发出,即打包完成到发送结束----调用输出流的flush()方法截止)
那我们其实能做到的也就是一秒钟发送5张图片
把服务器端发送图片的时间间隔改成200MS后
发现服务器端和客户端的屏幕传输还算是比较正常
能够及时看到服务器端的操作
恩
基本改良咱是实现了
可是,咱不能满足
好吧,服务器端看电影,
客户端连接看看情况怎么样
额,客户端连上后
发现看到的电影里的人动作是不连贯的
WHY?
大伙都做到
电影是24帧每秒
而我们现在用TCP/IP就算用多线程
发挥网络最大能量(我们寝室的网络)
一秒也就传了5帧
这肯定是没办法正常看电影的
好了
现在问题提出来了
这种情况下
我们应该怎么样继续改良呢?
恩
大伙都知道
TCP/IP是面向连接、可靠的传输
用TCP/IP只要网络是通的
咱就可以放心,不怕数据传布过去
过去了不怕它收不到
可是出来混,总得有代价嘛
其中的3次握手,差错重传等等功能开销都是很大的
TCP/IP提供了可靠性以后
传输速度啥的自然就受到了很大的影响
而UDP是无连接、不可靠的传输
它不需要提供负责的连接机制,不需要理会差错控制
UDP只管作死的一个劲往外发
至于收不收的到,收到多少,则是跟哥无关的事
在对实时性要求很多的情况下
我们都只想能尽可能的多传输一些数据过去
就顾不上考虑那么多差错控制啥的了
(你这边在看直播,如果传输有差错了,总不能暂停一下,回过头来再回放,然后继续吧?)
这个时候,适当的差错是可以容忍的
实时是第一位的
好吧
既然一堆废话解析已经说的差不多了
现在
就正式开始通过UDP传输来改造俺们滴远程控制系统
其实在原有系统的基础上
基本上可以什么都不用变
改几行代码就差不多了
首先
其他的操控类信息还是要求要准确的
所以我们继续保留原有的TCP/IP连接中的SOCKET连接
这一部分都不动
原有系统协议,构架都不动
我们要实施UDP传输改良性能
无非就是把图片传输这个绝对巨头用UDP处理掉
所以
我们需要该的代码其实只是如下
在服务器端,用UDP发送取代用SOCKET发送
// 原有的用Socket发送图片的语句
// clientInfo.getThread().sendMessage(_screenImageMessage);
//用UDP发送的语句
java.net.InetAddress clientIP = clientInfo.getIPaddress();
int port=10000;//设置UDP端口为10000
byte[] data = _screenImageMessage.pack();
//封装为DatagramPacket
DatagramPacket datapacke = new DatagramPacket(data,data.length,clientIP,port);
//用来发送的DatagramSocket对象
DatagramSocket castSocket = new DatagramSocket(11000);
castSocket.send(datapacke);
Log.recordTime("结束发送截屏图像");
在客户端,用UDP接收,取代用SOCKET接收,至于什么接收图片监听啥的都不用改了
把用UDP接收图片独立开一个线程,连接成功后,自动启动该线程
代码如下
public class ReciveScreenImageUDP extends Thread{
Client _client;
public ReciveScreenImageUDP(Client client) {
_client = client;
}
public void run(){
reciveUDPMessage();
}
public void reciveUDPMessage() {
while (true) {
try {
DatagramSocket reciveImageSocket = new DatagramSocket(10000);
// 定义缓冲区大小
byte[] buffer = new byte[250000];
DatagramPacket recivedPacket = new DatagramPacket(buffer,
buffer.length);
reciveImageSocket.receive(recivedPacket);
// 接收完字节数组,开始解封,处理
MessageHead message = UnpackMessageTools
.unpackUDPimageMessage(buffer);
_client.UDPMessageHandle(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
好了
问题出来了
咱又得开始说废话,讲道理,玩数学了
首先,我想大家应该明白一点
UDP中传输数据都是以DatagramPacket为单位传的
也就是说,我们接收也是一个pachet一个packet的来接收
那么,接收之前,我们怎么知道每个packet里面数据量的大小呢?
恭喜你
答对了
我们就是不知道
所以
我们必须用到缓冲区数组buffer来接收packet
buffer设置小了,那么剩下的那部分数据你是收不到的
人家发送方是不对你负责的,谁叫你Y自己小气,舍不得把buffer设大一点呢
于是有人说
我们就把buffer设大一点吧
你还真是大方。。。
只有一小杯水,你开一辆卡车去运,有必要吗?
我们得通过算数来解决问题的
要算数
我们首先得知道我们到底需要多大的缓冲区
才能够完整的接收packet,同时还不能太浪费,注意,我说的是太浪费
恩,那我们还是先知道每次对方传来的数据有多大吧
好,那我们就测试,每次生成的图片大概有多大
需要测试这个,无非就是,截个屏,化为数组,看下大小
测试代码如下,比较简单,就不废话来解释了
/**
* 用来测试每次截屏生成图片大小的类
* @author mzd
*/
public class ImageSizeTest {
/**
* @param args
* @throws AWTException
*/
public void getImageSize() throws AWTException {
java.awt.Robot rb = new java.awt.Robot();
Dimension d = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
Rectangle rt = new Rectangle(0, 0, (int) d.getWidth(), (int) d
.getHeight());
byte[] data = null;
for (int i = 0; i < 1000; i++) {
BufferedImage image = rb.createScreenCapture(rt);
data = bufferedImageTobytes(image);
System.out.println(i + "--------图片--------->" + data.length);
}
}
private byte[] bufferedImageTobytes(BufferedImage image) {
BufferedImage bImage = new BufferedImage(image.getWidth(null), image
.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics bg = bImage.getGraphics();
bg.drawImage(image, 0, 0, null);
bg.dispose();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(bImage, "jpeg", out);
} catch (IOException e) {
e.printStackTrace();
}
return out.toByteArray();
}
public static void main(String args[]) throws Exception {
ImageSizeTest test = new ImageSizeTest();
test.getImageSize();
}
}
我运行了1000次来观测,在这里就不可能贴出1000次的结果了
随机截取一部分吧
299--------图片--------->184001 300--------图片--------->183878 301--------图片--------->183825 302--------图片--------->183796 303--------图片--------->183710 304--------图片--------->183705 305--------图片--------->183705 306--------图片--------->183827 307--------图片--------->183827 308--------图片--------->183827 309--------图片--------->183921 310--------图片--------->184064 311--------图片--------->184040 312--------图片--------->183792 313--------图片--------->183809 314--------图片--------->183900 315--------图片--------->183867 316--------图片--------->183789 317--------图片--------->183871 318--------图片--------->183691 319--------图片--------->183685 320--------图片--------->183666 321--------图片--------->183666 322--------图片--------->183666 323--------图片--------->183666 324--------图片--------->183666 325--------图片--------->183666 326--------图片--------->183666 327--------图片--------->183666 328--------图片--------->183961 329--------图片--------->183961 330--------图片--------->183961 331--------图片--------->183961 332--------图片--------->183961 333--------图片--------->184059 |
很明显,我们发现每个图片的应该在200000左右
为了保险起见,
我们把缓冲区再设大点
250000
好了
废话完了
代码改完了
咱执行一次看看?
java.net.SocketException: The message is larger than the maximum supported by the underlying transport: Datagram send failed at java.net.PlainDatagramSocketImpl.send(Native Method) at java.net.DatagramSocket.send(DatagramSocket.java:612) at cn.javaeye.java_mzd.Monitor.Server.CastScreenImageUDP.castScreenImage(CastScreenImageUDP.java:49) at cn.javaeye.java_mzd.Monitor.Server.CastScreenImageUDP.run(CastScreenImageUDP.java:22) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:619) |
我勒个去……………………
辛辛苦苦,看我废话半天
改了半天
结果不行?
这也太恶心
太丢脸了
哎
我没办法
别急
咱再慢慢分解
首先
咱看报的错误
The message is larger than the maximum supported by the underlying transport: Datagram send failed
大家英语都比我好
都看的明白
简单的说
就是传的文件比Datagram允许的大了
这是怎么回事?
人家几个G都能传
我这么小个图片竟然不能传?
TCP/IP都能传
我UDP还不能传?
嘿
恭喜你答对了
TCP/IP能传
你用UDP直接用packet还就是传不了
(注意,我是说直接用packet)
恩
这是为什么呢?
还记得前面我废话过TCP/IP可以差错控制什么的嘛?
用TCP的时候
协议会把大文件分块成许多小块
发表路由发送至目的地后
再通过标识来重组,对没有成功发送的提供差错控制
完成重传
而UDP不提供此功能(当然,你可以直接去完善,那不是我今天讨论的内容)
好了
回到正题
为什么UDP会传不了呢?
我们知道
不管用UDP还是TCP最后得被封住成IP数据包
而一个IP数据报的总长度只是用16位来表示
16位,什么概念呢
也就是最多可以放64KB的数据
再回头看看我们截屏的图片大小
200KB
所以……………………
好了
休息会
下期分解
JAVA中图片有损压缩