共用方式為


網路基本概念

您必須針對任何網路功能的應用程式執行以下事項。

能力

若要使用網路功能,您必須將適當的功能元素新增至應用程式指令清單。 如果您的應用程式指令清單中未指定任何網路功能,您的應用程式將沒有網路功能,且任何連線到網路的嘗試都會失敗。

以下是最常用的網路功能。

能力 說明
網際網路用戶端 提供公共場所因特網和網路的輸出存取,例如機場和咖啡店。 大部分需要因特網存取的應用程式都應該使用這項功能。
internetClientServer 賦予應用程式從互聯網以及機場和咖啡館等公共場所的網路入站和出站存取權限。
私人網路客戶端伺服器 允許應用程式在使用者信任的位置(如家庭和工作)進行進入和輸出網路的存取。

在某些情況下,您的應用程式可能需要其他功能。

能力 說明
企業驗證 允許應用程式連線到需要網域認證的網路資源。 例如,從私人內部網路上的 SharePoint 伺服器擷取數據的應用程式。 透過這項功能,您的認證可用來存取需要認證的網路上的網路資源。 具有這項功能的應用程式可以在網路上模擬您。 您不需要這項功能,即可允許您的應用程式透過驗證 Proxy 存取因特網。

如需詳細資訊,請參閱 受限功能中的 Enterprise 能力場景說明文件
鄰近性 與接近電腦的裝置進行近距離通訊時,這項功能是必需的。 近距離鄰近性可以用於在附近裝置上發送或連接應用程式。

此功能可讓應用程式存取網路,以近距離連線到裝置,使用者同意傳送邀請或接受邀請。
共用使用者憑證 此功能可讓應用程式存取軟體和硬體憑證,例如智慧卡憑證。 在運行時間叫用這項功能時,用戶必須採取動作,例如插入卡片或選取憑證。

透過這項功能,您的軟體和硬體憑證或智慧卡會用於應用程式中的識別。 您的雇主、銀行或政府服務可以使用這項功能進行識別。

當您的應用程式不在前台時進行通訊

支援應用程式使用背景工作, 包含在應用程式不在前台時,如何使用背景工作來執行任務的一般資訊。 更具體來說,您的程式碼必須採取特殊步驟,以便在它不是目前的前台應用程式且當數據透過網路抵達時能收到通知。 您在 Windows 8 中為此使用控制通道觸發器,Windows 10 仍然支援它們。 使用控制通道觸發器的完整資訊可在這裡 找到。 Windows 10 中的新技術在某些情況下提供更好的功能並減少負擔,例如支援推送的串流套接字:套接字中介程式和套接字活動觸發器。

如果您的 app 使用 DatagramSocketStreamSocketStreamSocketListener,則您的應用程式可以將開啟套接字的擁有權轉移給系統提供的套接字代理程式,然後離開前景,甚至終止。 當在傳輸的套接字上建立連線或有流量到達該套接字時,您的應用程式或其指定的背景任務將會啟動。 如果您的應用程式未執行,則會啟動它。 套接字代理程式接著會使用 SocketActivityTrigger 通知您的應用程式, 新的流量已抵達。 您的應用程式會從套接字代理程式回收套接字,並處理套接字上的流量。 這表示當應用程式未主動處理網路流量時,會耗用較少的系統資源。

套接字代理程式旨在取代在適合的情況下使用的控制通道觸發程式,因為它提供相同的功能,但限制較少,且記憶體使用量較小。 套接字代理程式可以被非鎖屏應用程式使用,並且在手機上與其他裝置上的使用方式相同。 當流量到達時,應用程式不需要運行即可被套接字代理激活。 套接字代理支援監聽 TCP 套接字,而控制通道觸發則不支援此功能。

選擇網路觸發器

有些情境下,任一種觸發機制都適用。 當您選擇要在應用程式中使用哪種觸發程式時,請考慮下列建議。

如需如何使用套接字代理程式的詳細數據和範例,請參閱背景中的 網路通訊。

受保護的連線

安全套接字層 (SSL) 和較新的傳輸層安全性 (TLS) 是密碼編譯通訊協定,旨在提供網路通訊的驗證和加密。 這些通訊協議的設計目的是在傳送和接收網路數據時防止竊聽和竄改。 這些通訊協議會針對通訊協定交換使用用戶端-伺服器模型。 這些通訊協定也會使用數位證書和證書頒發機構單位來驗證伺服器是否為它聲稱的身分。

建立安全套接字連線

StreamSocket 物件可以設定為使用 SSL/TLS 進行客戶端與伺服器之間的通訊。 此 SSL/TLS 支援僅限於使用 StreamSocket 對象作為 SSL/TLS 交涉中的用戶端。 當收到傳入通訊時,您無法將 SSL/TLS 與由 StreamSocketListener 所建立的 StreamSocket 搭配使用,因為作為伺服器的 SSL/TLS 協商功能未由 StreamSocket 類別實作。

有兩種方式可以使用 SSL/TLS 來保護 StreamSocket 的連接:

  • ConnectAsync - 建立與網路服務的初始連線,並立即交涉以使用 SSL/TLS 進行所有通訊。
  • UpgradeToSslAsync - 一開始會連線到沒有加密的網路服務。 應用程式可能會傳送或接收數據。 然後,升級連線以針對所有進一步通訊使用 SSL/TLS。

SocketProtectionLevel 會指定應用程式想要建立或升級連線所需的套接字保護層級。 不過,所建立的連線的最終保護層級是在連線兩個端點之間的協商過程中決定的。 如果其他端點要求較低層級,結果可能是比您指定的保護層級低。

異步操作成功完成之後,您可以透過 StreamSocketinformation.ProtectionLevel 屬性,擷取 connectAsyncUpgradeToSslAsync 呼叫中使用的要求保護層級。 不過,這不會反映連線所使用的實際保護層級。

備註

您的程式代碼不應隱含依賴使用特定保護層級,或根據預設使用特定安全性層級的假設而定。 安全性環境會不斷變更,通訊協定和默認保護層級會隨著時間而變更,以避免使用具有已知弱點的通訊協定。 默認值可能會因個別計算機組態而有所不同,或根據已安裝的軟體和已套用的修補程式而定。 如果您的應用程式相依於特定安全性層級的使用,則您必須明確指定該層級,然後檢查以確定它實際上是在已建立的連線上使用。

使用 ConnectAsync

ConnectAsync 可用來建立與網路服務的初始連線,然後立即交涉以針對所有通訊使用 SSL/TLS。 有兩個 ConnectAsync 方法支持傳遞 protectionLevel 參數:

如果在呼叫上述任一 ConnectAsync 方法時,將 protectionLevel 參數設置為 Windows.Networking.Sockets.SocketProtectionLevel.Ssl,則必須建立 StreamSocket,以使用 SSL/TLS 進行加密。 此值需要加密,且絕不允許使用NULL 加密。

使用其中一個 ConnectAsync 方法時,通常會遵循相同的序列。

下列範例會建立 StreamSocket 並立即建立與網路服務的連線,進行協商以使用 SSL/TLS。 如果交涉成功,則會加密客戶端與網路伺服器之間使用 StreamSocket 的所有網路通訊。

using Windows.Networking;
using Windows.Networking.Sockets;

    // Define some variables and set values
    StreamSocket clientSocket = new StreamSocket();
     
    HostName serverHost = new HostName("www.contoso.com");
    string serverServiceName = "https";
    
    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 
    
    // Try to connect to contoso using HTTPS (port 443)
    try {

        // Call ConnectAsync method with SSL
        await clientSocket.ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel.Ssl);

        NotifyUser("Connected");
    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }
        
        NotifyUser("Connect failed with error: " + exception.Message);
        // Could retry the connection, but for this simple example
        // just close the socket.
        
        clientSocket.Dispose();
        clientSocket = null; 
    }
           
    // Add code to send and receive data using the clientSocket
    // and then close the clientSocket
#include <winrt/Windows.Networking.Sockets.h>

using namespace winrt;
...
    // Define some variables, and set values.
    Windows::Networking::Sockets::StreamSocket clientSocket;

    Windows::Networking::HostName serverHost{ L"www.contoso.com" };
    winrt::hstring serverServiceName{ L"https" };

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages.

    // Try to connect to the server using HTTPS and SSL (port 443).
    try
    {
        co_await clientSocket.ConnectAsync(serverHost, serverServiceName, Windows::Networking::Sockets::SocketProtectionLevel::Tls12);
        NotifyUser(L"Connected");
    }
    catch (winrt::hresult_error const& exception)
    {
        NotifyUser(L"Connect failed with error: " + exception.message());
        clientSocket = nullptr;
    }
    // Add code to send and receive data using the clientSocket,
    // then set the clientSocket to nullptr when done to close it.
using Windows::Networking;
using Windows::Networking::Sockets;

    // Define some variables and set values
    StreamSocket^ clientSocket = new ref StreamSocket();
 
    HostName^ serverHost = new ref HostName("www.contoso.com");
    String serverServiceName = "https";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to the server using HTTPS and SSL (port 443)
    task<void>(clientSocket->ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel::SSL)).then([this] (task<void> previousTask) {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();
            NotifyUser("Connected");
        }
        catch (Exception^ exception)
        {
            NotifyUser("Connect failed with error: " + exception->Message);
            
            clientSocket.Close();
            clientSocket = null;
        }
    });
    // Add code to send and receive data using the clientSocket
    // Then close the clientSocket when done

使用 UpgradeToSslAsync

當您的程式代碼使用 UpgradeToSslAsync時,它會先建立與網路服務的連線,而不需要加密。 應用程式可能會傳送或接收某些數據,然後升級連線以使用SSL/TLS進行所有進一步的通訊。

UpgradeToSslAsync 方法會採用兩個參數。 protectionLevel 參數表示所需的保護層級。 validationHostName 參數是升級至 SSL 時用於驗證的遠端網路目的地主機名。 一般而言,validationHostName 會是應用程式最初用來建立連線的相同主機名。 當呼叫 UpgradeToSslAsync時,如果 protectionLevel 參數設定為 Windows.System.Socket.SocketProtectionLevel.Ssl,則 StreamSocket 必須使用 SSL/TLS 進行加密,以保護套接字上的進一步通訊。 此值需要加密,且絕不允許使用NULL 加密。

UpgradeToSslAsync 方法搭配使用的一般順序如下:

下列範例會建立 StreamSocket,嘗試建立與網路服務的連線、傳送一些初始數據,然後交涉以使用 SSL/TLS。 如果交涉成功,則會加密用戶端與網路伺服器之間 StreamSocket 的所有網路通訊。

using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

    // Define some variables and set values
    StreamSocket clientSocket = new StreamSocket();
 
    HostName serverHost = new HostName("www.contoso.com");
    string serverServiceName = "http";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to contoso using HTTP (port 80)
    try {
        // Call ConnectAsync method with a plain socket
        await clientSocket.ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel.PlainSocket);

        NotifyUser("Connected");

    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }

        NotifyUser("Connect failed with error: " + exception.Message, NotifyType.ErrorMessage);
        // Could retry the connection, but for this simple example
        // just close the socket.

        clientSocket.Dispose();
        clientSocket = null; 
        return;
    }

    // Now try to send some data
    DataWriter writer = new DataWriter(clientSocket.OutputStream);
    string hello = "Hello, World! ☺ ";
    Int32 len = (int) writer.MeasureString(hello); // Gets the UTF-8 string length.
    writer.WriteInt32(len);
    writer.WriteString(hello);
    NotifyUser("Client: sending hello");

    try {
        // Call StoreAsync method to store the hello message
        await writer.StoreAsync();

        NotifyUser("Client: sent data");

        writer.DetachStream(); // Detach stream, if not, DataWriter destructor will close it.
    }
    catch (Exception exception) {
        NotifyUser("Store failed with error: " + exception.Message);
        // Could retry the store, but for this simple example
            // just close the socket.

            clientSocket.Dispose();
            clientSocket = null; 
            return;
    }

    // Now upgrade the client to use SSL
    try {
        // Try to upgrade to SSL
        await clientSocket.UpgradeToSslAsync(SocketProtectionLevel.Ssl, serverHost);

        NotifyUser("Client: upgrade to SSL completed");
           
        // Add code to send and receive data 
        // The close clientSocket when done
    }
    catch (Exception exception) {
        // If this is an unknown status it means that the error is fatal and retry will likely fail.
        if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
            throw;
        }

        NotifyUser("Upgrade to SSL failed with error: " + exception.Message);

        clientSocket.Dispose();
        clientSocket = null; 
        return;
    }
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.h>

using namespace winrt;
using namespace Windows::Storage::Streams;
...
    // Define some variables, and set values.
    Windows::Networking::Sockets::StreamSocket clientSocket;

    Windows::Networking::HostName serverHost{ L"www.contoso.com" };
    winrt::hstring serverServiceName{ L"https" };

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages. 

    // Try to connect to the server using HTTP (port 80).
    try
    {
        co_await clientSocket.ConnectAsync(serverHost, serverServiceName, Windows::Networking::Sockets::SocketProtectionLevel::PlainSocket);
        NotifyUser(L"Connected");
    }
    catch (winrt::hresult_error const& exception)
    {
        NotifyUser(L"Connect failed with error: " + exception.message());
        clientSocket = nullptr;
    }

    // Now, try to send some data.
    DataWriter writer{ clientSocket.OutputStream() };
    winrt::hstring hello{ L"Hello, World! ☺ " };
    uint32_t len{ writer.MeasureString(hello) }; // Gets the size of the string, in bytes.
    writer.WriteInt32(len);
    writer.WriteString(hello);
    NotifyUser(L"Client: sending hello");

    try
    {
        co_await writer.StoreAsync();
        NotifyUser(L"Client: sent hello");

        writer.DetachStream(); // Detach the stream when you want to continue using it; otherwise, the DataWriter destructor closes it.
    }
    catch (winrt::hresult_error const& exception)
    {
        NotifyUser(L"Store failed with error: " + exception.message());
        // We could retry the store operation. But, for this simple example, just close the socket by setting it to nullptr.
        clientSocket = nullptr;
        co_return;
    }

    // Now, upgrade the client to use SSL.
    try
    {
        co_await clientSocket.UpgradeToSslAsync(Windows::Networking::Sockets::SocketProtectionLevel::Tls12, serverHost);
        NotifyUser(L"Client: upgrade to SSL completed");

        // Add code to send and receive data using the clientSocket,
        // then set the clientSocket to nullptr when done to close it.
    }
    catch (winrt::hresult_error const& exception)
    {
        // If this is an unknown status, then the error is fatal and retry will likely fail.
        Windows::Networking::Sockets::SocketErrorStatus socketErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(exception.to_abi()) };
        if (socketErrorStatus == Windows::Networking::Sockets::SocketErrorStatus::Unknown)
        {
            throw;
        }

        NotifyUser(L"Upgrade to SSL failed with error: " + exception.message());
        // We could retry the store operation. But for this simple example, just close the socket by setting it to nullptr.
        clientSocket = nullptr;
        co_return;
    }
using Windows::Networking;
using Windows::Networking::Sockets;
using Windows::Storage::Streams;

    // Define some variables and set values
    StreamSocket^ clientSocket = new ref StreamSocket();
 
    Hostname^ serverHost = new ref HostName("www.contoso.com");
    String serverServiceName = "http";

    // For simplicity, the sample omits implementation of the
    // NotifyUser method used to display status and error messages 

    // Try to connect to contoso using HTTP (port 80)
    task<void>(clientSocket->ConnectAsync(serverHost, serverServiceName, SocketProtectionLevel::PlainSocket)).then([this] (task<void> previousTask) {
        try
        {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();
            NotifyUser("Connected");
        }
        catch (Exception^ exception)
        {
            NotifyUser("Connect failed with error: " + exception->Message);
 
            clientSocket->Close();
            clientSocket = null;
        }
    });
       
    // Now try to send some data
    DataWriter^ writer = new ref DataWriter(clientSocket.OutputStream);
    String hello = "Hello, World! ☺ ";
    Int32 len = (int) writer->MeasureString(hello); // Gets the UTF-8 string length.
    writer->writeInt32(len);
    writer->writeString(hello);
    NotifyUser("Client: sending hello");

    task<void>(writer->StoreAsync()).then([this] (task<void> previousTask) {
        try {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();

            NotifyUser("Client: sent hello");

            writer->DetachStream(); // Detach stream, if not, DataWriter destructor will close it.
       }
       catch (Exception^ exception) {
               NotifyUser("Store failed with error: " + exception->Message);
               // Could retry the store, but for this simple example
               // just close the socket.
 
               clientSocket->Close();
               clientSocket = null;
               return
       }
    });

    // Now upgrade the client to use SSL
    task<void>(clientSocket->UpgradeToSslAsync(clientSocket.SocketProtectionLevel.Ssl, serverHost)).then([this] (task<void> previousTask) {
        try {
            // Try getting all exceptions from the continuation chain above this point.
            previousTask.Get();

           NotifyUser("Client: upgrade to SSL completed");
           
           // Add code to send and receive data 
           // Then close clientSocket when done
        }
        catch (Exception^ exception) {
            // If this is an unknown status it means that the error is fatal and retry will likely fail.
            if (SocketError.GetStatus(exception.HResult) == SocketErrorStatus.Unknown) {
                throw;
            }

            NotifyUser("Upgrade to SSL failed with error: " + exception.Message);

            clientSocket->Close();
            clientSocket = null; 
            return;
        }
    });

建立安全的 WebSocket 連線

如同傳統的套接字連線,使用UWP應用程式的 StreamWebSocketMessageWebSocket 功能時,WebSocket 連線也可以使用傳輸層安全性 (TLS)/安全套接字層 (SSL) 加密。 在大部分情況下,您會想要使用安全的 WebSocket 連線。 這會增加連線成功的機會,因為許多 Proxy 會拒絕未加密的 WebSocket 連線。

如需如何建立或升級至網路服務的安全套接字連線的範例,請參閱 如何使用 TLS/SSL保護 WebSocket 連線。

除了 TLS/SSL 加密之外,伺服器可能需要 Sec-WebSocket-Protocol 標頭值來完成初始交握。 這個值,由 StreamWebSocketInformation.ProtocolMessageWebSocketInformation.Protocol 属性表示,表示聯機的通訊協定版本,並讓伺服器能夠正確地解譯開啟的交握,以及之後交換的數據。 使用此通訊協議資訊時,如果伺服器無法以安全的方式解譯傳入數據,則可以關閉連線。

如果來自用戶端的初始要求未包含此值,或提供的值不符合伺服器預期的值,則預期值會從伺服器傳送至 WebSocket 交握錯誤的用戶端。

認證

如何在透過網路連線時提供驗證認證。

使用 StreamSocket 類別提供客戶端憑證

Windows.Networking.Sockets.StreamSocket 類別支援使用 SSL/TLS 來驗證應用程式正在交談的伺服器。 在某些情況下,應用程式也需要使用TLS用戶端憑證向伺服器驗證自己。 在 Windows 10 中,您可以在 StreamSocket.Control 物件上提供用戶端憑證(這必須在 TLS 交握啟動之前設定)。 如果伺服器要求客戶端憑證,Windows 會以提供的憑證回應。

以下代碼段示範如何實作:

var socket = new StreamSocket();
Windows.Security.Cryptography.Certificates.Certificate certificate = await GetClientCert();
socket.Control.ClientCertificate = certificate;
await socket.ConnectAsync(destination, SocketProtectionLevel.Tls12);

將驗證認證提供給 Web 服務

讓應用程式能夠與安全 Web 服務互動的網路 API,各自提供自己的方法來初始化用戶端,或使用伺服器和 Proxy 驗證認證來設定要求標頭。 每個方法都會使用 PasswordCredential 物件來設定,指出使用這些認證的使用者名稱、密碼和資源。 下列表格提供這些 API 的對應關係:

WebSocket MessageWebSocketControl.ServerCredential
MessageWebSocketControl.ProxyCredential
StreamWebSocketControl.ServerCredential
StreamWebSocketControl.ProxyCredential
背景傳輸 BackgroundDownloader.ServerCredential
BackgroundDownloader.ProxyCredential
BackgroundUploader.ServerCredential
BackgroundUploader.ProxyCredential
新聞訂閱 SyndicationClient(PasswordCredential)
SyndicationClient.ServerCredential
SyndicationClient.ProxyCredential
AtomPub AtomPubClient(PasswordCredential)
AtomPubClient.ServerCredential
AtomPubClient.ProxyCredential

處理網路例外狀況

在程序設計的大部分區域中,例外狀況表示程式有一些缺陷所造成的重大問題或失敗。 在網路程序設計中,有一個額外的例外狀況來源:網路本身,以及網路通訊的性質。 網路通訊本質上是不可靠的,而且容易發生非預期的失敗。 對於每種應用程式使用網路的方式,您必須維護一些狀態資訊。此外,您的應用程式代碼必須透過更新這些狀態資訊來處理網路異常,並啟動適當的邏輯,以重新建立或重試因通訊失敗的連接。

當通用 Windows 應用程式擲回例外狀況時,您的例外狀況處理程式可以擷取例外狀況原因的詳細資訊,以進一步了解失敗並做出適當的決策。

每個語言投影都支援一種方法來存取這項更詳細的資訊。 例外狀況在通用 Windows 應用程式中表示為 HRESULT 值。 Winerror.h 包含檔案包含非常大量的可能 HRESULT 值清單,其中包含網路錯誤。

網路 API 支援不同的方法來擷取例外狀況原因的這項詳細資訊。

  • 某些 API 提供協助程式方法,可將例外狀況 HRESULT 值轉換為列舉值。
  • 其他 API 提供方法來擷取實際 HRESULT 值。
  • Windows 10 中的網路 API 改進