共用方式為


傳輸協議:UDP

UDP 傳輸範例示範如何實作 UDP 單播和多播,將其作為 Windows Communication Foundation (WCF) 的自訂傳輸方式。 此範例說明在 WCF 中建立自定義傳輸的建議程式,方法是使用通道架構和遵循 WCF 最佳做法。 建立自訂傳輸的步驟如下:

  1. 決定您的 ChannelFactory 和 ChannelListener 支援哪個通道 訊息交換模式 (IOutputChannel、IInputChannel、IDuplexChannel、IRequestChannel 或 IReplyChannel)。 然後決定是否支持這些介面的會話變化。

  2. 建立支援訊息交換模式的通道處理站和接聽程式。

  3. 請確定任何網路特定的例外狀況會正規化為的適當衍生類別 CommunicationException

  4. <綁定> 元素新增至通道堆疊,以加入自定義傳輸。 如需詳細資訊,請參閱 新增綁定項

  5. 新增綁定項延伸區段,以將新的綁定項公開至組態系統。

  6. 新增元數據延伸模組,以與其他端點通訊功能。

  7. 根據定義完善的配置檔,新增預先設定綁定項堆疊的系結。 如需詳細資訊,請參閱 新增標準系結

  8. 新增系結區段和系結組態專案,以將系結公開至組態系統。 如需詳細資訊,請參閱 新增組態支援

訊息交換模式

撰寫自定義傳輸的第一個步驟是決定傳輸所需的訊息交換模式(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 有一個通用的狀態機器,用於管理 IChannelIChannelFactoryIChannelListener 這些用於通訊的物件的生命週期。 有五種狀態可以存在這些通訊物件。 這些狀態是以 CommunicationState 列舉表示,如下所示:

  • 已建立:這是ICommunicationObject第一次被實例化時的狀態。 此狀態中不會發生任何輸入/輸出 (I/O)。

  • 開啟:呼叫 時 Open ,物件會轉換成此狀態。 此時,屬性會不可變,而且輸入/輸出可以開始。 此轉換僅適用於「已建立」狀態。

  • 開啟:當開啟的進程完成時,物件會轉換成此狀態。 此轉換僅適用於開啟狀態。 此時,物件完全可用於傳輸。

  • 關閉:呼叫 Close 時,物件會轉換成此狀態,以進行正常關機。 此轉換僅能從 [開啟] 狀態生效。

  • 已關閉:物件處於「已關閉」狀態時不再可用。 一般而言,大部分的組態仍可供檢查,但無法進行通訊。 此狀態相當於已被棄置。

  • 故障:在故障狀態下,對象可供檢查,但無法使用。 發生無法復原的錯誤時,物件會轉換成這個狀態。 這個狀態的唯一有效轉換是進入 Closed 狀態。

每個狀態轉換都會觸發事件。 Abort方法可隨時呼叫,並使物件立即從其目前狀態轉換至關閉狀態。 呼叫 Abort 會終止任何未完成的工作。

通道處理站和通道接聽程式

撰寫自定義傳輸的下一步驟是為用戶端通道建立IChannelFactory的實作,以及為服務通道建立IChannelListener的實作。 通道層會使用工廠模式來建構通道。 WCF 提供基類協助工具來支援此過程。

在此範例中,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:

  1. 使用 /SvcutilConfig:<file> 將 Svcutil.exe 指向我們的配置檔案。

  2. 將組態區段新增至 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時,整合有兩個選項:

  1. 使用 /SvcutilConfig:<file> 將 Svcutil.exe 指向我們的配置檔案。

  2. 將組態區段新增至 Svcutil.exe.config,此位置位於與 Svcutil.exe相同的目錄中。

新增標準系結

我們的綁定項可以透過下列兩種方式使用:

  • 透過自定義系結:自定義系結可讓用戶根據一組任意綁定項建立自己的系結。

  • 使用系統提供的系結,其中包含我們的綁定項。 WCF 提供許多系統定義的系結,例如 BasicHttpBindingNetTcpBindingWsHttpBinding。 每個系結都與定義完善的描述檔相關聯。

這個範例在SampleProfileUdpBinding中實作設定檔綁定,並衍生自BindingSampleProfileUdpBinding內最多包含四個綁定項:UdpTransportBindingElementTextMessageEncodingBindingElement CompositeDuplexBindingElementReliableSessionBindingElement

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 能匯入 SampleProfileUdpBindingUdpBindingElementImporter 也充當自定義標準系結匯入器。

自訂標準系結匯入器會在 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;
        }
    }
}

一般而言,實作自定義標準系結匯入工具牽涉到檢查匯入綁定項的屬性,以確認只有標準系結可以設定的屬性已變更,而所有其他屬性都是其預設值。 實作標準系結匯入工具的基本策略是建立標準系結的實例、將系結專案的屬性傳播至標準系結支援的標準系結實例,以及比較標準系結中的綁定項與匯入綁定項。

新增組態支援

若要透過組態公開傳輸,我們必須實作兩個組態區段。 第一個是 BindingElementExtensionElementUdpTransportBindingElement。 如此一來, 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。 大部分的實作會委派給衍生自 SampleProfileUdpBindingConfigurationElementStandardBindingElementSampleProfileUdpBindingConfigurationElement具有與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>

要設定、建置和執行範例,請執行以下步驟:

  1. 若要建置解決方案,請遵循 建置 Windows Communication Foundation 範例中的指示。

  2. 若要在單一或跨計算機組態中執行範例,請遵循執行 Windows Communication Foundation 範例 中的指示。

  3. 請參閱上述一節。