Socket
I socket sono una tecnologia di trasferimento dei dati di basso livello su cui vengono implementati numerosi protocolli di rete. La piattaforma UWP offre classi di socket TCP e UDP per applicazioni client-server o peer-to-peer, se le connessioni sono di lunga durata o non è necessaria una connessione stabilita.
Questo argomento indica come usare le classi socket della piattaforma UWP (Universal Windows Platform) incluse nello spazio dei nomi Windows.Networking.Sockets, ma puoi usare anche Windows Sockets 2 (Winsock) in un'app UWP.
Nota
Come conseguenza dell'isolamento di rete , Windows non permette che venga stabilita una connessione socket (Sockets o WinSock) tra due app UWP in esecuzione nello stesso computer, né tramite l'indirizzo di loopback locale (127.0.0.0), né specificando in modo esplicito l'indirizzo IP locale. Per i dettagli sui meccanismi tramite i quali le app UWP possono comunicare tra loro, vedi Comunicazione tra app.
Un socket TCP (Transmission Control Protocol) permette trasferimenti di dati in rete a basso livello nelle due direzioni per connessioni di lunga durata. I socket TCP costituiscono la funzionalità sottostante usata da gran parte dei protocolli di rete in uso su Internet. Per illustrare le operazioni TCP di base, il codice di esempio riportato di seguito mostra un oggetto StreamSocket e un oggetto StreamSocketListener che inviano e ricevono dati tramite TCP per formare un client e server echo.
Per iniziare con il minor numero possibile di elementi mobili (e per evitare problemi di isolamento di rete per il presente), crea un nuovo progetto e inserisci il codice per client e server riportato di seguito nello stesso progetto.
Dovrai dichiarare una funzionalità dell'app nel progetto. Apri il file di origine relativo al manifesto del pacchetto dell'app (file Package.appxmanifest
) e nella scheda Funzionalità seleziona Reti private (client e server). Ecco un esempio nel markup Package.appxmanifest
.
<Capability Name="privateNetworkClientServer" />
Invece di privateNetworkClientServer
, puoi dichiarare internetClientServer
se la connessione avviene tramite Internet. È necessario che una o l'altra di queste funzionalità dell'app sia dichiarata sia per StreamSocket che per StreamSocketListener.
Crea un oggetto StreamSocketListener e inizia ad attendere le connessioni TCP in ingresso. L'evento StreamSocketListener.ConnectionReceived viene generato ogni volta che un client stabilisce una connessione con l'oggetto StreamSocketListener.
Costruisci inoltre un oggetto StreamSocket, stabilisci una connessione al server, invia una richiesta e ricevi una risposta.
Crea una nuova pagina chiamata StreamSocketAndListenerPage
. Inserisci il markup XAML in StreamSocketAndListenerPage.xaml
e aggiungi il codice imperativo all'interno della classe 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();
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 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();
}
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.serverListBox.Items.Add(string.Format("server sent back the response: \"{0}\"", request)));
sender.Dispose();
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 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/Windows.UI.Core.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
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 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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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);
}
}
Riferimenti a oggetti StreamSocket nelle continuazioni PPL di C++ (si applica principalmente a C++/CX)
Nota
Se usi le coroutine C++/WinRT e passi i parametri in base al valore, questo problema non si applica. Per consigli sul passaggio di parametri, vedi Concorrenza e operazioni asincrone con C++/WinRT.
Un oggetto StreamSocket rimane attivo fino a quando esiste un'operazione di lettura/scrittura attiva nel relativo flusso di input/output (prendiamo ad esempio StreamSocketListenerConnectionReceivedEventArgs.Socket a cui puoi accedere nel gestore eventi StreamSocketListener.ConnectionReceived). La chiamata a DataReader.LoadAsync (o ReadAsync/WriteAsync/StoreAsync
) contiene quindi un riferimento al socket (tramite il flusso di input del socket) finché il gestore eventi completati (se presente) di LoadAsync non termina l'esecuzione.
La libreria PPL (Parallel Patterns Library) non pianifica le continuazioni delle attività inline per impostazione predefinita. In altre parole, l'aggiunta di un'attività di continuazione (con task::then()
) non garantisce che quest'ultima sia eseguita inline come gestore di completamento.
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.
});
}
Dal punto di vista dell'oggetto StreamSocket, il gestore di completamento termina l'esecuzione (e il socket è idoneo per l'eliminazione) prima che venga eseguito il corpo della continuazione. Pertanto, per evitare l'eliminazione del socket e per usarlo all'interno di tale continuazione, devi fare riferimento al socket direttamente (tramite l'acquisizione lambda) e usarlo oppure indirettamente (continuando ad accedere a args->Socket
all'interno delle continuazioni) o, infine, forzare le attività di continuazione a restare inline. La prima tecnica (acquisizione lambda) è illustrata nell'esempio di StreamSocket. Il codice C++/CX nella sezione Creare un client e un server socket TCP di base precedente usa la seconda tecnica, ovvero ripete la richiesta come risposta e accede a args->Socket
da una delle continuazioni più interne.
La terza tecnica è appropriata quando non c'è ripetizione della risposta. L'uso dell'opzione task_continuation_context::use_synchronous_execution()
forza la libreria PPL a eseguire il corpo della continuazione inline. Ecco un esempio di codice che illustra come fare.
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());
}
Questo comportamento si applica a tutte le classi socket e WebSocket nello spazio dei nomi Windows.Networking.Sockets. Ma gli scenari lato client prevedono in genere la memorizzazione dei socket nelle variabili membro, quindi il problema è applicabile principalmente allo scenario StreamSocketListener.ConnectionReceived illustrato sopra.
Un socket UDP (User Datagram Protocol) è simile a un socket TCP perché anch'esso fornisce trasferimenti di dati in rete a basso livello nelle due direzioni. Tuttavia, mentre un socket TCP è destinato a connessioni di lunga durata, un socket UDP è per applicazioni in cui non è necessaria una connessione stabilita. Poiché non mantengono la connessione su entrambi gli endpoint, i socket UDP sono una soluzione rapida e semplice per le connessioni di rete tra computer remoti. I socket UDP non assicurano però l'integrità dei pacchetti di rete e non garantiscono neanche l'arrivo dei pacchetti alla destinazione remota. L'app dovrà quindi essere progettata per tollerare questo comportamento. Alcuni esempi di applicazioni che usano i socket UDP sono i client di individuazione della rete locale e di chat locale.
Per illustrare le operazioni UDP di base, il codice di esempio riportato di seguito mostra la classe DatagramSocket usata per inviare e ricevere dati tramite UDP per formare un client e un server echo. Crea un nuovo progetto e inserisci il codice per client e server riportato di seguito nello stesso progetto. Proprio come per un socket TCP, dovrai dichiarare la funzionalità dell'app Reti private (client e server).
Costruisci un oggetto DatagramSocket per riprodurre il ruolo del server echo, associalo a un numero di porta specifico, attendi un messaggio UDP in ingresso e ripetilo. L'evento DatagramSocket.MessageReceived viene generato quando viene ricevuto un messaggio nel socket.
Costruisci un altro oggetto DatagramSocket per riprodurre il ruolo del client echo, associalo a un numero di porta specifico, invia un messaggio UDP e ricevi una risposta.
Crea una nuova pagina chiamata DatagramSocketPage
. Inserisci il markup XAML in DatagramSocketPage.xaml
e aggiungi il codice imperativo all'interno della classe 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 TCP connections 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();
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 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();
}
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.serverListBox.Items.Add(string.Format("server sent back the response: \"{0}\"", request)));
sender.Dispose();
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 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();
}
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => this.clientListBox.Items.Add(string.Format("client received the response: \"{0}\"", response)));
sender.Dispose();
await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => 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/Windows.UI.Core.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
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 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 TCP connections 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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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.
Windows::Networking::Sockets::DatagramSocket serverDatagramSocket;
IOutputStream outputStream = co_await m_serverDatagramSocket.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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [=]()
{
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 TCP connections 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"); }));
}
Puoi usare il gestore del socket e i trigger canale di controllo per garantire che la tua app riceva correttamente connessioni o dati tramite socket mentre non è in primo piano. Per altre informazioni, vedi Comunicazioni di rete in background.
Quando effettui operazioni di scrittura nel flusso associato a un socket, si verifica una transizione dalla modalità utente (il tuo codice) alla modalità kernel (dove si trova lo stack di rete). Se la scrittura coinvolge più buffer alla volta, queste transizioni ripetute danno vita a un notevole sovraccarico. L'invio in batch permette di inviare più buffer di dati insieme, evitando sovraccarichi. È particolarmente utile se la tua app esegue attività VoIP, VPN o di altro tipo che comportano lo spostamento di una grande quantità di dati nel modo più efficiente possibile.
In questa sezione vengono illustrate alcune tecniche di invio in batch che puoi usare con un oggetto StreamSocket o con un oggetto DatagramSocket connesso.
Per ottenere una baseline, vediamo come inviare un numero elevato di buffer in modo poco efficiente. Ecco una dimostrazione minima tramite 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/Windows.UI.Core.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
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 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();
}
}
Questo primo esempio di una tecnica più efficiente è appropriato solo se usi C#. Modifica OnNavigatedTo
per chiamare BatchedSendsCSharpOnly
anziché SendMultipleBuffersInefficiently
o 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);
}
Il prossimo esempio è appropriato per qualsiasi linguaggio UWP, non solo per C#. Si basa sul comportamento in StreamSocket.OutputStream e DatagramSocket.OutputStream che eseguono gli invii in batch insieme. Le tecnica chiama FlushAsync su tale flusso di output il cui ritorno, a partire da Windows 10, è garantito solo dopo il completamento di tutte le operazioni sul flusso di output.
// 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());
}
L'uso di operazioni di invio in batch nel codice comporta alcune importanti limitazioni.
- Non puoi infatti modificare il contenuto delle istanze di IBuffer di cui è in corso la scrittura fino al termine dell'operazione di scrittura asincrona.
- Il modello FlushAsync funziona solo su StreamSocket.OutputStream e DatagramSocket.OutputStream.
- Il modello FlushAsync funziona solo in Windows 10 e versioni successive.
- In altri casi usa Task.WaitAll invece del modello FlushAsync.
Puoi configurare un oggetto DatagramSocket perché coesista con altri socket multicast Win32 o UWP associati alla stessa porta/indirizzo. A questo scopo, imposta DatagramSocketControl.MulticastOnly su true
prima di associare o collegare il socket. Puoi accedere a un'istanza di DatagramSocketControl dall'oggetto DatagramSocket stesso tramite la relativa proprietà DatagramSocket.Control.
StreamSocket supporta l'uso di SSL/TLS per autenticare il server con cui comunica l'app client. In alcuni casi l'app client deve eseguire l'autenticazione con il server con un certificato client SSL/TLS. Puoi fornire un certificato client con la proprietà StreamSocketControl.ClientCertificate prima di associare o collegare un socket (l'impostazione deve essere effettuata prima dell'avvio dell'handshake SSL/TLS). Puoi accedere a un'istanza di StreamSocketControl dall'oggetto StreamSocket stesso tramite la relativa proprietà StreamSocket.Control. Se il server richiede il certificato client, Windows risponderà con il certificato client fornito.
Usa un override di StreamSocket.ConnectAsync che accetta un oggetto SocketProtectionLevel, come illustrato in questo esempio con codice minimo.
Importante
Come indicato dal commento negli esempi di codice riportati di seguito, il progetto deve dichiarare la capacità dell'app sharedUserCertificates affinché questo codice funzioni.
// 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(
[=]
{
...
});
}
});
Un errore verificatosi in un'operazione DatagramSocket, StreamSocket o StreamSocketListener viene restituito come valore HRESULT. Puoi passare tale valore HRESULT al metodo SocketError.GetStatus per convertirlo in un valore di enumerazione SocketErrorStatus.
La maggior parte dei valori di enumerazione SocketErrorStatus corrisponde a un errore restituito dall'operazione socket di Windows nativa. L'app può attivare i valori di enumerazione di SocketErrorStatus allo scopo di modificare il comportamento dell'app a seconda della causa dell'eccezione.
Per quanto riguarda gli errori di convalida dei parametri, puoi usare il valore HRESULT dell'eccezione per ottenere informazioni più dettagliate sull'errore. I possibili valori HRESULT sono elencati in Winerror.h
, reperibile nell'installazione dell'SDK (ad esempio nella cartella C:\Program Files (x86)\Windows Kits\10\Include\<VERSION>\shared
). Per gran parte degli errori di convalida dei parametri, il valore HRESULT restituito è E\_INVALIDARG.
Il costruttore HostName può generare un'eccezione se la stringa passata non è un nome host valido. Ad esempio contiene caratteri non consentiti, il che è probabile se il nome host viene digitato nell'app dall'utente. Costruisci un oggetto HostName all'interno di un blocco try/catch. In questo modo, se viene generata un'eccezione, l'app può inviare una notifica all'utente e richiedere un nuovo nome host.
- CertificateQuery
- CertificateStores.FindAllAsync
- DatagramSocket
- DatagramSocket.BindServiceNameAsync
- DatagramSocket.Control
- DatagramSocket.GetOutputStreamAsync
- DatagramSocket.MessageReceived
- DatagramSocketControl.MulticastOnly
- DatagramSocketMessageReceivedEventArgs
- DatagramSocketMessageReceivedEventArgs.GetDataReader
- DataReader.LoadAsync
- IOutputStream.FlushAsync
- SocketError.GetStatus
- SocketErrorStatus
- SocketProtectionLevel
- StreamSocket
- StreamSocketControl.ClientCertificate
- StreamSocket.ConnectAsync
- StreamSocket.InputStream
- StreamSocket.OutputStream
- StreamSocketListener
- StreamSocketListener.BindServiceNameAsync
- StreamSocketListener.ConnectionReceived
- StreamSocketListenerConnectionReceivedEventArgs
- Windows.Networking.Sockets