为WCF增加UDP绑定(实践篇)

这两天忙着系统其它功能的开发,没顾上写日志。本篇所述皆围绕为WCF增加UDP绑定(储备篇)中讲到的微软示例,该示例我已上传到网盘。

上篇说道,绑定是由若干绑定元素有序组成,为WCF增加UDP绑定其实就是为绑定增加UDP传输绑定元素,最终目的是在信道栈中生成UDP传输信道。因此我们定义一个类UdpTransportBindingElement,它继承自TransportBindingElement表明这是传输相关的绑定元素。示例中该类还实现了IPolicyExportExtension和IWsdlExportExtension接口,关于这两个接口,我知道它们的作用,但看到代码却是一头雾水,我认为要能理解这块内容首先要对WSDL相关的各种规范做一详细了解,目前我先不考虑。对这有兴趣的朋友可阅读蒋大牛的元数据架构体系全景展现以前总觉得WCF不外如是,不成想这水也太特码深了(WCF相关的概念有很多,包括ChannelDispatcher、ListenerHandler、ChannelHandler等,这些和消息传输关系不大)。 

UdpTransportBindingElement主要负责绑定管理类的创建工作,服务器端和客户端分别创建的是UdpChannelListener和UdpChannelFactory。UdpChannelFactory的实现比较简单,我们重点看UdpChannelListener实现。网上资料都有类似这么一段描述:ChannelListener负责监听消息,一旦消息抵达则使用对应的Channel接收消息。在UDP解决方案中,消息监听依赖System.Net.Sockets.Socket类,示例中调用该类的BeginReceiveFrom方法进行异步监听。

 1 void StartReceiving(object state)
 2 {
 3     Socket listenSocket = (Socket)state;
 4     IAsyncResult result = null;
 5 
 6     try
 7     {
 8         lock (ThisLock)
 9         {
10             if (base.State == CommunicationState.Opened)
11             {
12                 EndPoint dummy = CreateDummyEndPoint(listenSocket);
13                 byte[] buffer = this.bufferManager.TakeBuffer(maxMessageSize);
14                 result = listenSocket.BeginReceiveFrom(buffer, 0, buffer.Length,
15                     SocketFlags.None, ref dummy, this.onReceive, new SocketReceiveState(listenSocket, buffer));
16             }
17         }
18 
19         if (result != null && result.CompletedSynchronously)
20         {
21             ContinueReceiving(result, listenSocket);
22         }
23     }
24     catch (Exception e)
25     {
26         Debug.WriteLine("Error in receiving from the socket.");
27         Debug.WriteLine(e.ToString());
28     }
29 }

需要关注Socket.BeginReceiveFrom方法,MSDN有一段描述:当应用程序调用 BeginReceiveFrom 时,系统将会使用单独的线程来执行指定的回调方法,并将在 EndReceiveFrom一直阻塞Socket 读取数据或引发异常为止。但示例中有判断该方法是否同步执行的代码,因此我认为该方法并不会每次都用异步的方式来进行数据的接收,具体请看我的另一篇文章关于IAsyncResult接口的CompletedSynchronously属性

不出意外,当数据抵达后采用Socket.EndReceiveFrom获取。我看到这里的时候觉得有点不对劲,不是说“ChannelListener负责监听消息,一旦消息抵达则使用对应的Channel接收消息”吗?既然ChannelListener都把数据接收完成了,还要Channel何用。此处似乎Channel真的只是走个过场,它存在的意义只是因为WCF框架需要它。所以我们还是需要将已接收的消息从ChannelListener传递给Channel,于是我翻看代码,希望找到一个event,在消息接收完毕后触发,以便Channel能实时得到消息可用的信号,结果没找到哪怕一个event的声明,于是我苦恼了。

 1 public interface IInputChannel : IChannel, ICommunicationObject
 2 {
 3     EndpointAddress LocalAddress { get; }
 4 
 5     IAsyncResult BeginReceive(AsyncCallback callback, object state);
 6     IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state);
 7     IAsyncResult BeginTryReceive(TimeSpan timeout, AsyncCallback callback, object state);
 8     IAsyncResult BeginWaitForMessage(TimeSpan timeout, AsyncCallback callback, object state);
 9     Message EndReceive(IAsyncResult result);
10     bool EndTryReceive(IAsyncResult result, out Message message);
11     bool EndWaitForMessage(IAsyncResult result);
12     Message Receive();
13     Message Receive(TimeSpan timeout);
14     bool TryReceive(TimeSpan timeout, out Message message);
15     bool WaitForMessage(TimeSpan timeout);
16 }

从IInputChannel的定义可以看到,什么时候接收消息,什么时候等待,是不可控的,因为这些方法可以外部调用(一般是WCF框架自己调用)。简单地说,就是IInputChannel本身获取消息采用的是拉模式,而非推模式。为什么WCF框架不使用event模式(推模式)呢,我没有深入研究过,有知道的朋友还望赐教。这些方法调用时间不知,但若有可用消息时,不论何时调用Receive方法,我们都应该返回正确的Message,换句话说,从消息抵达到获取消息,可能有个时间差,这段时间内,消息数据不能丢失,于是我们应该有个临时存储消息数据的地方。考虑到消息数量和处理顺序,先入先出队列是个不错的选择。不出所料,示例中有个InputQueue类,该类相当复杂,不过万变不离其宗,UdpChannelListener将接收到的消息存入InputQueue,UdpInputChannel从InuptQueue中Receive消息。弄清楚这个,我便释然了。

上述模式只是实现消息从ChannelListener到Channel传递的其中一种方式,只要按规范实现WCF框架提供的关键接口,我们可以使用能想到的任何方式,比如使用event将消息接收工作转移到Channel类(更符合WCF对Channel的职责说明),当然接收到的消息仍旧需要用Receive方法去某个地方读取,因此消息的临时存储仍然必不可少。

由于最近比较忙,就先写到这里吧。示例中最核心的就是ChannelListener、InputQueue和AsyncResult类,AsyncResult类的作用也在关于IAsyncResult接口的CompletedSynchronously属性中有过阐述。若是全面铺开就太多了,有兴趣的朋友可以下载代码自己研究,有什么心得体会欢迎一起讨论。

注意用4G上网:这个地址是由网络中的GGSN或PGW分配的,是一个私网地址,但经过Gi防火墙会转换成公网IP再访问Internet。这样的话,手机侧看到的就是一个私网地址,而在互联网看来(比如站在微信服务器的角度来看),手机还有一个对应的公网IP。

 

转载请注明本文出处:http://www.cnblogs.com/newton/archive/2012/11/29/2793931.html

posted @ 2012-12-11 21:13  莱布尼茨  阅读(1856)  评论(0编辑  收藏  举报