UDP 傳輸範例示範如何實作 UDP 單播和多播,將其作為 Windows Communication Foundation (WCF) 的自訂傳輸方式。 此範例說明在 WCF 中建立自定義傳輸的建議程式,方法是使用通道架構和遵循 WCF 最佳做法。 建立自訂傳輸的步驟如下:
決定您的 ChannelFactory 和 ChannelListener 支援哪個通道 訊息交換模式 (IOutputChannel、IInputChannel、IDuplexChannel、IRequestChannel 或 IReplyChannel)。 然後決定是否支持這些介面的會話變化。
建立支援訊息交換模式的通道處理站和接聽程式。
請確定任何網路特定的例外狀況會正規化為的適當衍生類別 CommunicationException。
新增綁定項延伸區段,以將新的綁定項公開至組態系統。
新增元數據延伸模組,以與其他端點通訊功能。
根據定義完善的配置檔,新增預先設定綁定項堆疊的系結。 如需詳細資訊,請參閱 新增標準系結。
新增系結區段和系結組態專案,以將系結公開至組態系統。 如需詳細資訊,請參閱 新增組態支援。
訊息交換模式
撰寫自定義傳輸的第一個步驟是決定傳輸所需的訊息交換模式(MEP)。 有三個 MEP 可供選擇:
數據報 (IInputChannel/IOutputChannel)
使用數據報 MEP 時,用戶端會使用「引發和忘記」交換來傳送訊息。 火災和忘記交換是需要成功傳遞頻外確認的交換。 訊息可能會在傳輸中遺失,且永遠不會到達服務。 如果傳送作業在用戶端成功完成,則不保證遠端端點已收到訊息。 Datagram 是傳訊的基本建置組塊,因為您可以在其中建置自己的通訊協定,包括可靠的通訊協定和安全通訊協定。 用戶端數據報通道會實作 IOutputChannel 介面和服務數據報通道實作 IInputChannel 介面。
Request-Response (IRequestChannel/IReplyChannel)
在這個 MEP 中,訊息會被傳送,並且會收到回覆。 此模式包含要求與回應配對。 要求回應呼叫的範例包括遠端過程調用 (RPC) 和瀏覽器 GET。 此模式也稱為半雙工。 在此 MEP 中,用戶端通道實作 IRequestChannel,而服務通道則實作 IReplyChannel。
雙工 (IDuplexChannel)
雙工 MEP 允許用戶端傳送任意數目的訊息,並以任何順序接收。 雙工 MEP 就像電話交談一樣,其中所說的每個字都是一則訊息。 由於雙方都可以在此 MEP 中傳送和接收,因此客戶端和服務通道所實作的介面是 IDuplexChannel。
每個 MEP 也可以支援會話。 會話敏感通道所提供的新增功能在於,它確保通道上所有發送和接收的訊息均相互關聯。 Request-Response 模式是一個獨立的雙訊息會話,因為請求和回覆是相互關聯的。 相反地,支援會話的 Request-Response 模式表示該通道上的所有要求/回應組彼此相互關聯。 這總共提供六個 MEP—Datagram、Request-Response、Duplex、Datagram 搭配會話、Request-Response 搭配會話,以及 Duplex 搭配會話進行選擇。
備註
針對UDP傳輸,唯一支援的MEP是Datagram,因為UDP原本就是「引發和忘記」通訊協定。
ICommunicationObject 和 WCF 物件生命週期
WCF 有一個通用的狀態機器,用於管理 IChannel、IChannelFactory 和 IChannelListener 這些用於通訊的物件的生命週期。 有五種狀態可以存在這些通訊物件。 這些狀態是以 CommunicationState 列舉表示,如下所示:
已建立:這是ICommunicationObject第一次被實例化時的狀態。 此狀態中不會發生任何輸入/輸出 (I/O)。
開啟:呼叫 時 Open ,物件會轉換成此狀態。 此時,屬性會不可變,而且輸入/輸出可以開始。 此轉換僅適用於「已建立」狀態。
開啟:當開啟的進程完成時,物件會轉換成此狀態。 此轉換僅適用於開啟狀態。 此時,物件完全可用於傳輸。
關閉:呼叫 Close 時,物件會轉換成此狀態,以進行正常關機。 此轉換僅能從 [開啟] 狀態生效。
已關閉:物件處於「已關閉」狀態時不再可用。 一般而言,大部分的組態仍可供檢查,但無法進行通訊。 此狀態相當於已被棄置。
故障:在故障狀態下,對象可供檢查,但無法使用。 發生無法復原的錯誤時,物件會轉換成這個狀態。 這個狀態的唯一有效轉換是進入
Closed
狀態。
每個狀態轉換都會觸發事件。 Abort方法可隨時呼叫,並使物件立即從其目前狀態轉換至關閉狀態。 呼叫 Abort 會終止任何未完成的工作。
通道處理站和通道接聽程式
撰寫自定義傳輸的下一步驟是為用戶端通道建立IChannelFactory的實作,以及為服務通道建立IChannelListener的實作。 通道層會使用工廠模式來建構通道。 WCF 提供基類協助工具來支援此過程。
類別 CommunicationObject 會實作 ICommunicationObject 並強制執行先前在步驟 2 中所述的狀態機器。
類別ChannelManagerBase實作CommunicationObject並提供ChannelFactoryBase和ChannelListenerBase的統一基類。 類別 ChannelManagerBase 與 ChannelBase 搭配運作,ChannelBase 是一個實作了 的基類。
類別 ChannelFactoryBase 實作了 ChannelManagerBase 和 IChannelFactory,並將
CreateChannel
的多載合併成一個OnCreateChannel
抽象方法。類別 ChannelListenerBase 會實作 IChannelListener。 它負責基本狀態管理。
在此範例中,Factory 實作會包含在 UdpChannelFactory.cs 中,而接聽程式實作則包含在UdpChannelListener.cs中。 實作位於 UdpOutputChannel.cs 和 UdpInputChannel.cs 檔案中。
UDP 通道工廠
UdpChannelFactory
是衍生自 ChannelFactoryBase。 範例覆寫GetProperty,以提供訊息編碼器的版本存取。 此範例也會覆寫 OnClose ,使我們能夠在狀態機器轉換時,卸除 BufferManager 的實例。
UDP 輸出通道
UdpOutputChannel
實施 IOutputChannel。 建構函式會驗證自變數,並根據傳入的 建構目的地 EndPoint 物件 EndpointAddress 。
this.socket = new Socket(this.remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
通道可以正常地或不正常地關閉。 如果通道正常關閉,則套接字會關閉,並且會呼叫基類 OnClose
方法。 如果此動作拋出例外狀況,基礎結構會呼叫 Abort
以清理通道。
this.socket.Close(0);
然後,我們會實作 Send()
和 BeginSend()
/EndSend()
。 這會分成兩個主要區段。 首先,我們會將訊息串行化為位元組陣列。
ArraySegment<byte> messageBuffer = EncodeMessage(message);
然後,我們會在線路上傳送產生的數據。
this.socket.SendTo(messageBuffer.Array, messageBuffer.Offset, messageBuffer.Count, SocketFlags.None, this.remoteEndPoint);
UDP通道監聽器
範例所實作的UdpChannelListener
衍生自ChannelListenerBase類別。 它會使用單一 UDP 套接字來接收數據報。 方法 OnOpen
使用 UDP 套接字在異步迴圈中接收資料。 然後,數據會使用訊息編碼架構轉換成訊息。
message = MessageEncoderFactory.Encoder.ReadMessage(new ArraySegment<byte>(buffer, 0, count), bufferManager);
因為相同的數據報通道代表從許多來源送達的訊息, UdpChannelListener
因此 是單一接聽程式。 最多只有一個作用中的 IChannel 與這個接聽程式相關聯。 只有當 AcceptChannel
方法傳回的通道被處置後,此範例才會產生另一個通道。 當收到訊息時,它會被排入此單例通道的佇列中。
UdpInputChannel
類別 UdpInputChannel
會實作 IInputChannel
。 它是由由套接字填入 UdpChannelListener
的傳入訊息佇列所組成。 這些訊息會由 IInputChannel.Receive
方法解除佇列。
新增綁定項
現在已建置工廠和管道,我們必須透過綁定將其公開給 ServiceModel 執行環境。 系結是綁定項的集合,表示與服務地址相關聯的通訊堆棧。 堆疊中的每個項目都會以 <綁定> 項表示。
在範例中,綁定項是 UdpTransportBindingElement
,其衍生自 TransportBindingElement。 它會覆寫下列方法,以建置與我們的系結相關聯的處理站。
public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
return (IChannelFactory<TChannel>)(object)new UdpChannelFactory(this, context);
}
public IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
return (IChannelListener<TChannel>)(object)new UdpChannelListener(this, context);
}
它也包含用於複製 BindingElement
以及傳回我們的方案 (soap.udp) 的成員。
新增傳輸綁定項的元數據支援
若要將交通運輸整合到元數據系統中,我們必須支持政策的匯入和匯出。 這可讓我們透過 ServiceModel 元數據公用程式工具 (Svcutil.exe) 產生系結的用戶端。
新增 WSDL 支援
系結中的傳輸綁定項負責匯出和匯入元數據中的尋址資訊。 使用SOAP系結時,傳輸綁定項也應該在元數據中匯出正確的傳輸URI。
WSDL 匯出
若要匯出尋址資訊,UdpTransportBindingElement
會實作 IWsdlExportExtension
介面。 方法 ExportEndpoint
會將正確的尋址資訊新增至 WSDL 埠。
if (context.WsdlPort != null)
{
AddAddressToWsdlPort(context.WsdlPort, context.Endpoint.Address, encodingBindingElement.MessageVersion.Addressing);
}
方法 UdpTransportBindingElement
的實作 ExportEndpoint
也會在端點使用SOAP系結時匯出傳輸URI。
WsdlNS.SoapBinding soapBinding = GetSoapBinding(context, exporter);
if (soapBinding != null)
{
soapBinding.Transport = UdpPolicyStrings.UdpNamespace;
}
WSDL 匯入
若要擴充 WSDL 匯入系統來處理匯入地址,我們必須將下列組態新增至 Svcutil.exe 的組態檔,如 Svcutil.exe.config 檔案所示。
<configuration>
<system.serviceModel>
<client>
<metadata>
<policyImporters>
<extension type=" Microsoft.ServiceModel.Samples.UdpBindingElementImporter, UdpTransport" />
</policyImporters>
</metadata>
</client>
</system.serviceModel>
</configuration>
執行 Svcutil.exe時,有兩個選項可用來載入 WSDL 匯入延伸模組 Svcutil.exe:
使用 /SvcutilConfig:<file> 將 Svcutil.exe 指向我們的配置檔案。
將組態區段新增至 Svcutil.exe.config,此位置位於與 Svcutil.exe相同的目錄中。
UdpBindingElementImporter
類型實作了 IWsdlImportExtension
介面。 方法 ImportEndpoint
會從 WSDL 埠匯入位址。
BindingElementCollection bindingElements = context.Endpoint.Binding.CreateBindingElements();
TransportBindingElement transportBindingElement = bindingElements.Find<TransportBindingElement>();
if (transportBindingElement is UdpTransportBindingElement)
{
ImportAddress(context);
}
新增政策支援
自定義綁定項可以在服務端點的 WSDL 系結中匯出原則判斷提示,以表示該綁定項的功能。
原則匯出
UdpTransportBindingElement
型別實作 IPolicyExportExtension
以增加對匯出策略的支援。 因此,System.ServiceModel.MetadataExporter
在任何包含它的系結的政策制定中都包含 UdpTransportBindingElement
。
在IPolicyExportExtension.ExportPolicy
中,我們新增了一個針對UDP的判斷提示,如果我們處於多播模式,則再新增一個判斷提示。 這是因為多播模式會影響通訊堆疊的建構方式,因此必須在兩端之間協調。
ICollection<XmlElement> bindingAssertions = context.GetBindingAssertions();
XmlDocument xmlDocument = new XmlDocument();
bindingAssertions.Add(xmlDocument.CreateElement(
UdpPolicyStrings.Prefix, UdpPolicyStrings.TransportAssertion, UdpPolicyStrings.UdpNamespace));
if (Multicast)
{
bindingAssertions.Add(xmlDocument.CreateElement(
UdpPolicyStrings.Prefix,
UdpPolicyStrings.MulticastAssertion,
UdpPolicyStrings.UdpNamespace));
}
由於自定義傳輸綁定元素負責處理定址,IPolicyExportExtension
上的 UdpTransportBindingElement
實作也必須處理匯出適當的 WS-Addressing 政策斷言,以指出所使用的 WS-Addressing 版本。
AddWSAddressingAssertion(context, encodingBindingElement.MessageVersion.Addressing);
政策匯入
若要擴充原則匯入系統,我們必須將下列組態新增至 Svcutil.exe 的組態檔,如 Svcutil.exe.config 檔案所示。
<configuration>
<system.serviceModel>
<client>
<metadata>
<policyImporters>
<extension type=" Microsoft.ServiceModel.Samples.UdpBindingElementImporter, UdpTransport" />
</policyImporters>
</metadata>
</client>
</system.serviceModel>
</configuration>
然後,我們實作 IPolicyImporterExtension
從已註冊的類別 UdpBindingElementImporter
。 在 ImportPolicy()
中,我們查看命名空間中的斷言,處理用於產生傳輸的斷言,並檢查其是否為多播。 我們也必須從綁定斷言的清單中移除我們處理的斷言。 同樣地,執行 Svcutil.exe時,整合有兩個選項:
使用 /SvcutilConfig:<file> 將 Svcutil.exe 指向我們的配置檔案。
將組態區段新增至 Svcutil.exe.config,此位置位於與 Svcutil.exe相同的目錄中。
新增標準系結
我們的綁定項可以透過下列兩種方式使用:
透過自定義系結:自定義系結可讓用戶根據一組任意綁定項建立自己的系結。
使用系統提供的系結,其中包含我們的綁定項。 WCF 提供許多系統定義的系結,例如
BasicHttpBinding
、NetTcpBinding
和WsHttpBinding
。 每個系結都與定義完善的描述檔相關聯。
這個範例在SampleProfileUdpBinding
中實作設定檔綁定,並衍生自Binding。
SampleProfileUdpBinding
內最多包含四個綁定項:UdpTransportBindingElement
、 TextMessageEncodingBindingElement CompositeDuplexBindingElement
和 ReliableSessionBindingElement
。
public override BindingElementCollection CreateBindingElements()
{
BindingElementCollection bindingElements = new BindingElementCollection();
if (ReliableSessionEnabled)
{
bindingElements.Add(session);
bindingElements.Add(compositeDuplex);
}
bindingElements.Add(encoding);
bindingElements.Add(transport);
return bindingElements.Clone();
}
新增自訂標準綁定匯入工具
根據預設,Svcutil.exe 和 WsdlImporter
型別會辨識和匯入系統定義的系結。 否則,系結會匯入為 CustomBinding
實例。 若要啟用 Svcutil.exe 並讓 WsdlImporter
能匯入 SampleProfileUdpBinding
,UdpBindingElementImporter
也充當自定義標準系結匯入器。
自訂標準系結匯入器會在 ImportEndpoint
介面上實作 IWsdlImportExtension
方法,以檢查從元數據匯入的 CustomBinding
實例,以查看它是否可能由特定標準系結生成。
if (context.Endpoint.Binding is CustomBinding)
{
Binding binding;
if (transportBindingElement is UdpTransportBindingElement)
{
//if TryCreate is true, the CustomBinding will be replace by a SampleProfileUdpBinding in the
//generated config file for better typed generation.
if (SampleProfileUdpBinding.TryCreate(bindingElements, out binding))
{
binding.Name = context.Endpoint.Binding.Name;
binding.Namespace = context.Endpoint.Binding.Namespace;
context.Endpoint.Binding = binding;
}
}
}
一般而言,實作自定義標準系結匯入工具牽涉到檢查匯入綁定項的屬性,以確認只有標準系結可以設定的屬性已變更,而所有其他屬性都是其預設值。 實作標準系結匯入工具的基本策略是建立標準系結的實例、將系結專案的屬性傳播至標準系結支援的標準系結實例,以及比較標準系結中的綁定項與匯入綁定項。
新增組態支援
若要透過組態公開傳輸,我們必須實作兩個組態區段。 第一個是 BindingElementExtensionElement
的 UdpTransportBindingElement
。 如此一來, CustomBinding
實作就可以參考我們的綁定項。 第二個Configuration
是我們的SampleProfileUdpBinding
。
綁定元素延伸元素
區段 UdpTransportElement
是將 BindingElementExtensionElement
公開給組態系統的UdpTransportBindingElement
。 透過一些基本覆寫,我們會定義組態區段名稱、綁定項的類型,以及如何建立綁定項。 然後,我們可以在組態檔中註冊延伸模組區段,如下列程式代碼所示。
<configuration>
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="udpTransport" type="Microsoft.ServiceModel.Samples.UdpTransportElement, UdpTransport" />
</bindingElementExtensions>
</extensions>
</system.serviceModel>
</configuration>
擴充套件可以從自定義綁定中引用,以使用 UDP 作為傳輸方式。
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding configurationName="UdpCustomBinding">
<udpTransport/>
</binding>
</customBinding>
</bindings>
</system.serviceModel>
</configuration>
綁定區段
區段 SampleProfileUdpBindingCollectionElement
是將 StandardBindingCollectionElement
公開給組態系統的SampleProfileUdpBinding
。 大部分的實作會委派給衍生自 SampleProfileUdpBindingConfigurationElement
的 StandardBindingElement
。
SampleProfileUdpBindingConfigurationElement
具有與SampleProfileUdpBinding
屬性相對應的屬性,並且有函數從ConfigurationElement
綁定進行映射。 最後,覆寫 OnApplyConfiguration
中的 SampleProfileUdpBinding
方法,如下列範例程式代碼所示。
protected override void OnApplyConfiguration(string configurationName)
{
if (binding == null)
throw new ArgumentNullException("binding");
if (binding.GetType() != typeof(SampleProfileUdpBinding))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
"Invalid type for binding. Expected type: {0}. Type passed in: {1}.",
typeof(SampleProfileUdpBinding).AssemblyQualifiedName,
binding.GetType().AssemblyQualifiedName));
}
SampleProfileUdpBinding udpBinding = (SampleProfileUdpBinding)binding;
udpBinding.OrderedSession = this.OrderedSession;
udpBinding.ReliableSessionEnabled = this.ReliableSessionEnabled;
udpBinding.SessionInactivityTimeout = this.SessionInactivityTimeout;
if (this.ClientBaseAddress != null)
udpBinding.ClientBaseAddress = ClientBaseAddress;
}
若要向組態系統註冊此處理程式,我們會將下一節新增至相關的組態檔。
<configuration>
<configSections>
<sectionGroup name="system.serviceModel">
<sectionGroup name="bindings">
<section name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
</sectionGroup>
</sectionGroup>
</configSections>
</configuration>
然後,您可以從 serviceModel 組態區段參考它。
<configuration>
<system.serviceModel>
<client>
<endpoint configurationName="calculator"
address="soap.udp://localhost:8001/"
bindingConfiguration="CalculatorServer"
binding="sampleProfileUdpBinding"
contract= "Microsoft.ServiceModel.Samples.ICalculatorContract">
</endpoint>
</client>
</system.serviceModel>
</configuration>
UDP 測試服務和用戶端
使用這個範例傳輸的測試程序代碼可在 UdpTestService 和 UdpTestClient 目錄中取得。 服務程式代碼是由兩個測試所組成:一個測試會設定來自程式代碼的系結和端點,另一個則透過組態加以設定。 這兩個測試都會使用兩個端點。 一個端點使用 SampleUdpProfileBinding
並將 <reliableSession> 設定為 true
。 另一個端點會搭配 UdpTransportBindingElement
使用自定義系結。 這相當於使用 SampleUdpProfileBinding
並將 <reliableSession> 設定為 false
。 這兩項測試都會建立服務、為每個系結新增端點、開啟服務,然後等候使用者按 ENTER 鍵再關閉服務。
當您啟動服務測試應用程式時,應該會看到下列輸出。
Testing Udp From Code.
Service is started from code...
Press <ENTER> to terminate the service and start service from config...
然後,您可以針對已發佈的端點執行測試用戶端應用程式。 用戶端應用程式會為每個端點建立用戶端,並將五則訊息傳送至每個端點。 下列輸出位於用戶端上。
Testing Udp From Imported Files Generated By SvcUtil.
0
3
6
9
12
Press <ENTER> to complete test.
以下是服務的完整輸出。
Service is started from code...
Press <ENTER> to terminate the service and start service from config...
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
adding 0 + 0
adding 1 + 2
adding 2 + 4
adding 3 + 6
adding 4 + 8
若要針對使用組態發佈的端點執行用戶端應用程式,請在服務上按 ENTER,然後再次執行測試用戶端。 您應該會在服務上看到下列輸出。
Testing Udp From Config.
Service is started from config...
Press <ENTER> to terminate the service and exit...
再次執行客戶端會產生與上述結果相同的結果。
若要使用 Svcutil.exe重新產生用戶端程式代碼和組態,請啟動服務應用程式,然後從範例的根目錄執行下列 Svcutil.exe。
svcutil http://localhost:8000/udpsample/ /reference:UdpTransport\bin\UdpTransport.dll /svcutilConfig:svcutil.exe.config
請注意,Svcutil.exe 不會產生的 SampleProfileUdpBinding
系結延伸模組組態,因此您必須手動新增它。
<configuration>
<system.serviceModel>
<extensions>
<!-- This was added manually because svcutil.exe does not add this extension to the file -->
<bindingExtensions>
<add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
</bindingExtensions>
</extensions>
</system.serviceModel>
</configuration>
要設定、建置和執行範例,請執行以下步驟:
若要建置解決方案,請遵循 建置 Windows Communication Foundation 範例中的指示。
若要在單一或跨計算機組態中執行範例,請遵循執行 Windows Communication Foundation 範例 中的指示。
請參閱上述
一節。