網路功能基本知識
如何為應用程式增加網路功能。
功能
若要使用網路功能,您必須在應用程式資訊清單新增適當的功能元素。 如果您的應用程式資訊清單中未指定任何網路功能,您的應用程式將缺乏網路功能,且任何連線到網路的嘗試都會失敗。
以下為最常用的網路功能。
功能 | 描述 |
---|---|
internetClient | 提供對網際網路和公共場所 (如機場和咖啡廳) 網路的傳出存取功能。 大部分需要網際網路存取的應用程式都應使用此功能。 |
internetClientServer | 提供應用程式對網際網路和公共場所 (如機場和咖啡廳) 網路的傳入和傳出網路存取功能。 |
privateNetworkClientServer | 提供應用程式在使用者信任場所 (如家中和工作場所) 的輸入和輸出網路存取功能。 |
在特定情況下,您的應用程式可能需要其他功能。
功能 | 描述 |
---|---|
enterpriseAuthentication | 允許應用程式連線至需要網域認證的網路資源。 例如,從私人內部網路上的 SharePoint 伺服器擷取資料的應用程式。 透過此功能,您可透過認證存取網路上需要認證的網路資源。 具有此功能的應用程式可在網路上模擬您的身分。 若要允許應用程式透過驗證 Proxy 來存取網際網路,您並不需要這項功能。 如需詳細資訊,請參閱文件中有關受限制的功能中的「企業」功能案例。 |
proximity | 需有此功能才可與電腦附近裝置進行近場鄰近 (near-field proximity) 通訊。 近場鄰近通訊可用於傳送或連線至附近裝置上的應用程式。 只要使用者同意傳送邀請或接受邀請,應用程式即可透過此功能存取網路,並連線至附近裝置。 |
sharedUserCertificates | 應用程式可透過此功能存取軟體和硬體憑證,例如智慧卡憑證。 在執行階段叫用此功能時,使用者必須採取相應動作,例如插入卡片或選取憑證。 透過此功能,軟體和硬體憑證或智慧卡可成為應用程式內的識別方式, 以供雇主、銀行或政府服務單位識別您的身分。 |
當應用程式不在前景時所進行的通訊
使用背景工作支援應用程式概述了如何在應用程式不在前景的情況下使用背景工作執行任務。 更具體來說,若應用程式並非目前的前景應用程式,您必須針對程式碼採取特殊步驟,才可於資料透過網路送達時收到通知。 在 Windows 8 中,您可以透過控制通道觸發程序進行通訊,在 Windows 10 中此方法也同樣適用。 關於控制通道觸發程序的完整資訊,請參閱此處。 在某些情況下,Windows 10 的新技術可提供更好的功能與較低的額外負荷,例如啟用推播的資料流通訊端:通訊端代理程式與通訊端活動觸發程序。
如果您的應用程式使用 DatagramSocket、StreamSocket 或 StreamSocketListener,則可將開啟通訊端的擁有權移轉至系統提供的通訊端代理程式,此時應用程式可以離開前景,甚至終止運作。 一旦與移轉的通訊端建立連線,或流量抵達該通訊端時,應用程式或其指定的背景工作便會啟動。 若應用程式當時並未執行,則會啟動應用程式。 接著,通訊端代理程式會透過 SocketActivityTrigger 通知應用程式新流量已抵達, 您的應用程式則會從通訊端代理程式回收通訊端,並處理通訊端上的流量。 藉此,應用程式不會主動處理網路流量,大幅減少系統資源消耗。
由於通訊端代理程式限制較少,記憶體使用量也較小,適用時可取代具相同功能的控制通道觸發程序。 除螢幕鎖定應用程式以外的應用程式皆可使用通訊端代理程式,且在手機與其他裝置上的使用方式均相同。 流量抵達時,即使應用程式未執行,也可由通訊端代理程式啟動, 而通訊端代理程式可支援接聽 TCP 通訊端,控制通道觸發程序則不支援此功能。
選擇網路觸發程序
在某些情況下,可使用任何觸發程序。 在為您的應用程式選擇使用何種觸發程序時,請考慮下列建議。
- 如果使用 IXMLHTTPRequest2、System.Net.Http.HttpClient 或 System.Net.Http.HttpClientHandler,則必須使用 ControlChannelTrigger。
- 如果使用已啟用推播的 StreamSockets,您可使用控制通道觸發程序,但應優先選擇 SocketActivityTrigger, 協助系統在未主動使用連線時釋放記憶體空間,同時降低電源需求。
- 如果想在應用程式未主動執行網路要求時將其記憶體使用量降到最低,請優先使用 SocketActivityTrigger。
- 如果希望應用程式能在系統處於連線待命模式時接收資料,請使用 SocketActivityTrigger。
如需通訊端代理程序的詳細資訊和使用範例,請參閱背景中的網路通訊。
受保護的連線
安全通訊端層 (SSL) 和較新的傳輸層安全性 (TLS) 是密碼編譯通訊協定,可針對網路通訊進行驗證與加密, 防止傳送和接收網路資料時遭竊聽和竄改。 這些通訊協定會透過用戶端-伺服器模型保護通訊協定交換過程, 並利用數位憑證和憑證授權單位,驗證伺服器是否符合其宣告身分。
建立安全通訊端連線
針對用戶端與伺服器之間的通訊,可設定 StreamSocket 物件以使用 SSL/TLS。 唯有使用 StreamSocket 物件作為 SSL/TLS 交涉用戶端時,可使用 SSL/TLS 功能。 由於伺服器的 SSL/TLS 交涉並非由 StreamSocket 類別執行,因此收到傳入通訊時,無法利用 StreamSocketListener 建立的 StreamSocket 使用 SSL/TLS。
有兩種方式可以利用 SSL/TLS 保護 StreamSocket 連線:
- ConnectAsync - 建立連至網路服務的初始連線,並立即交涉讓所有通訊使用 SSL/TLS。
- UpgradeToSslAsync - 在不加密的情況下初次連線至網路服務。 應用程式可能會傳送或接收資料, 並將所有進一步通訊升級為使用 SSL/TLS。
SocketProtectionLevel 指定應用程式想用來建立或升級連線的所需通訊端保護層級。 不過,已建立連線的最終保護層級,仍取決於連線中兩個端點間的交涉程序。 如果其他端點要求較低的層級,結果可能是比您指定的要更低的保護層級。
順利完成非同步作業後,您可以透過 StreamSocketinformation.ProtectionLevel 屬性擷取 ConnectAsync 或 UpgradeToSslAsync 呼叫中使用的要求保護層級。 不過,這不會反映連線所使用的實際保護層級。
注意
您的程式碼不應該以隱含方式依賴使用特定的保護層級,或是依預設使用提供的安全性層級的假設。 安全性概況經常變更,為避免使用含有已知弱點的通訊協定,通訊協定和預設保護層級會隨著時間變更。 預設值可能因個別機器組態而異,或根據已安裝的軟體和已套用的修補程式而定。 如果您的應用程式需要使用特定的安全性層級,則必須明確地指定層級,並確定它實際上已在建立的連線中使用。
使用 ConnectAsync
ConnectAsync 可用來建立具有網路服務的初始連線,然後為所有通訊立即交涉以使用 SSL/TLS。 可透過兩種 ConnectAsync 方法傳遞 protectionLevel 參數:
- ConnectAsync(EndpointPair, SocketProtectionLevel) - 在 StreamSocket 物件上啟動非同步操作,以連線至指定為 EndpointPair 物件與 SocketProtectionLevel的遠端網路目的地。
- ConnectAsync(HostName, String, SocketProtectionLevel) - 在 StreamSocket 物件上啟動非同步操作,以連線至遠端主機名稱、遠端服務名稱以及 SocketProtectionLevel 所指定的遠端目的地。
呼叫上述任一 ConnectAsync 方法時,如果 protectionLevel 參數設定為 Windows.Networking.Sockets.SocketProtectionLevel.Ssl,則必須建立 StreamSocket 以使用 SSL/TLS 進行加密。 此值必須加密,且禁止使用 Null 加密。
使用這些 ConnectAsync 方法的標準順序均相同。
- 建立一個 StreamSocket。
- 如果需要通訊端上的進階選項,請使用 StreamSocket.Control 屬性,取得與 StreamSocket 物件相關聯的 StreamSocketControl 執行個體。 在 StreamSocketControl 上設定屬性。
- 呼叫上述任一 ConnectAsync 方法以開始作業,連線至遠端目的地,並立即交涉 SSL/TLS 的使用。
- 非同步作業順利完成後,可藉由取得 StreamSocketinformation.ProtectionLevel 屬性來判定使用 ConnectAsync 實際交涉的 SSL 強度。
以下範例會建立一個 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。
- 如果需要通訊端上的進階選項,請使用 StreamSocket.Control 屬性,取得與 StreamSocket 物件相關聯的 StreamSocketControl 執行個體。 在 StreamSocketControl 上設定屬性。
- 如果需要傳送任何未加密資料,請於此時傳送。
- 呼叫 UpgradeToSslAsync 方法來開始作業,以將連線升級為使用 SSL/TLS。
- 非同步作業順利完成後,可藉由取得 StreamSocketinformation.ProtectionLevel 屬性來判定使用 UpgradeToSslAsync 實際交涉的 SSL 強度。
下列範例會建立一個 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 應用程式中的 StreamWebSocket 和 MessageWebSocket 功能時,也可以使用傳輸層安全性 (TLS)/安全通訊端層 (SSL) 加密 WebSocket 連線。 在大多數情況下,最好都使用安全的 WebSocket 連線, 如此可提高連線的成功機率,因為許多 Proxy 都會拒絕未加密的 WebSocket 連線。
關於如何針對網路服務建立或升級安全通訊端連線,相關範例請參閱如何使用使用 TLS/SSL 保護 WebSocket 連線。
除 TLS/SSL 加密外,伺服器還需要 Sec-WebSocket-Protocol 標頭值以完成初始交握, 標頭值以 StreamWebSocketInformation.Protocol 和 MessageWebSocketInformation.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 服務
每個網路 API 皆有各自的方法可初始化用戶端,或使用伺服器和 Proxy 驗證認證來設定標頭要求,讓應用程式能夠與安全 Web 服務互動。 每個方法均使用 PasswordCredential 物件,藉此表示使用這些認證的使用者名稱、密碼與資源。 下表列出這些 API 的對應:
處理網路例外狀況
在大部分的程式設計領域中,例外狀況表示程式有瑕疵而導致重大問題或失敗。 而在網路程式設計中,網路本身及網路通訊的性質則是另一種導致例外狀況的可能原因。 網路通訊本質上並不可靠,且容易發生未預期的失敗。 您必須針對應用程式使用網路的各種方式維護狀態資訊。此外,為了處理網路例外狀況,您必須更新程式碼的狀態資訊,並為應用程式起始適當邏輯,以重新建立通訊或重新嘗試已失敗的通訊。
當 Windows 通用應用程式擲回例外狀況時,例外狀況處理常式可取出例外狀況的詳細資訊,進一步瞭解失敗原因並做出適當決策。
每種語言均可回傳此類詳細資訊並供存取。 在 Windows 通用應用程式中,例外狀況將回傳為 HRESULT 值。 Winerror.h 所包含的檔案含有極大量可能的 HRESULT 值清單,包括網路錯誤。
網路 API 可透過不同方法,取出例外狀況發生原因的詳細資訊。
- 某些 API 提供協助程式方法,可將例外狀況的 HRESULT 值轉換為列舉值。
- 其他 API 則可擷取實際 HRESULT 值。