好久没动笔了,有点手生了。最近因为项目要求,重新抬起了这个远程控制。远程控制其实就两样东西:屏幕传输和指令传输(鼠标、键盘等指令),但是这次与以往不一样的是多了一个小标题“论BeginReceive .”,这也是为了强调这是在Socket开发中需要慎重的方法。
在前几篇中,已经向大家介绍了的实现。这次所写的远程控制,其中的屏幕差异查找算法与屏幕传输v2.0中的一样采用的是分块扫描的方式,未作修改。在之前的基础上,调整了网络传输方式,这次xx采用UDP的方式进行实现,并且增加了鼠标指令的传输。文中仅为个人观点,有不足之处欢迎大家的批评。
先来看下效果图,和之前的几篇中的差不多:
效果图
开发平台及环境:
VS 2008 SP1、C#、WindowXP、集成显卡、双核
几个重要改进:
1.采用了UDP方式。
如果您对网络知识有一定的认识,肯定知道UDP在传输的速率上要远远优于TCP,因为UDP没有TCP严格的确认机制和滑动窗口等一系列的机制。但是要注意的是,虽然通过UDP方式在传输性能上有了一定的提高,但是面临的是随之增大的丢包机率。由于时间比较仓促,在当前版本中并未对丢包进行控制。如果您要实现可靠性控制,可以从以下2个角度进行考虑:1.减少单个UDP报文的大小到1472以下{zh0}是548字节以下(这是因为普通的交换路由设备的MTU值一般为1500字节,即单个IP报文分片的{zd0}长度为1500字节,减去IP协议头部字节长度20字节和UDP报文的头部长度8字节后得到1472字节。而548是因为internet的标准MTU值为570字节。详细请查询TCP/IP方面的书籍),如果一个数据报文长度超过MTU值,会被交换路由设备进行分片,而这些分片在没有可靠性机制的情况下到达目的后可能会丢失和乱序,如果人为的控制单个报文的{zd0}长度,则可以{zd0}限度的提高UDP传输的可靠性和顺序性;2.通过在应用层增加可靠性机制来实现,这些机制可以模拟TCP的实现方式,比如消息应答确认机制等。
2.放弃使用Socket.BeginReceive来接收数据(此观点基于UDP,并不能确定在TCP方式下是否适用)
之所以要把这个单独提出来作为一个改进,是因为在最近几天的调试中,我发现了这个很可恶的BeginReceive所干得好事。如果您在网络开发中遇到这个了,请慎重。当使用Socket.BeginReceive这个方法时,您考虑过您为什么选择使用它,而不是Socket.Receive方法吗?我想您肯定是考虑过的,使用它的目的也就是为了能让Socket异步接收消息,并对消息进行处理,而不希望影响后续消息的接收。当来自网络上的多个数据间隔比较久且您的消息处理时间小于该间隔时间的时候,它会表现很出色,但是当网络上的数据以毫秒的间隔时间不断的向您的接收端发送数据的时候,除非您的消息处理时间小于这个间隔(但我想您很难保证所有您的消息处理时间都是毫秒级或微秒级的),否则“恭喜您”,您将会遇到和我所遭遇的相同的情况:一部分数据并没有及时得到响应,但在某一刻(可能是好几秒后),部分这些数据被一下子一连串的处理了,如果您用的是UDP,可能还会丢失很多数据。
下面来看看这样的程序:
{
Socket remoteSocket = ((Socket)result.AsyncState).EndAccept(result);
if (_remoteSockets == null)
{
//处理语句
}
else
{
//处理语句
}
_state = new ReceiveState();
_state.Buffer = new byte[BUFFER_LENGTH];
_state.HandleSocket = remoteSocket;
remoteSocket.BeginReceive(_state.Buffer, 0, BUFFER_LENGTH, SocketFlags.None, new AsyncCallback(ReceiveCallBack), _state);//注意
}
这是我在屏幕传输v2.0TransitEngineBasicOfTCP类中写的一段代码,这个方法的{zh1}调用了BeginReceive,但是回调函数是本身。什么意思呢?也就是说我开始接收下一个数据包的前提是我已经处理完了前面的这个数据包,因为BeginReceive是在消息处理的{zh1}被调用。你可以试着在这段代码中放置一条Thread.Sleep(10000);的语句看看效果。
上面的这段程序并不能说明BeginReceive的问题,只是告诉我们{zh0}不要通过这种方式来循环获取数据。
接着来看下面的程序:
{
if (_localSocket.Poll(10,SelectMode.SelectRead))
{
_localSocket.BeginReceive((_state.Buffer, 0, BUFFER_LENGTH, SocketFlags.None,new AsyncCallback(ReceiveCallBack), _state);
}
}
这段代码可能是比较常见的情况。在您的程序中,你是否写过如上的方法,如果回答“是”,那您要小心了。为什么这么说呢?您不仿自己做一个小测试:在ReceiveCallBack这个回调函数中,与{dy}段程序一样放置一条Thread.Sleep(10000);的语句。运行您的程序,会发现您的机器对发来的请求响应超慢,是不是接收端压根没有接收到数据呢?这个很容易判断,只要在上面的if语句中加入一个Console.WriteLine(“….”);就会发现数据包都能正常接收到,但是响应却超慢。原因就在于BeginReceive这个方法,据我观察,当有数据包时,即SelectRead为true时,BeginReceive会新开一个线程进行处理,因为是异步机制,所以这个处理会立刻返回;当第二个消息到达时,BeginReceive并不会立刻创建一个线程开始处理,而是会阻塞,直到前一个数据包已经被处理完。这就是为什么数据包的接收和处理不是相对应的原因。
那么如何改进呢?这里我提供我的一个方法。
{
try
{
//通过select方式检查网络上的请求,如果有请求则处理
if (_localSocket.Poll(100, SelectMode.SelectRead))
{
/* 创建线程来处理收到的数据
* 注:不能单纯的使用BeginReceive,BeginReceive虽然也是创建一个线程执行处理,但是他不会为每一个新来的请求都立刻创建一个
* 线程开始处理,而是要等上一个请求处理结束后,才会开始处理。因此,要自己在接收到一个新的请求后创建线程,并通过Receive来
* 进行处理
*/
ThreadPool.QueueUserWorkItem(
(object o) =>
{
recvData = _localSocket.Receive(ref _remoteEP);
if (recvData != null)
{
OnReceived(recvData);
}
});
}
}
catch
{
break;
}
}
上面的注释已经写的很明白了,我就不另外解释了。通过这个方法,可以及时对接收到的数据作出响应。
3.添加了鼠标指令的传输
指令的传输并不困难,只要你知道windows与鼠标事件相关的API,那相当轻松。下面我就以传输鼠标移动、和鼠标按下的指令为例进行说明:
a.鼠标移动
首先必须截获鼠标移动事件,这个可以利用.Net提供的事件机制来做,就是注册MouseMove事件。在该事件中,需要将鼠标移动的位置信息记录下来,并且按照您所规定的协议格式进行封装后发送给对方。对方解析此包,分析出鼠标的移动事件和移动的坐标后,通过p/invoke方式调用如下的方法:
private static extern void SetCursorPos(int x, int y);
b.鼠标按下
截获鼠标按下的事件为MouseDown。然后同样封装后发送。接收端解析后,得到鼠标按下的信息,然后同样以P/invoke方式调用如下方法:
public static extern int mouse_event(uint dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
dwFlags表示对应的鼠标事件,如果是鼠标左键按下则该值为:0x0002。(这是由操作系统规定的)
当前版本的不足:
1.差异查找算法还不待完善,当前的算法很容易产生碎片。
2.这个产品只是为实际开发做得一个抛弃原型,因此很多细节未观注,比如在窗体关闭的时候可能会出现问题。
3.UDP未提供可靠性机制。
可以改进的方式:
1.目前采用的是GDI+的方式对图像进行处理,可以通过DirectX进行处理,从而利用GPU的功能。
2.目前采用的是动态分块扫描算法,可以利用Windows的API截获当前的活动窗口,来获取最有可能被改变的那一部分图片。
源代码下载:
[]
Demo下载:
[]