套接字是一種低階資料傳輸技術,其上實作了許多網路協定。 Windows 提供 TCP 與 UDP Socket 類別,適用於用戶端-伺服器或點對點應用程式,不論是長時間維持連線,還是不需要已建立的連線。
本主題著重說明如何使用位於 Windows.Networking.Sockets 命名空間中的 Windows 通訊端類別。 但你也可以在 Windows 應用程式中使用 Windows Sockets 2(Winsock)。
Note
由於網路隔離,Windows 不允許在同一台機器上運行的兩個 Windows 應用程式之間建立 socket 連線(Sockets 或 WinSock);無論是透過本地迴圈位址(127.0.0.1),或是明確指定本地 IP 位址。 關於 Windows 應用程式之間通訊機制的詳細資訊,請參見應用程式間通訊。
建立一個基本的 TCP socket 用戶端與伺服器
TCP(傳輸控制協定)套接字提供雙向的低階網路資料傳輸,以滿足長壽命連線的需求。 TCP 套接字是大多數網際網路協定所使用的底層功能。 為了展示基本的 TCP 操作,以下範例程式碼展示了 StreamSocket 與 StreamSocketListener 透過 TCP 傳送與接收資料,形成一個 echo 客戶端與伺服器。
為了盡量減少運作環節,並暫時避免網路隔離問題——建立一個新專案,並將用戶端與伺服器程式碼放在同一個專案中。
你需要 在專案中宣告應用程式功能 。 打開你的應用程式套件清單原始檔案(該檔案),Package.appxmanifest在能力標籤中勾選私有網路(客戶端與伺服器)。 這就是它在 Package.appxmanifest 標記中的樣子。
<Capability Name="privateNetworkClientServer" />
如果您是透過網際網路連線,可以宣告 internetClientServer,而不是 privateNetworkClientServer。 StreamSocket 和 StreamSocketListener 都需要宣告上述一項應用程式功能。
使用 TCP socket 的 echo 用戶端與伺服器
建立一個 StreamSocketListener 並開始監聽 TCP 連線的進站。 每當用戶端與 StreamSocketListerner 建立連線時,都會觸發 StreamSocketListener.ConnectionReceived 事件。
同時建立 StreamSocket,建立與伺服器的連線,發送請求,並接收回應。
建立一個名為 的新StreamSocketAndListenerPage。 把 XAML 標記放進 StreamSocketAndListenerPage.xaml,然後把命令式程式碼放進 StreamSocketAndListenerPage 類別裡。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Margin="9.6,0" Style="{StaticResource TitleTextBlockStyle}" Text="TCP socket example"/>
<TextBlock Margin="7.2,0,0,0" Style="{StaticResource HeaderTextBlockStyle}" Text="StreamSocket & StreamSocketListener"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="9.6" Style="{StaticResource SubtitleTextBlockStyle}" Text="client"/>
<ListBox x:Name="clientListBox" Grid.Row="1" Margin="9.6"/>
<TextBlock Grid.Column="1" Margin="9.6" Style="{StaticResource SubtitleTextBlockStyle}" Text="server"/>
<ListBox x:Name="serverListBox" Grid.Column="1" Grid.Row="1" Margin="9.6"/>
</Grid>
</Grid>
// Every protocol typically has a standard port number. For example, HTTP is typically 80, FTP is 20 and 21, etc.
// For this example, we'll choose an arbitrary port number.
static string PortNumber = "1337";
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.StartServer();
this.StartClient();
}
private async void StartServer()
{
try
{
var streamSocketListener = new Windows.Networking.Sockets.StreamSocketListener();
// The ConnectionReceived event is raised when connections are received.
streamSocketListener.ConnectionReceived += this.StreamSocketListener_ConnectionReceived;
// Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
await streamSocketListener.BindServiceNameAsync(StreamSocketAndListenerPage.PortNumber);
this.serverListBox.Items.Add("server is listening...");
}
catch (Exception ex)
{
Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
this.serverListBox.Items.Add(webErrorStatus.ToString() != "Unknown" ? webErrorStatus.ToString() : ex.Message);
}
}
private async void StreamSocketListener_ConnectionReceived(Windows.Networking.Sockets.StreamSocketListener sender, Windows.Networking.Sockets.StreamSocketListenerConnectionReceivedEventArgs args)
{
string request;
using (var streamReader = new StreamReader(args.Socket.InputStream.AsStreamForRead()))
{
request = await streamReader.ReadLineAsync();
}
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add(string.Format("server received the request: \"{0}\"", request)));
// Echo the request back as the response.
using (Stream outputStream = args.Socket.OutputStream.AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(request);
await streamWriter.FlushAsync();
}
}
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add(string.Format("server sent back the response: \"{0}\"", request)));
sender.Dispose();
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add("server closed its socket"));
}
private async void StartClient()
{
try
{
// Create the StreamSocket and establish a connection to the echo server.
using (var streamSocket = new Windows.Networking.Sockets.StreamSocket())
{
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
var hostName = new Windows.Networking.HostName("localhost");
this.clientListBox.Items.Add("client is trying to connect...");
await streamSocket.ConnectAsync(hostName, StreamSocketAndListenerPage.PortNumber);
this.clientListBox.Items.Add("client connected");
// Send a request to the echo server.
string request = "Hello, World!";
using (Stream outputStream = streamSocket.OutputStream.AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(request);
await streamWriter.FlushAsync();
}
}
this.clientListBox.Items.Add(string.Format("client sent the request: \"{0}\"", request));
// Read data from the echo server.
string response;
using (Stream inputStream = streamSocket.InputStream.AsStreamForRead())
{
using (StreamReader streamReader = new StreamReader(inputStream))
{
response = await streamReader.ReadLineAsync();
}
}
this.clientListBox.Items.Add(string.Format("client received the response: \"{0}\" ", response));
}
this.clientListBox.Items.Add("client closed its socket");
}
catch (Exception ex)
{
Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
this.clientListBox.Items.Add(webErrorStatus.ToString() != "Unknown" ? webErrorStatus.ToString() : ex.Message);
}
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Microsoft::UI::Dispatching;
using namespace Microsoft::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamSocketListener m_streamSocketListener;
Windows::Networking::Sockets::StreamSocket m_streamSocket;
public:
void OnNavigatedTo(NavigationEventArgs const& /* e */)
{
StartServer();
StartClient();
}
private:
IAsyncAction StartServer()
{
try
{
// The ConnectionReceived event is raised when connections are received.
m_streamSocketListener.ConnectionReceived({ this, &StreamSocketAndListenerPage::OnConnectionReceived });
// Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
// Every protocol typically has a standard port number. For example, HTTP is typically 80, FTP is 20 and 21, etc.
// For this example, we'll choose an arbitrary port number.
co_await m_streamSocketListener.BindServiceNameAsync(L"1337");
serverListBox().Items().Append(winrt::box_value(L"server is listening..."));
}
catch (winrt::hresult_error const& ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(ex.to_abi()) };
serverListBox().Items().Append(webErrorStatus != Windows::Networking::Sockets::SocketErrorStatus::Unknown ? winrt::box_value(winrt::to_hstring((int32_t)webErrorStatus)) : winrt::box_value(winrt::to_hstring(ex.to_abi())));
}
}
IAsyncAction OnConnectionReceived(Windows::Networking::Sockets::StreamSocketListener /* sender */, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs args)
{
try
{
auto socket{ args.Socket() }; // Keep the socket referenced, and alive.
DataReader dataReader{ socket.InputStream() };
unsigned int bytesLoaded = co_await dataReader.LoadAsync(sizeof(unsigned int));
unsigned int stringLength = dataReader.ReadUInt32();
bytesLoaded = co_await dataReader.LoadAsync(stringLength);
winrt::hstring request = dataReader.ReadString(bytesLoaded);
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
std::wstringstream wstringstream;
wstringstream << L"server received the request: \"" << request.c_str() << L"\"";
serverListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
});
// Echo the request back as the response.
DataWriter dataWriter{ socket.OutputStream() };
dataWriter.WriteUInt32(request.size());
dataWriter.WriteString(request);
co_await dataWriter.StoreAsync();
dataWriter.DetachStream();
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
std::wstringstream wstringstream;
wstringstream << L"server sent back the response: \"" << request.c_str() << L"\"";
serverListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
});
m_streamSocketListener = nullptr;
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
serverListBox().Items().Append(winrt::box_value(L"server closed its socket"));
});
}
catch (winrt::hresult_error const& ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(ex.to_abi()) };
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
serverListBox().Items().Append(webErrorStatus != Windows::Networking::Sockets::SocketErrorStatus::Unknown ? winrt::box_value(winrt::to_hstring((int32_t)webErrorStatus)) : winrt::box_value(winrt::to_hstring(ex.to_abi())));
});
}
}
IAsyncAction StartClient()
{
try
{
// Establish a connection to the echo server.
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
Windows::Networking::HostName hostName{ L"localhost" };
clientListBox().Items().Append(winrt::box_value(L"client is trying to connect..."));
co_await m_streamSocket.ConnectAsync(hostName, L"1337");
clientListBox().Items().Append(winrt::box_value(L"client connected"));
// Send a request to the echo server.
DataWriter dataWriter{ m_streamSocket.OutputStream() };
winrt::hstring request{ L"Hello, World!" };
dataWriter.WriteUInt32(request.size());
dataWriter.WriteString(request);
co_await dataWriter.StoreAsync();
std::wstringstream wstringstream;
wstringstream << L"client sent the request: \"" << request.c_str() << L"\"";
clientListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
co_await dataWriter.FlushAsync();
dataWriter.DetachStream();
// Read data from the echo server.
DataReader dataReader{ m_streamSocket.InputStream() };
unsigned int bytesLoaded = co_await dataReader.LoadAsync(sizeof(unsigned int));
unsigned int stringLength = dataReader.ReadUInt32();
bytesLoaded = co_await dataReader.LoadAsync(stringLength);
winrt::hstring response{ dataReader.ReadString(bytesLoaded) };
wstringstream.str(L"");
wstringstream << L"client received the response: \"" << response.c_str() << L"\"";
clientListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
m_streamSocket = nullptr;
clientListBox().Items().Append(winrt::box_value(L"client closed its socket"));
}
catch (winrt::hresult_error const& ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(ex.to_abi()) };
serverListBox().Items().Append(webErrorStatus != Windows::Networking::Sockets::SocketErrorStatus::Unknown ? winrt::box_value(winrt::to_hstring((int32_t)webErrorStatus)) : winrt::box_value(winrt::to_hstring(ex.to_abi())));
}
}
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamSocketListener^ streamSocketListener;
Windows::Networking::Sockets::StreamSocket^ streamSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->StartServer();
this->StartClient();
}
private:
void StartServer()
{
try
{
this->streamSocketListener = ref new Windows::Networking::Sockets::StreamSocketListener();
// The ConnectionReceived event is raised when connections are received.
streamSocketListener->ConnectionReceived += ref new TypedEventHandler<Windows::Networking::Sockets::StreamSocketListener^, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^>(this, &StreamSocketAndListenerPage::StreamSocketListener_ConnectionReceived);
// Start listening for incoming TCP connections on the specified port. You can specify any port that's not currently in use.
// Every protocol typically has a standard port number. For example, HTTP is typically 80, FTP is 20 and 21, etc.
// For this example, we'll choose an arbitrary port number.
Concurrency::create_task(streamSocketListener->BindServiceNameAsync(L"1337")).then(
[=]
{
this->serverListBox->Items->Append(L"server is listening...");
});
}
catch (Platform::Exception^ ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus = Windows::Networking::Sockets::SocketError::GetStatus(ex->HResult);
this->serverListBox->Items->Append(webErrorStatus.ToString() != L"Unknown" ? webErrorStatus.ToString() : ex->Message);
}
}
void StreamSocketListener_ConnectionReceived(Windows::Networking::Sockets::StreamSocketListener^ sender, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^ args)
{
try
{
auto dataReader = ref new DataReader(args->Socket->InputStream);
Concurrency::create_task(dataReader->LoadAsync(sizeof(unsigned int))).then(
[=](unsigned int bytesLoaded)
{
unsigned int stringLength = dataReader->ReadUInt32();
Concurrency::create_task(dataReader->LoadAsync(stringLength)).then(
[=](unsigned int bytesLoaded)
{
Platform::String^ request = dataReader->ReadString(bytesLoaded);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]
{
std::wstringstream wstringstream;
wstringstream << L"server received the request: \"" << request->Data() << L"\"";
this->serverListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
}));
// Echo the request back as the response.
auto dataWriter = ref new DataWriter(args->Socket->OutputStream);
dataWriter->WriteUInt32(request->Length());
dataWriter->WriteString(request);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](unsigned int)
{
dataWriter->DetachStream();
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]()
{
std::wstringstream wstringstream;
wstringstream << L"server sent back the response: \"" << request->Data() << L"\"";
this->serverListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
}));
delete this->streamSocketListener;
this->streamSocketListener = nullptr;
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([=]() {this->serverListBox->Items->Append(L"server closed its socket"); }));
});
});
});
}
catch (Platform::Exception^ ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus = Windows::Networking::Sockets::SocketError::GetStatus(ex->HResult);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([=]() {this->serverListBox->Items->Append(webErrorStatus.ToString() != L"Unknown" ? webErrorStatus.ToString() : ex->Message); }));
}
}
void StartClient()
{
try
{
// Create the StreamSocket and establish a connection to the echo server.
this->streamSocket = ref new Windows::Networking::Sockets::StreamSocket();
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
auto hostName = ref new Windows::Networking::HostName(L"localhost");
this->clientListBox->Items->Append(L"client is trying to connect...");
Concurrency::create_task(this->streamSocket->ConnectAsync(hostName, L"1337")).then(
[=](Concurrency::task< void >)
{
this->clientListBox->Items->Append(L"client connected");
// Send a request to the echo server.
auto dataWriter = ref new DataWriter(this->streamSocket->OutputStream);
auto request = ref new Platform::String(L"Hello, World!");
dataWriter->WriteUInt32(request->Length());
dataWriter->WriteString(request);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](Concurrency::task< unsigned int >)
{
std::wstringstream wstringstream;
wstringstream << L"client sent the request: \"" << request->Data() << L"\"";
this->clientListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
Concurrency::create_task(dataWriter->FlushAsync()).then(
[=](Concurrency::task< bool >)
{
dataWriter->DetachStream();
// Read data from the echo server.
auto dataReader = ref new DataReader(this->streamSocket->InputStream);
Concurrency::create_task(dataReader->LoadAsync(sizeof(unsigned int))).then(
[=](unsigned int bytesLoaded)
{
unsigned int stringLength = dataReader->ReadUInt32();
Concurrency::create_task(dataReader->LoadAsync(stringLength)).then(
[=](unsigned int bytesLoaded)
{
Platform::String^ response = dataReader->ReadString(bytesLoaded);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]
{
std::wstringstream wstringstream;
wstringstream << L"client received the response: \"" << response->Data() << L"\"";
this->clientListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
delete this->streamSocket;
this->streamSocket = nullptr;
this->clientListBox->Items->Append(L"client closed its socket");
}));
});
});
});
});
});
}
catch (Platform::Exception^ ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus = Windows::Networking::Sockets::SocketError::GetStatus(ex->HResult);
this->serverListBox->Items->Append(webErrorStatus.ToString() != L"Unknown" ? webErrorStatus.ToString() : ex->Message);
}
}
C++ PPL 延續中對 StreamSockets 的引用(主要適用於 C++/CX)
Note
如果你使用 C++/WinRT 協程,並且是用值傳遞參數,那麼這個問題就不適用。 關於參數傳遞的建議,請參見 C++/WinRT 的並行與非同步操作。
只要 StreamSocket 的輸入/輸出串流上仍有進行中的讀取/寫入作業,它就會維持存活(例如,你可以在 StreamSocketListener.ConnectionReceived 事件處理常式中存取的 StreamSocketListenerConnectionReceivedEventArgs.Socket)。 當您呼叫 DataReader.LoadAsync(或 ReadAsync/WriteAsync/StoreAsync)時,它會保留對該通訊端的參考(透過通訊端的輸入資料流),直到 LoadAsync 的 Completed 事件處理常式(如果有的話)執行完畢。
平行模式程式庫(PPL)預設情況下不會以內嵌方式排程工作延續。 換句話說,加入接續工作(帶有 task::then())並不保證該接續工作會直接在完成處理常式中內嵌執行。
void StreamSocketListener_ConnectionReceived(Windows::Networking::Sockets::StreamSocketListener^ sender, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^ args)
{
auto dataReader = ref new DataReader(args->Socket->InputStream);
Concurrency::create_task(dataReader->LoadAsync(sizeof(unsigned int))).then(
[=](unsigned int bytesLoaded)
{
// Work in here isn't guaranteed to execute inline as the completion handler of the LoadAsync.
});
}
從 StreamSocket 的角度來看,完成處理程序在延續主體執行前已完成執行(且該套接程式有資格丟棄)。 所以,如果你想在該 continuation 中使用 socket,為了避免它被釋放,你需要直接參照 socket(透過 lambda 擷取)並使用它,或間接參照它(也就是在 continuation 中持續存取 args->Socket),或者強制 continuation 工作以 inline 方式執行。 你可以在 StreamSocket 範例中看到第一種技術(lambda 擷取)的實際運作。 上述「 建構基本 TCP socket 用戶端與伺服器 」部分的 C++/CX 程式碼使用第二種技術——它會將請求回傳為回應,並 args->Socket 從最內層的延續之一存取。
第三種技術適用於你不需要將回應原樣傳回時。 你可以使用 task_continuation_context::use_synchronous_execution() 選項,強制 PPL 以內嵌方式執行接續主體。 這裡有一個範例說明如何做到。
void StreamSocketListener_ConnectionReceived(Windows::Networking::Sockets::StreamSocketListener^ sender, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^ args)
{
auto dataReader = ref new DataReader(args->Socket->InputStream);
Concurrency::create_task(dataReader->LoadAsync(sizeof(unsigned int))).then(
[=](unsigned int bytesLoaded)
{
unsigned int messageLength = dataReader->ReadUInt32();
Concurrency::create_task(dataReader->LoadAsync(messageLength)).then(
[=](unsigned int bytesLoaded)
{
Platform::String^ request = dataReader->ReadString(bytesLoaded);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]
{
std::wstringstream wstringstream;
wstringstream << L"server received the request: \"" << request->Data() << L"\"";
this->serverListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
}));
});
}, Concurrency::task_continuation_context::use_synchronous_execution());
}
此行為適用於 Windows.Networking.Sockets 命名空間中的所有 Socket 和 WebSockets 類別。 但用戶端情境通常將套接字儲存在成員變數中,因此問題最適用於如上所述的 StreamSocketListener.ConnectionReceived 情境。
建立一個基本的 UDP socket 客戶端和伺服器
UDP(使用者資料報協定)套接字與 TCP 套接字相似,因為它也提供雙向的低階網路資料傳輸。 不過,TCP 套接字用於長壽命連線,而 UDP 套接字則適用於不需要建立連線的應用。 由於 UDP 套接字不需在通訊雙方之間維持連線,因此對於遠端機器之間的網路通訊而言,它們是一種快速且簡單的解決方案。 但 UDP 套接字無法確保網路封包的完整性,甚至無法確保封包是否能到達遠端目的地。 所以你的應用程式必須設計成能容忍這些問題。 使用 UDP 套接字的應用程式範例包括本地網路發現與本地聊天用戶端。
為了展示基本的 UDP 操作,以下範例程式碼展示了 DatagramSocket 類別如何透過 UDP 傳送與接收資料,形成一個 echo 客戶端與伺服器。 建立一個新專案,並將以下客戶端和伺服器程式碼放進同一個專案。 就像 TCP 套接字一樣,你需要宣告 私有網路(客戶端與伺服器) 應用程式的能力。
一個 echo 用戶端與伺服器,使用 UDP 插槽
建立一個 DatagramSocket 來扮演 echo 伺服器的角色,綁定到特定的埠號,監聽收到的 UDP 訊息,然後回傳回去。 當 socket 收到訊息時,會觸發 DatagramSocket.MessageReceived 事件。
再建一個 DatagramSocket 扮演 echo 用戶端的角色,綁定到特定埠號,發送 UDP 訊息,並接收回應。
建立一個名為 的新DatagramSocketPage。 把 XAML 標記放進 DatagramSocketPage.xaml,然後把命令式程式碼放進 DatagramSocketPage 類別裡。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Margin="9.6,0" Style="{StaticResource TitleTextBlockStyle}" Text="UDP socket example"/>
<TextBlock Margin="7.2,0,0,0" Style="{StaticResource HeaderTextBlockStyle}" Text="DatagramSocket"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="9.6" Style="{StaticResource SubtitleTextBlockStyle}" Text="client"/>
<ListBox x:Name="clientListBox" Grid.Row="1" Margin="9.6"/>
<TextBlock Grid.Column="1" Margin="9.6" Style="{StaticResource SubtitleTextBlockStyle}" Text="server"/>
<ListBox x:Name="serverListBox" Grid.Column="1" Grid.Row="1" Margin="9.6"/>
</Grid>
</Grid>
// Every protocol typically has a standard port number. For example, HTTP is typically 80, FTP is 20 and 21, etc.
// For this example, we'll choose different arbitrary port numbers for client and server, since both will be running on the same machine.
static string ClientPortNumber = "1336";
static string ServerPortNumber = "1337";
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.StartServer();
this.StartClient();
}
private async void StartServer()
{
try
{
var serverDatagramSocket = new Windows.Networking.Sockets.DatagramSocket();
// The ConnectionReceived event is raised when connections are received.
serverDatagramSocket.MessageReceived += ServerDatagramSocket_MessageReceived;
this.serverListBox.Items.Add("server is about to bind...");
// Start listening for incoming UDP datagrams on the specified port. You can specify any port that's not currently in use.
await serverDatagramSocket.BindServiceNameAsync(DatagramSocketPage.ServerPortNumber);
this.serverListBox.Items.Add(string.Format("server is bound to port number {0}", DatagramSocketPage.ServerPortNumber));
}
catch (Exception ex)
{
Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
this.serverListBox.Items.Add(webErrorStatus.ToString() != "Unknown" ? webErrorStatus.ToString() : ex.Message);
}
}
private async void ServerDatagramSocket_MessageReceived(Windows.Networking.Sockets.DatagramSocket sender, Windows.Networking.Sockets.DatagramSocketMessageReceivedEventArgs args)
{
string request;
using (DataReader dataReader = args.GetDataReader())
{
request = dataReader.ReadString(dataReader.UnconsumedBufferLength).Trim();
}
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add(string.Format("server received the request: \"{0}\"", request)));
// Echo the request back as the response.
using (Stream outputStream = (await sender.GetOutputStreamAsync(args.RemoteAddress, DatagramSocketPage.ClientPortNumber)).AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(request);
await streamWriter.FlushAsync();
}
}
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add(string.Format("server sent back the response: \"{0}\"", request)));
sender.Dispose();
DispatcherQueue.TryEnqueue(() => this.serverListBox.Items.Add("server closed its socket"));
}
private async void StartClient()
{
try
{
// Create the DatagramSocket and establish a connection to the echo server.
var clientDatagramSocket = new Windows.Networking.Sockets.DatagramSocket();
clientDatagramSocket.MessageReceived += ClientDatagramSocket_MessageReceived;
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
var hostName = new Windows.Networking.HostName("localhost");
this.clientListBox.Items.Add("client is about to bind...");
await clientDatagramSocket.BindServiceNameAsync(DatagramSocketPage.ClientPortNumber);
this.clientListBox.Items.Add(string.Format("client is bound to port number {0}", DatagramSocketPage.ClientPortNumber));
// Send a request to the echo server.
string request = "Hello, World!";
using (var serverDatagramSocket = new Windows.Networking.Sockets.DatagramSocket())
{
using (Stream outputStream = (await serverDatagramSocket.GetOutputStreamAsync(hostName, DatagramSocketPage.ServerPortNumber)).AsStreamForWrite())
{
using (var streamWriter = new StreamWriter(outputStream))
{
await streamWriter.WriteLineAsync(request);
await streamWriter.FlushAsync();
}
}
}
this.clientListBox.Items.Add(string.Format("client sent the request: \"{0}\"", request));
}
catch (Exception ex)
{
Windows.Networking.Sockets.SocketErrorStatus webErrorStatus = Windows.Networking.Sockets.SocketError.GetStatus(ex.GetBaseException().HResult);
this.clientListBox.Items.Add(webErrorStatus.ToString() != "Unknown" ? webErrorStatus.ToString() : ex.Message);
}
}
private async void ClientDatagramSocket_MessageReceived(Windows.Networking.Sockets.DatagramSocket sender, Windows.Networking.Sockets.DatagramSocketMessageReceivedEventArgs args)
{
string response;
using (DataReader dataReader = args.GetDataReader())
{
response = dataReader.ReadString(dataReader.UnconsumedBufferLength).Trim();
}
DispatcherQueue.TryEnqueue(() => this.clientListBox.Items.Add(string.Format("client received the response: \"{0}\"", response)));
sender.Dispose();
DispatcherQueue.TryEnqueue(() => this.clientListBox.Items.Add("client closed its socket"));
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Microsoft::UI::Dispatching;
using namespace Microsoft::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::DatagramSocket m_clientDatagramSocket;
Windows::Networking::Sockets::DatagramSocket m_serverDatagramSocket;
public:
void OnNavigatedTo(NavigationEventArgs const& /* e */)
{
StartServer();
StartClient();
}
private:
IAsyncAction StartServer()
{
try
{
// The ConnectionReceived event is raised when connections are received.
m_serverDatagramSocket.MessageReceived({ this, &DatagramSocketPage::ServerDatagramSocket_MessageReceived });
serverListBox().Items().Append(winrt::box_value(L"server is about to bind..."));
// Start listening for incoming UDP datagrams on the specified port. You can specify any port that's not currently in use.
co_await m_serverDatagramSocket.BindServiceNameAsync(L"1337");
serverListBox().Items().Append(winrt::box_value(L"server is bound to port number 1337"));
}
catch (winrt::hresult_error const& ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(ex.to_abi()) };
serverListBox().Items().Append(webErrorStatus != Windows::Networking::Sockets::SocketErrorStatus::Unknown ? winrt::box_value(winrt::to_hstring((int32_t)webErrorStatus)) : winrt::box_value(winrt::to_hstring(ex.to_abi())));
}
}
IAsyncAction ServerDatagramSocket_MessageReceived(Windows::Networking::Sockets::DatagramSocket sender, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs args)
{
DataReader dataReader{ args.GetDataReader() };
winrt::hstring request{ dataReader.ReadString(dataReader.UnconsumedBufferLength()) };
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
std::wstringstream wstringstream;
wstringstream << L"server received the request: \"" << request.c_str() << L"\"";
serverListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
});
// Echo the request back as the response.
IOutputStream outputStream = co_await sender.GetOutputStreamAsync(args.RemoteAddress(), L"1336");
DataWriter dataWriter{ outputStream };
dataWriter.WriteString(request);
co_await dataWriter.StoreAsync();
dataWriter.DetachStream();
serverListBox().DispatcherQueue().TryEnqueue([=]()
{
std::wstringstream wstringstream;
wstringstream << L"server sent back the response: \"" << request.c_str() << L"\"";
serverListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
m_serverDatagramSocket = nullptr;
serverListBox().Items().Append(winrt::box_value(L"server closed its socket"));
});
}
IAsyncAction StartClient()
{
try
{
m_clientDatagramSocket.MessageReceived({ this, &DatagramSocketPage::ClientDatagramSocket_MessageReceived });
// Establish a connection to the echo server.
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
Windows::Networking::HostName hostName{ L"localhost" };
clientListBox().Items().Append(winrt::box_value(L"client is about to bind..."));
co_await m_clientDatagramSocket.BindServiceNameAsync(L"1336");
clientListBox().Items().Append(winrt::box_value(L"client is bound to port number 1336"));
// Send a request to the echo server.
IOutputStream outputStream = co_await m_clientDatagramSocket.GetOutputStreamAsync(hostName, L"1337");
winrt::hstring request{ L"Hello, World!" };
DataWriter dataWriter{ outputStream };
dataWriter.WriteString(request);
co_await dataWriter.StoreAsync();
dataWriter.DetachStream();
std::wstringstream wstringstream;
wstringstream << L"client sent the request: \"" << request.c_str() << L"\"";
clientListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
}
catch (winrt::hresult_error const& ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus{ Windows::Networking::Sockets::SocketError::GetStatus(ex.to_abi()) };
serverListBox().Items().Append(webErrorStatus != Windows::Networking::Sockets::SocketErrorStatus::Unknown ? winrt::box_value(winrt::to_hstring((int32_t)webErrorStatus)) : winrt::box_value(winrt::to_hstring(ex.to_abi())));
}
}
void ClientDatagramSocket_MessageReceived(Windows::Networking::Sockets::DatagramSocket const& /* sender */, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs const& args)
{
DataReader dataReader{ args.GetDataReader() };
winrt::hstring response{ dataReader.ReadString(dataReader.UnconsumedBufferLength()) };
clientListBox().DispatcherQueue().TryEnqueue([=]()
{
std::wstringstream wstringstream;
wstringstream << L"client received the response: \"" << response.c_str() << L"\"";
clientListBox().Items().Append(winrt::box_value(wstringstream.str().c_str()));
});
m_clientDatagramSocket = nullptr;
clientListBox().DispatcherQueue().TryEnqueue([=]()
{
clientListBox().Items().Append(winrt::box_value(L"client closed its socket"));
});
}
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::DatagramSocket^ clientDatagramSocket;
Windows::Networking::Sockets::DatagramSocket^ serverDatagramSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->StartServer();
this->StartClient();
}
private:
void StartServer()
{
try
{
this->serverDatagramSocket = ref new Windows::Networking::Sockets::DatagramSocket();
// The ConnectionReceived event is raised when connections are received.
this->serverDatagramSocket->MessageReceived += ref new TypedEventHandler<Windows::Networking::Sockets::DatagramSocket^, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs^>(this, &DatagramSocketPage::ServerDatagramSocket_MessageReceived);
this->serverListBox->Items->Append(L"server is about to bind...");
// Start listening for incoming UDP datagrams on the specified port. You can specify any port that's not currently in use.
Concurrency::create_task(this->serverDatagramSocket->BindServiceNameAsync("1337")).then(
[=]
{
this->serverListBox->Items->Append(L"server is bound to port number 1337");
});
}
catch (Platform::Exception^ ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus = Windows::Networking::Sockets::SocketError::GetStatus(ex->HResult);
this->serverListBox->Items->Append(webErrorStatus.ToString() != L"Unknown" ? webErrorStatus.ToString() : ex->Message);
}
}
void ServerDatagramSocket_MessageReceived(Windows::Networking::Sockets::DatagramSocket^ sender, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs^ args)
{
DataReader^ dataReader = args->GetDataReader();
Platform::String^ request = dataReader->ReadString(dataReader->UnconsumedBufferLength);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]
{
std::wstringstream wstringstream;
wstringstream << L"server received the request: \"" << request->Data() << L"\"";
this->serverListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
}));
// Echo the request back as the response.
Concurrency::create_task(sender->GetOutputStreamAsync(args->RemoteAddress, "1336")).then(
[=](IOutputStream^ outputStream)
{
auto dataWriter = ref new DataWriter(outputStream);
dataWriter->WriteString(request);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](unsigned int)
{
dataWriter->DetachStream();
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]()
{
std::wstringstream wstringstream;
wstringstream << L"server sent back the response: \"" << request->Data() << L"\"";
this->serverListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
delete this->serverDatagramSocket;
this->serverDatagramSocket = nullptr;
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([=]() {this->serverListBox->Items->Append(L"server closed its socket"); }));
}));
});
});
}
void StartClient()
{
try
{
// Create the DatagramSocket and establish a connection to the echo server.
this->clientDatagramSocket = ref new Windows::Networking::Sockets::DatagramSocket();
this->clientDatagramSocket->MessageReceived += ref new TypedEventHandler<Windows::Networking::Sockets::DatagramSocket^, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs^>(this, &DatagramSocketPage::ClientDatagramSocket_MessageReceived);
// The server hostname that we will be establishing a connection to. In this example, the server and client are in the same process.
auto hostName = ref new Windows::Networking::HostName(L"localhost");
this->clientListBox->Items->Append(L"client is about to bind...");
Concurrency::create_task(this->clientDatagramSocket->BindServiceNameAsync("1336")).then(
[=]
{
this->clientListBox->Items->Append(L"client is bound to port number 1336");
});
// Send a request to the echo server.
auto serverDatagramSocket = ref new Windows::Networking::Sockets::DatagramSocket();
Concurrency::create_task(serverDatagramSocket->GetOutputStreamAsync(hostName, "1337")).then(
[=](IOutputStream^ outputStream)
{
auto request = ref new Platform::String(L"Hello, World!");
auto dataWriter = ref new DataWriter(outputStream);
dataWriter->WriteString(request);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](unsigned int)
{
dataWriter->DetachStream();
std::wstringstream wstringstream;
wstringstream << L"client sent the request: \"" << request->Data() << L"\"";
this->clientListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
});
});
}
catch (Platform::Exception^ ex)
{
Windows::Networking::Sockets::SocketErrorStatus webErrorStatus = Windows::Networking::Sockets::SocketError::GetStatus(ex->HResult);
this->serverListBox->Items->Append(webErrorStatus.ToString() != L"Unknown" ? webErrorStatus.ToString() : ex->Message);
}
}
void ClientDatagramSocket_MessageReceived(Windows::Networking::Sockets::DatagramSocket^ sender, Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs^ args)
{
DataReader^ dataReader = args->GetDataReader();
Platform::String^ response = dataReader->ReadString(dataReader->UnconsumedBufferLength);
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler(
[=]
{
std::wstringstream wstringstream;
wstringstream << L"client received the response: \"" << response->Data() << L"\"";
this->clientListBox->Items->Append(ref new Platform::String(wstringstream.str().c_str()));
}));
delete this->clientDatagramSocket;
this->clientDatagramSocket = nullptr;
this->Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler([=]() {this->clientListBox->Items->Append(L"client closed its socket"); }));
}
背景操作與套接程式代理
你可以使用 socket broker 和控制通道觸發器,確保你的應用程式在未於前景執行時,仍能透過 socket 正確接收連線或資料。 欲了解更多資訊,請參閱 背景中的網路通訊。
批次傳送
每當你寫入與套接字相關的串流時,使用者模式(你的程式碼)會轉換到核心模式(網路堆疊所在)。 如果你一次寫入多個緩衝區,這些重複的轉換就會累積成相當大的開銷。 將傳送作業批次處理,可一次傳送多個資料緩衝區,並避免這類額外負荷。 如果你的應用程式需要執行 VoIP、VPN 或其他需要高效傳輸大量資料的任務,這功能特別有用。
本節示範幾種批次發送技巧,適用於 StreamSocket 或連接的 DatagramSocket。
為了取得基準,我們來看看如何以低效率的方式傳送大量緩衝區。 這裡有一個簡短的示範,使用 StreamSocket。
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var streamSocketListener = new Windows.Networking.Sockets.StreamSocketListener();
streamSocketListener.ConnectionReceived += this.StreamSocketListener_ConnectionReceived;
await streamSocketListener.BindServiceNameAsync("1337");
var streamSocket = new Windows.Networking.Sockets.StreamSocket();
await streamSocket.ConnectAsync(new Windows.Networking.HostName("localhost"), "1337");
this.SendMultipleBuffersInefficiently(streamSocket, "Hello, World!");
//this.BatchedSendsCSharpOnly(streamSocket, "Hello, World!");
//this.BatchedSendsAnyUWPLanguage(streamSocket, "Hello, World!");
}
private async void StreamSocketListener_ConnectionReceived(Windows.Networking.Sockets.StreamSocketListener sender, Windows.Networking.Sockets.StreamSocketListenerConnectionReceivedEventArgs args)
{
using (var dataReader = new DataReader(args.Socket.InputStream))
{
dataReader.InputStreamOptions = InputStreamOptions.Partial;
while (true)
{
await dataReader.LoadAsync(256);
if (dataReader.UnconsumedBufferLength == 0) break;
IBuffer requestBuffer = dataReader.ReadBuffer(dataReader.UnconsumedBufferLength);
string request = Windows.Security.Cryptography.CryptographicBuffer.ConvertBinaryToString(Windows.Security.Cryptography.BinaryStringEncoding.Utf8, requestBuffer);
Debug.WriteLine(string.Format("server received the request: \"{0}\"", request));
}
}
}
// This implementation incurs kernel transition overhead for each packet written.
private async void SendMultipleBuffersInefficiently(Windows.Networking.Sockets.StreamSocket streamSocket, string message)
{
var packetsToSend = new List<IBuffer>();
for (int count = 0; count < 5; ++count) { packetsToSend.Add(Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(message, Windows.Security.Cryptography.BinaryStringEncoding.Utf8)); }
foreach (IBuffer packet in packetsToSend)
{
await streamSocket.OutputStream.WriteAsync(packet);
}
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <winrt/Microsoft.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Microsoft::UI::Dispatching;
using namespace Microsoft::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamSocketListener m_streamSocketListener;
Windows::Networking::Sockets::StreamSocket m_streamSocket;
public:
IAsyncAction OnNavigatedTo(NavigationEventArgs /* e */)
{
m_streamSocketListener.ConnectionReceived({ this, &BatchedSendsPage::OnConnectionReceived });
co_await m_streamSocketListener.BindServiceNameAsync(L"1337");
co_await m_streamSocket.ConnectAsync(Windows::Networking::HostName{ L"localhost" }, L"1337");
SendMultipleBuffersInefficientlyAsync(L"Hello, World!");
//BatchedSendsAnyUWPLanguageAsync(L"Hello, World!");
}
private:
IAsyncAction OnConnectionReceived(Windows::Networking::Sockets::StreamSocketListener const& /* sender */, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs const& args)
{
DataReader dataReader{ args.Socket().InputStream() };
dataReader.InputStreamOptions(Windows::Storage::Streams::InputStreamOptions::Partial);
while (true)
{
unsigned int bytesLoaded = co_await dataReader.LoadAsync(256);
if (bytesLoaded == 0) break;
winrt::hstring message{ dataReader.ReadString(bytesLoaded) };
::OutputDebugString(message.c_str());
}
}
// This implementation incurs kernel transition overhead for each packet written.
IAsyncAction SendMultipleBuffersInefficientlyAsync(winrt::hstring message)
{
co_await winrt::resume_background();
std::vector< IBuffer > packetsToSend;
for (unsigned int count = 0; count < 5; ++count)
{
packetsToSend.push_back(Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary(message, Windows::Security::Cryptography::BinaryStringEncoding::Utf8));
}
for (auto const& element : packetsToSend)
{
m_streamSocket.OutputStream().WriteAsync(element).get();
}
}
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamSocketListener^ streamSocketListener;
Windows::Networking::Sockets::StreamSocket^ streamSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->streamSocketListener = ref new Windows::Networking::Sockets::StreamSocketListener();
streamSocketListener->ConnectionReceived += ref new TypedEventHandler<Windows::Networking::Sockets::StreamSocketListener^, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^>(this, &BatchedSendsPage::StreamSocketListener_ConnectionReceived);
Concurrency::create_task(this->streamSocketListener->BindServiceNameAsync(L"1337")).then(
[=]
{
this->streamSocket = ref new Windows::Networking::Sockets::StreamSocket();
Concurrency::create_task(this->streamSocket->ConnectAsync(ref new Windows::Networking::HostName(L"localhost"), L"1337")).then(
[=](Concurrency::task< void >)
{
this->SendMultipleBuffersInefficiently(L"Hello, World!");
// this->BatchedSendsAnyUWPLanguage(L"Hello, World!");
}, Concurrency::task_continuation_context::use_synchronous_execution());
});
}
private:
void StreamSocketListener_ConnectionReceived(Windows::Networking::Sockets::StreamSocketListener^ sender, Windows::Networking::Sockets::StreamSocketListenerConnectionReceivedEventArgs^ args)
{
auto dataReader = ref new DataReader(args->Socket->InputStream);
dataReader->InputStreamOptions = Windows::Storage::Streams::InputStreamOptions::Partial;
this->ReceiveStringRecurse(dataReader, args->Socket);
}
void ReceiveStringRecurse(DataReader^ dataReader, Windows::Networking::Sockets::StreamSocket^ streamSocket)
{
Concurrency::create_task(dataReader->LoadAsync(256)).then(
[this, dataReader, streamSocket](unsigned int bytesLoaded)
{
if (bytesLoaded == 0) return;
Platform::String^ message = dataReader->ReadString(bytesLoaded);
::OutputDebugString(message->Data());
this->ReceiveStringRecurse(dataReader, streamSocket);
});
}
// This implementation incurs kernel transition overhead for each packet written.
void SendMultipleBuffersInefficiently(Platform::String^ message)
{
std::vector< IBuffer^ > packetsToSend{};
for (unsigned int count = 0; count < 5; ++count)
{
packetsToSend.push_back(Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary(message, Windows::Security::Cryptography::BinaryStringEncoding::Utf8));
}
for (auto element : packetsToSend)
{
Concurrency::create_task(this->streamSocket->OutputStream->WriteAsync(element)).wait();
}
}
這個較有效率技巧的第一個範例,只有在您使用 C# 時才適用。 改 OnNavigatedTo 為 call BatchedSendsCSharpOnly 而不是 SendMultipleBuffersInefficiently or SendMultipleBuffersInefficientlyAsync。
// A C#-only technique for batched sends.
private async void BatchedSendsCSharpOnly(Windows.Networking.Sockets.StreamSocket streamSocket, string message)
{
var packetsToSend = new List<IBuffer>();
for (int count = 0; count < 5; ++count) { packetsToSend.Add(Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(message, Windows.Security.Cryptography.BinaryStringEncoding.Utf8)); }
var pendingTasks = new System.Threading.Tasks.Task[packetsToSend.Count];
for (int index = 0; index < packetsToSend.Count; ++index)
{
// track all pending writes as tasks, but don't wait on one before beginning the next.
pendingTasks[index] = streamSocket.OutputStream.WriteAsync(packetsToSend[index]).AsTask();
// Don't modify any buffer's contents until the pending writes are complete.
}
// Wait for all of the pending writes to complete.
System.Threading.Tasks.Task.WaitAll(pendingTasks);
}
以下範例適用於任何 UWP 語言,不僅限於 C#。 它依賴 StreamSocket.OutputStream 和 DatagramSocket.OutputStream 中批次傳送的行為。 這個方法會在該輸出串流上呼叫 FlushAsync;從 Windows 10 開始,可保證此呼叫只有在該輸出串流上的所有作業都完成後才會傳回。
// An implementation of batched sends suitable for any UWP language.
private async void BatchedSendsAnyUWPLanguage(Windows.Networking.Sockets.StreamSocket streamSocket, string message)
{
var packetsToSend = new List<IBuffer>();
for (int count = 0; count < 5; ++count) { packetsToSend.Add(Windows.Security.Cryptography.CryptographicBuffer.ConvertStringToBinary(message, Windows.Security.Cryptography.BinaryStringEncoding.Utf8)); }
var pendingWrites = new IAsyncOperationWithProgress<uint, uint>[packetsToSend.Count];
for (int index = 0; index < packetsToSend.Count; ++index)
{
// track all pending writes as tasks, but don't wait on one before beginning the next.
pendingWrites[index] = streamSocket.OutputStream.WriteAsync(packetsToSend[index]);
// Don't modify any buffer's contents until the pending writes are complete.
}
// Wait for all of the pending writes to complete. This step enables batched sends on the output stream.
await streamSocket.OutputStream.FlushAsync();
}
// An implementation of batched sends suitable for any UWP language.
IAsyncAction BatchedSendsAnyUWPLanguageAsync(winrt::hstring message)
{
std::vector< IBuffer > packetsToSend{};
std::vector< IAsyncOperationWithProgress< unsigned int, unsigned int > > pendingWrites{};
for (unsigned int count = 0; count < 5; ++count)
{
packetsToSend.push_back(Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary(message, Windows::Security::Cryptography::BinaryStringEncoding::Utf8));
}
for (auto const& element : packetsToSend)
{
// track all pending writes as tasks, but don't wait on one before beginning the next.
pendingWrites.push_back(m_streamSocket.OutputStream().WriteAsync(element));
// Don't modify any buffer's contents until the pending writes are complete.
}
// Wait for all of the pending writes to complete. This step enables batched sends on the output stream.
co_await m_streamSocket.OutputStream().FlushAsync();
}
private:
// An implementation of batched sends suitable for any UWP language.
void BatchedSendsAnyUWPLanguage(Platform::String^ message)
{
std::vector< IBuffer^ > packetsToSend{};
std::vector< IAsyncOperationWithProgress< unsigned int, unsigned int >^ >pendingWrites{};
for (unsigned int count = 0; count < 5; ++count)
{
packetsToSend.push_back(Windows::Security::Cryptography::CryptographicBuffer::ConvertStringToBinary(message, Windows::Security::Cryptography::BinaryStringEncoding::Utf8));
}
for (auto element : packetsToSend)
{
// track all pending writes as tasks, but don't wait on one before beginning the next.
pendingWrites.push_back(this->streamSocket->OutputStream->WriteAsync(element));
// Don't modify any buffer's contents until the pending writes are complete.
}
// Wait for all of the pending writes to complete. This step enables batched sends on the output stream.
Concurrency::create_task(this->streamSocket->OutputStream->FlushAsync());
}
在程式碼中使用批次傳送會帶來一些重要的限制。
- 在非同步寫入完成前,無法修改正在寫入的 IBuffer 實例內容。
- FlushAsync 模式僅適用於 StreamSocket.OutputStream 和 DatagramSocket.OutputStream。
- FlushAsync 模式僅在 Windows 10 及以後版本有效。
- 在其他情況下,則使用 Task.WaitAll 取代 FlushAsync 模式。
DatagramSocket 的埠口共享
你可以將 DatagramSocket 設定為與其他繫結至相同位址/連接埠的 Win32 或 UWP 多點傳送通訊端共存。 做法是在繫結或連線通訊端之前,將 DatagramSocketControl.MulticastOnly 設定為 true。 你可以透過 DatagramSocket 物件本身的 DatagramSocket.Control 屬性,存取 DatagramSocketControl 的實例。
提供帶有 StreamSocket 類別的用戶端憑證
StreamSocket 支援使用 SSL/TLS 來驗證客戶端應用程式正在通訊的伺服器。 在某些情況下,客戶端應用程式需要使用 SSL/TLS 客戶端憑證來驗證自己給伺服器。 您可以在繫結或連線通訊端之前,透過 StreamSocketControl.ClientCertificate 屬性提供用戶端憑證(必須在 SSL/TLS 交握開始前設定)。 你可以透過 StreamSocket 物件本身的 StreamSocket.Control 屬性存取 StreamSocketControl 的實例。 如果伺服器要求客戶端憑證,Windows 會回應你提供的用戶端憑證。
請使用 StreamSocket.ConnectAsync 的覆寫,該覆蓋會取得 SocketProtectionLevel,如本簡短程式碼範例所示。
Important
如同下方程式碼範例中的註解所示,你的專案需要宣告 sharedUserCertificates 應用程式的功能,這段程式碼才能正常運作。
// For this code to work, you need at least one certificate to be present in the user MY certificate store.
// Plugging a smartcard into a smartcard reader connected to your PC will achieve that.
// Also, your project needs to declare the sharedUserCertificates app capability.
var certificateQuery = new Windows.Security.Cryptography.Certificates.CertificateQuery();
certificateQuery.StoreName = "MY";
IReadOnlyList<Windows.Security.Cryptography.Certificates.Certificate> certificates = await Windows.Security.Cryptography.Certificates.CertificateStores.FindAllAsync(certificateQuery);
if (certificates.Count > 0)
{
streamSocket.Control.ClientCertificate = certificates[0];
await streamSocket.ConnectAsync(hostName, "1337", Windows.Networking.Sockets.SocketProtectionLevel.Tls12);
}
// For this code to work, you need at least one certificate to be present in the user MY certificate store.
// Plugging a smartcard into a smartcard reader connected to your PC will achieve that.
// Also, your project needs to declare the sharedUserCertificates app capability.
Windows::Security::Cryptography::Certificates::CertificateQuery certificateQuery;
certificateQuery.StoreName(L"MY");
IVectorView< Windows::Security::Cryptography::Certificates::Certificate > certificates = co_await Windows::Security::Cryptography::Certificates::CertificateStores::FindAllAsync(certificateQuery);
if (certificates.Size() > 0)
{
m_streamSocket.Control().ClientCertificate(certificates.GetAt(0));
co_await m_streamSocket.ConnectAsync(Windows::Networking::HostName{ L"localhost" }, L"1337", Windows::Networking::Sockets::SocketProtectionLevel::Tls12);
...
}
// For this code to work, you need at least one certificate to be present in the user MY certificate store.
// Plugging a smartcard into a smartcard reader connected to your PC will achieve that.
// Also, your project needs to declare the sharedUserCertificates app capability.
auto certificateQuery = ref new Windows::Security::Cryptography::Certificates::CertificateQuery();
certificateQuery->StoreName = L"MY";
Concurrency::create_task(Windows::Security::Cryptography::Certificates::CertificateStores::FindAllAsync(certificateQuery)).then(
[=](IVectorView< Windows::Security::Cryptography::Certificates::Certificate^ >^ certificates)
{
if (certificates->Size > 0)
{
this->streamSocket->Control->ClientCertificate = certificates->GetAt(0);
Concurrency::create_task(this->streamSocket->ConnectAsync(ref new Windows::Networking::HostName(L"localhost"), L"1337", Windows::Networking::Sockets::SocketProtectionLevel::Tls12)).then(
[=]
{
...
});
}
});
處理例外狀況
在 DatagramSocket、StreamSocket 或 StreamSocketListen 操作中遇到的錯誤會以 HRESULT 值回傳。 你可以把這個 HRESULT 值傳給 SocketError.GetStatus 方法,將其轉換成 SocketErrorStatus 的列舉值。
大多數 SocketErrorStatus 的列舉值對應於原生 Windows 套接字操作回傳的錯誤。 你的應用程式可以開啟 SocketErrorStatus 列舉值,根據例外原因修改應用程式行為。
對於參數驗證錯誤,你可以利用例外的 HRESULT 來獲取更詳細的錯誤資訊。 可能的 HRESULT 值列在 Winerror.h中,可在你的 SDK 安裝中找到(例如資料夾中 C:\Program Files (x86)\Windows Kits\10\Include\<VERSION>\shared)。 對於大多數參數驗證錯誤,回傳的 HRESULT 為 E_INVALIDARG。
如果傳遞的字串不是有效的主機名稱, HostName 建構子可以拋出例外。 例如,它包含不允許的字元,這很可能是因為使用者在你的應用程式中輸入了主機名稱。 在 try/catch 區塊內建構 HostName。 這樣一來,如果發生例外,應用程式就能通知使用者並請求新的主機名稱。
重要 API
- 憑證查詢
- CertificateStores.FindAllAsync
- 資料報套接字
- DatagramSocket.BindServiceNameAsync
- DatagramSocket.Control
- DatagramSocket.GetOutputStreamAsync
- DatagramSocket.MessageReceived
- DatagramSocketControl.MulticastOnly
- DatagramSocketMessageReceivedEventArgs
- DatagramSocketMessageReceivedEventArgs.GetDataReader
- DataReader.LoadAsync
- IOutputStream.FlushAsync
- SocketError.GetStatus
- SocketErrorStatus
- 插槽保護等級
- StreamSocket
- StreamSocketControl.ClientCertificate
- StreamSocket.ConnectAsync
- StreamSocket.InputStream
- StreamSocket.OutputStream
- StreamSocketListener
- StreamSocketListener.BindServiceNameAsync
- StreamSocketListener.已接收連線
- StreamSocketListenerConnectionReceivedEventArgs
- Windows.Networking.Sockets