WebSockets
WebSocket 提供了一种机制,用于使用 HTTP 通过 Web 在客户端与服务器之间进行既快捷又安全的双向通信,同时支持 UTF-8 和二进制消息。
在 WebSocket 协议下,数据通过全双工单套接字连接立即传输,从而允许从两个终结点实时发送和接收消息。 WebSocket 非常适用于在多玩家游戏(实时游戏和回合制游戏)、即时社交网络通知、显示的最新股票和天气信息以及需要安全、快速地传输数据的其他应用。
为建立 WebSocket 连接,需在客户端与服务器之间交换基于 HTTP 的特定握手。 如果成功,则应用程序层协议会使用之前建立的 TCP 连接从 HTTP“升级”为 WebSocket。 然后就完全不使用 HTTP;两个终结点可以使用 WebSocket 协议来发送或接收数据,直至 WebSocket 连接关闭。
注意:除非服务器也使用 WebSocket 协议,否则客户端无法使用 Websocket 传输数据。 如果服务器不支持 WebSocket,则必须使用其他数据传输方法。
通用 Windows 平台 (UWP) 支持客户端和服务器使用 Websocket。 Windows.Networking.Sockets 命名空间定义两个供客户端使用的 WebSocket 类:MessageWebSocket 和 StreamWebSocket。 下面对这两个 WebSocket 类进行了比较。
MessageWebSocket | StreamWebSocket |
---|---|
在单个操作中读取/写入整个 WebSocket 消息。 | 可通过每个读取操作读取消息的各部分。 |
适用于消息不太长的情况。 | 适用于传输大文件(如照片或视频)的情况。 |
支持 UTF-8 和二进制消息。 | 仅支持二进制消息。 |
与 UDP 或数据报套接字相似(从适用于频繁、小型消息的角度而言),但又具有 TCP 的可靠性、数据包顺序保证和拥塞控制。 | 类似于 TCP 或流套接字。 |
通过 TLS/SSL 保证连接安全
在大多数情况下,需要使用安全的 WebSocket 连接,以便对你发送和接收的数据加密。 这还将提高连接成功的几率,因为防火墙和代理之类的许多媒介都会拒绝未加密的 WebSocket 连接。 WebSocket 协议定义了以下两种 URI 方案。
URI 方案 | 目的 |
---|---|
wss: | 用于应该加密的安全连接。 |
ws: | 用于未加密的连接。 |
若要加密 WebSocket 连接,请使用 wss:
URI 方案。 下面是一个示例。
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var webSocket = new Windows.Networking.Sockets.MessageWebSocket();
await webSocket.ConnectAsync(new Uri("wss://www.contoso.com/mywebservice"));
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::UI::Xaml::Navigation;
...
IAsyncAction OnNavigatedTo(NavigationEventArgs /* e */)
{
Windows::Networking::Sockets::MessageWebSocket webSocket;
co_await webSocket.ConnectAsync(Uri{ L"wss://www.contoso.com/mywebservice" });
}
使用 MessageWebSocket 连接
MessageWebSocket 允许在单个操作中读取/写入整个 WebSocket 消息。 因此,它适用于消息不太长的情况。 该类同时支持 UTF-8 和二进制消息。
下面的代码示例使用了 WebSocket.org 回显服务器,该服务将其接收的任何消息回显回发送者。
private Windows.Networking.Sockets.MessageWebSocket messageWebSocket;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.messageWebSocket = new Windows.Networking.Sockets.MessageWebSocket();
// In this example, we send/receive a string, so we need to set the MessageType to Utf8.
this.messageWebSocket.Control.MessageType = Windows.Networking.Sockets.SocketMessageType.Utf8;
this.messageWebSocket.MessageReceived += WebSocket_MessageReceived;
this.messageWebSocket.Closed += WebSocket_Closed;
try
{
Task connectTask = this.messageWebSocket.ConnectAsync(new Uri("wss://echo.websocket.org")).AsTask();
connectTask.ContinueWith(_ => this.SendMessageUsingMessageWebSocketAsync("Hello, World!"));
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add additional code here to handle exceptions.
}
}
private async Task SendMessageUsingMessageWebSocketAsync(string message)
{
using (var dataWriter = new DataWriter(this.messageWebSocket.OutputStream))
{
dataWriter.WriteString(message);
await dataWriter.StoreAsync();
dataWriter.DetachStream();
}
Debug.WriteLine("Sending message using MessageWebSocket: " + message);
}
private void WebSocket_MessageReceived(Windows.Networking.Sockets.MessageWebSocket sender, Windows.Networking.Sockets.MessageWebSocketMessageReceivedEventArgs args)
{
try
{
using (DataReader dataReader = args.GetDataReader())
{
dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
string message = dataReader.ReadString(dataReader.UnconsumedBufferLength);
Debug.WriteLine("Message received from MessageWebSocket: " + message);
this.messageWebSocket.Dispose();
}
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add additional code here to handle exceptions.
}
}
private void WebSocket_Closed(Windows.Networking.Sockets.IWebSocket sender, Windows.Networking.Sockets.WebSocketClosedEventArgs args)
{
Debug.WriteLine("WebSocket_Closed; Code: " + args.Code + ", Reason: \"" + args.Reason + "\"");
// Add additional code here to handle the WebSocket being closed.
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.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::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::MessageWebSocket m_messageWebSocket;
winrt::event_token m_messageReceivedEventToken;
winrt::event_token m_closedEventToken;
public:
IAsyncAction OnNavigatedTo(NavigationEventArgs /* e */)
{
// In this example, we send/receive a string, so we need to set the MessageType to Utf8.
m_messageWebSocket.Control().MessageType(Windows::Networking::Sockets::SocketMessageType::Utf8);
m_messageReceivedEventToken = m_messageWebSocket.MessageReceived({ this, &MessageWebSocketPage::OnWebSocketMessageReceived });
m_closedEventToken = m_messageWebSocket.Closed({ this, &MessageWebSocketPage::OnWebSocketClosed });
try
{
co_await m_messageWebSocket.ConnectAsync(Uri{ L"wss://echo.websocket.org" });
SendMessageUsingMessageWebSocketAsync(L"Hello, World!");
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
private:
IAsyncAction SendMessageUsingMessageWebSocketAsync(std::wstring message)
{
DataWriter dataWriter{ m_messageWebSocket.OutputStream() };
dataWriter.WriteString(message);
co_await dataWriter.StoreAsync();
dataWriter.DetachStream();
std::wstringstream wstringstream;
wstringstream << L"Sending message using MessageWebSocket: " << message.c_str() << std::endl;
::OutputDebugString(wstringstream.str().c_str());
}
void OnWebSocketMessageReceived(Windows::Networking::Sockets::MessageWebSocket const& /* sender */, Windows::Networking::Sockets::MessageWebSocketMessageReceivedEventArgs const& args)
{
try
{
DataReader dataReader{ args.GetDataReader() };
dataReader.UnicodeEncoding(Windows::Storage::Streams::UnicodeEncoding::Utf8);
auto message = dataReader.ReadString(dataReader.UnconsumedBufferLength());
std::wstringstream wstringstream;
wstringstream << L"Message received from MessageWebSocket: " << message.c_str() << std::endl;
::OutputDebugString(wstringstream.str().c_str());
m_messageWebSocket.Close(1000, L"");
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
void OnWebSocketClosed(Windows::Networking::Sockets::IWebSocket const& /* sender */, Windows::Networking::Sockets::WebSocketClosedEventArgs const& args)
{
std::wstringstream wstringstream;
wstringstream << L"WebSocket_Closed; Code: " << args.Code() << ", Reason: \"" << args.Reason().c_str() << "\"" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
// Add additional code here to handle the WebSocket being closed.
}
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::MessageWebSocket^ messageWebSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->messageWebSocket = ref new Windows::Networking::Sockets::MessageWebSocket();
// In this example, we send/receive a string, so we need to set the MessageType to Utf8.
this->messageWebSocket->Control->MessageType = Windows::Networking::Sockets::SocketMessageType::Utf8;
this->messageWebSocket->MessageReceived += ref new TypedEventHandler<Windows::Networking::Sockets::MessageWebSocket^, Windows::Networking::Sockets::MessageWebSocketMessageReceivedEventArgs^>(this, &MessageWebSocketPage::WebSocket_MessageReceived);
this->messageWebSocket->Closed += ref new TypedEventHandler<Windows::Networking::Sockets::IWebSocket^, Windows::Networking::Sockets::WebSocketClosedEventArgs^>(this, &MessageWebSocketPage::WebSocket_Closed);
try
{
auto connectTask = Concurrency::create_task(this->messageWebSocket->ConnectAsync(ref new Uri(L"wss://echo.websocket.org")));
connectTask.then([this] { this->SendMessageUsingMessageWebSocketAsync(L"Hello, World!"); });
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
private:
void SendMessageUsingMessageWebSocketAsync(Platform::String^ message)
{
auto dataWriter = ref new DataWriter(this->messageWebSocket->OutputStream);
dataWriter->WriteString(message);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](unsigned int)
{
dataWriter->DetachStream();
std::wstringstream wstringstream;
wstringstream << L"Sending message using MessageWebSocket: " << message->Data() << std::endl;
::OutputDebugString(wstringstream.str().c_str());
});
}
void WebSocket_MessageReceived(Windows::Networking::Sockets::MessageWebSocket^ sender, Windows::Networking::Sockets::MessageWebSocketMessageReceivedEventArgs^ args)
{
try
{
DataReader^ dataReader = args->GetDataReader();
dataReader->UnicodeEncoding = Windows::Storage::Streams::UnicodeEncoding::Utf8;
Platform::String^ message = dataReader->ReadString(dataReader->UnconsumedBufferLength);
std::wstringstream wstringstream;
wstringstream << L"Message received from MessageWebSocket: " << message->Data() << std::endl;
::OutputDebugString(wstringstream.str().c_str());
this->messageWebSocket->Close(1000, L"");
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
void WebSocket_Closed(Windows::Networking::Sockets::IWebSocket^ sender, Windows::Networking::Sockets::WebSocketClosedEventArgs^ args)
{
std::wstringstream wstringstream;
wstringstream << L"WebSocket_Closed; Code: " << args->Code << ", Reason: \"" << args->Reason->Data() << "\"" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
// Add additional code here to handle the WebSocket being closed.
}
处理 MessageWebSocket.MessageReceived 和 MessageWebSocket.Closed 事件
如上例所示,在用 MessageWebSocket 建立连接并发送数据之前,应订阅 MessageWebSocket.MessageReceived 和 MessageWebSocket.Closed 事件。
接收数据时会引发 MessageReceived。 可通过 MessageWebSocketMessageReceivedEventArgs 访问数据。 客户端或服务器关闭套接字时会引发 Closed。
通过 MessageWebSocket 发送数据
建立连接后,可以向服务器发送数据。 执行此操作的方法是使用 MessageWebSocket.OutputStream 属性和 DataWriter 写入数据。
注意:DataWriter 获得输出流的所有权。 DataWriter 超出范围时,如果输出流与之连接,DataWriter 将解除分配输出流。 此后,使用该输出流的任何后续尝试都会失败,HRESULT 值为 0x80000013。 但你可以调用 DataWriter.DetachStream 使输出流与 DataWriter 分离并将该流的所有权返回给 MessageWebSocket。
使用 StreamWebSocket 连接
StreamWebSocket 允许通过每个读取操作读取消息的各部分。 因此,它适用于传输大文件(如照片或视频)的情况。 该类仅支持二进制消息。
下面的代码示例使用了 WebSocket.org 回显服务器,该服务将其接收的任何消息回显回发送者。
private Windows.Networking.Sockets.StreamWebSocket streamWebSocket;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.streamWebSocket = new Windows.Networking.Sockets.StreamWebSocket();
this.streamWebSocket.Closed += WebSocket_Closed;
try
{
Task connectTask = this.streamWebSocket.ConnectAsync(new Uri("wss://echo.websocket.org")).AsTask();
connectTask.ContinueWith(_ =>
{
Task.Run(() => this.ReceiveMessageUsingStreamWebSocket());
Task.Run(() => this.SendMessageUsingStreamWebSocket(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }));
});
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add code here to handle exceptions.
}
}
private async void ReceiveMessageUsingStreamWebSocket()
{
try
{
using (var dataReader = new DataReader(this.streamWebSocket.InputStream))
{
dataReader.InputStreamOptions = InputStreamOptions.Partial;
await dataReader.LoadAsync(256);
byte[] message = new byte[dataReader.UnconsumedBufferLength];
dataReader.ReadBytes(message);
Debug.WriteLine("Data received from StreamWebSocket: " + message.Length + " bytes");
}
this.streamWebSocket.Dispose();
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add code here to handle exceptions.
}
}
private async void SendMessageUsingStreamWebSocket(byte[] message)
{
try
{
using (var dataWriter = new DataWriter(this.streamWebSocket.OutputStream))
{
dataWriter.WriteBytes(message);
await dataWriter.StoreAsync();
dataWriter.DetachStream();
}
Debug.WriteLine("Sending data using StreamWebSocket: " + message.Length.ToString() + " bytes");
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add code here to handle exceptions.
}
}
private void WebSocket_Closed(Windows.Networking.Sockets.IWebSocket sender, Windows.Networking.Sockets.WebSocketClosedEventArgs args)
{
Debug.WriteLine("WebSocket_Closed; Code: " + args.Code + ", Reason: \"" + args.Reason + "\"");
// Add additional code here to handle the WebSocket being closed.
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.Storage.Streams.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::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamWebSocket m_streamWebSocket;
winrt::event_token m_closedEventToken;
public:
IAsyncAction OnNavigatedTo(NavigationEventArgs /* e */)
{
m_closedEventToken = m_streamWebSocket.Closed({ this, &StreamWebSocketPage::OnWebSocketClosed });
try
{
co_await m_streamWebSocket.ConnectAsync(Uri{ L"wss://echo.websocket.org" });
ReceiveMessageUsingStreamWebSocket();
SendMessageUsingStreamWebSocket({ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 });
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
private:
IAsyncAction SendMessageUsingStreamWebSocket(std::vector< byte > message)
{
try
{
DataWriter dataWriter{ m_streamWebSocket.OutputStream() };
dataWriter.WriteBytes(message);
co_await dataWriter.StoreAsync();
dataWriter.DetachStream();
std::wstringstream wstringstream;
wstringstream << L"Sending data using StreamWebSocket: " << message.size() << L" bytes" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
IAsyncAction ReceiveMessageUsingStreamWebSocket()
{
try
{
DataReader dataReader{ m_streamWebSocket.InputStream() };
dataReader.InputStreamOptions(InputStreamOptions::Partial);
unsigned int bytesLoaded = co_await dataReader.LoadAsync(256);
std::vector< byte > message(bytesLoaded);
dataReader.ReadBytes(message);
std::wstringstream wstringstream;
wstringstream << L"Data received from StreamWebSocket: " << message.size() << " bytes" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
m_streamWebSocket.Close(1000, L"");
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
void OnWebSocketClosed(Windows::Networking::Sockets::IWebSocket const&, Windows::Networking::Sockets::WebSocketClosedEventArgs const& args)
{
std::wstringstream wstringstream;
wstringstream << L"WebSocket_Closed; Code: " << args.Code() << ", Reason: \"" << args.Reason().c_str() << "\"" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
// Add additional code here to handle the WebSocket being closed.
}
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::StreamWebSocket^ streamWebSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->streamWebSocket = ref new Windows::Networking::Sockets::StreamWebSocket();
this->streamWebSocket->Closed += ref new TypedEventHandler<Windows::Networking::Sockets::IWebSocket^, Windows::Networking::Sockets::WebSocketClosedEventArgs^>(this, &StreamWebSocketPage::WebSocket_Closed);
try
{
auto connectTask = Concurrency::create_task(this->streamWebSocket->ConnectAsync(ref new Uri(L"wss://echo.websocket.org")));
connectTask.then(
[=]
{
this->ReceiveMessageUsingStreamWebSocket();
this->SendMessageUsingStreamWebSocket(ref new Platform::Array< byte >{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 });
});
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
private:
void SendMessageUsingStreamWebSocket(const Platform::Array< byte >^ message)
{
try
{
auto dataWriter = ref new DataWriter(this->streamWebSocket->OutputStream);
dataWriter->WriteBytes(message);
Concurrency::create_task(dataWriter->StoreAsync()).then(
[=](Concurrency::task< unsigned int >) // task< unsigned int > instead of unsigned int in order to handle any exceptions thrown in StoreAsync().
{
dataWriter->DetachStream();
std::wstringstream wstringstream;
wstringstream << L"Sending data using StreamWebSocket: " << message->Length << L" bytes" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
});
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
void ReceiveMessageUsingStreamWebSocket()
{
try
{
DataReader^ dataReader = ref new DataReader(this->streamWebSocket->InputStream);
dataReader->InputStreamOptions = InputStreamOptions::Partial;
Concurrency::create_task(dataReader->LoadAsync(256)).then(
[=](unsigned int bytesLoaded)
{
auto message = ref new Platform::Array< byte >(bytesLoaded);
dataReader->ReadBytes(message);
std::wstringstream wstringstream;
wstringstream << L"Data received from StreamWebSocket: " << message->Length << " bytes" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
this->streamWebSocket->Close(1000, L"");
});
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
void WebSocket_Closed(Windows::Networking::Sockets::IWebSocket^ sender, Windows::Networking::Sockets::WebSocketClosedEventArgs^ args)
{
std::wstringstream wstringstream;
wstringstream << L"WebSocket_Closed; Code: " << args->Code << ", Reason: \"" << args->Reason->Data() << "\"" << std::endl;
::OutputDebugString(wstringstream.str().c_str());
// Add additional code here to handle the WebSocket being closed.
}
处理 StreamWebSocket.Closed 事件
在用 StreamWebSocket 建立连接和发送数据之前,应该订阅 StreamWebSocket.Closed 事件。 客户端或服务器关闭套接字时会引发 Closed。
通过 StreamWebSocket 发送数据
建立连接后,可以向服务器发送数据。 执行此操作的方法是使用 StreamWebSocket.OutputStream 属性和 DataWriter 写入数据。
注意:如果你要在同一套接字中写入许多数据,则务必在 DataWriter 超出范围之前调用 DataWriter.DetachStream,以使输出流与 DataWriter 分离。 此操作会将流的所有权返回给 MessageWebSocket。
通过 StreamWebSocket 接收数据
使用 StreamWebSocket.InputStream 属性和 DataReader 可读取数据。
用于 MessageWebSocket 和 StreamWebSocket 的高级选项
在建立连接之前,可以通过设置 MessageWebSocketControl 或 StreamWebSocketControl 的属性来设置用于套接字的高级选项。 通过它的相应 MessageWebSocket.Control 属性或 StreamWebSocket.Control 属性可从套接字对象自身中访问这些类的实例。
下面是使用 StreamWebSocket 的示例。 相同的模式适用于 MessageWebSocket。
var streamWebSocket = new Windows.Networking.Sockets.StreamWebSocket();
// By default, the Nagle algorithm is not used. This overrides that, and causes it to be used.
streamWebSocket.Control.NoDelay = false;
await streamWebSocket.ConnectAsync(new Uri("wss://echo.websocket.org"));
Windows::Networking::Sockets::StreamWebSocket streamWebSocket;
// By default, the Nagle algorithm is not used. This overrides that, and causes it to be used.
streamWebSocket.Control().NoDelay(false);
auto connectAsyncAction = streamWebSocket.ConnectAsync(Uri{ L"wss://echo.websocket.org" });
auto streamWebSocket = ref new Windows::Networking::Sockets::StreamWebSocket();
// By default, the Nagle algorithm is not used. This overrides that, and causes it to be used.
streamWebSocket->Control->NoDelay = false;
auto connectTask = Concurrency::create_task(streamWebSocket->ConnectAsync(ref new Uri(L"wss://echo.websocket.org")));
注意:不要尝试在调用 ConnectAsync 之后更改控件属性。 该规则的唯一例外是 MessageWebSocketControl.MessageType。
WebSocket 信息类
MessageWebSocket 和 StreamWebSocket 都具有相应的类,用于提供有关对象的其他信息。
MessageWebSocketInformation 提供有关 MessageWebSocket 的信息,以便于使用 MessageWebSocket.Information 属性检索它的实例。
StreamWebSocketInformation 提供有关 StreamWebSocket 的信息,以便于使用 StreamWebSocket.Information 属性检索它的实例。
请注意,这些信息类上的属性均为只读形式,但你能够在 Web 套接字对象的生存期内随时使用它们来检索信息。
处理异常
在进行 MessageWebSocket 或 StreamWebSocket 操作时发生的错误将以 HRESULT 值的形式返回。 可将该 HRESULT 值传递给 WebSocketError.GetStatus 方法,将其转换为 WebErrorStatus 枚举值。
大部分 WebErrorStatus 枚举值对应由本机 HTTP 客户端操作返回的错误。 应用可以打开 WebErrorStatus 枚举值来基于异常原因修改应用行为。
对于参数验证错误,可以使用来自异常的 HRESULT 了解有关错误的更详细信息。 Winerror.h
中列出了可能的 HRESULT 值,可以在 SDK 安装位置中找到 Winerror.h,例如,C:\Program Files (x86)\Windows Kits\10\Include\<VERSION>\shared
文件夹。 对于大多数参数验证错误,返回的 HRESULT 为 E_INVALIDARG。
对 WebSocket 操作设置超时
MessageWebSocket 和 StreamWebSocket 使用内部系统服务,发送 WebSocket 客户端请求并从服务器接收响应。 WebSocket 连接操作使用的默认超时值为 60 秒。 如果支持 WebSocket 的 HTTP 服务器不对或无法对 WebSocket 连接请求做出响应(临时关闭或因网络中断而被阻止),则内部系统服务将先等待默认 60 秒,然后返回一个错误。 该错误会导致 WebSocket ConnectAsync 方法中引发异常。 建立 WebSocket 连接后,用于发送和接收操作的默认超时为 30 秒。
如果对 URI 中 HTTP 服务器名称进行名称查询时返回该名称的多个 IP 地址,则内部系统将为该站点尝试最多 5 个 IP 地址,并且在每次尝试失败前都默认超时 60 秒。 因此,你的应用可能会在尝试连接多个 IP 地址时等待几分钟,然后再处理异常。 此行为可能会使用户以为应用已停止工作。
若要使你的应用更具响应性并尽量减少这些问题,可为连接请求设置更短的超时。 为 MessageWebSocket 和 StreamWebSocket 设置超时的方式类似。
private Windows.Networking.Sockets.MessageWebSocket messageWebSocket;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.messageWebSocket = new Windows.Networking.Sockets.MessageWebSocket();
try
{
var cancellationTokenSource = new CancellationTokenSource();
var connectTask = this.messageWebSocket.ConnectAsync(new Uri("wss://echo.websocket.org")).AsTask(cancellationTokenSource.Token);
// Cancel connectTask after 5 seconds.
cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(5000));
connectTask.ContinueWith((antecedent) =>
{
if (antecedent.Status == TaskStatus.RanToCompletion)
{
// connectTask ran to completion, so we know that the MessageWebSocket is connected.
// Add additional code here to use the MessageWebSocket.
}
else
{
// connectTask timed out, or faulted.
}
});
}
catch (Exception ex)
{
Windows.Web.WebErrorStatus webErrorStatus = Windows.Networking.Sockets.WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add additional code here to handle exceptions.
}
}
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Networking.Sockets.h>
#include <winrt/Windows.UI.Xaml.Navigation.h>
#include <sstream>
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::MessageWebSocket m_messageWebSocket;
IAsyncAction TimeoutAsync()
{
// Return control to the caller, and resume again to complete the async action after the timeout period.
// 5 seconds, in this example.
co_await(std::chrono::seconds{ 5 });
}
public:
IAsyncAction OnNavigatedTo(NavigationEventArgs /* e */)
{
try
{
// Return control to the caller, and then immediately resume on a thread pool thread.
co_await winrt::resume_background();
auto connectAsyncAction = m_messageWebSocket.ConnectAsync(Uri{ L"wss://echo.websocket.org" });
TimeoutAsync().Completed([connectAsyncAction](IAsyncAction const& sender, AsyncStatus const)
{
// TimeoutAsync completes after the timeout period. After that period, it's safe
// to cancel the ConnectAsync action even if it has already completed.
connectAsyncAction.Cancel();
});
try
{
// Block until the ConnectAsync action completes or is canceled.
connectAsyncAction.get();
}
catch (winrt::hresult_error const& ex)
{
std::wstringstream wstringstream;
wstringstream << L"ConnectAsync threw an exception: " << ex.message().c_str() << std::endl;
::OutputDebugString(wstringstream.str().c_str());
}
if (connectAsyncAction.Status() == AsyncStatus::Completed)
{
// connectTask ran to completion, so we know that the MessageWebSocket is connected.
// Add additional code here to use the MessageWebSocket.
}
else
{
// connectTask did not run to completion.
}
}
catch (winrt::hresult_error const& ex)
{
Windows::Web::WebErrorStatus webErrorStatus{ Windows::Networking::Sockets::WebSocketError::GetStatus(ex.to_abi()) };
// Add additional code here to handle exceptions.
}
}
#include <agents.h>
#include <ppltasks.h>
#include <sstream>
...
using namespace Windows::Foundation;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Xaml::Navigation;
...
private:
Windows::Networking::Sockets::MessageWebSocket^ messageWebSocket;
protected:
virtual void OnNavigatedTo(NavigationEventArgs^ e) override
{
this->messageWebSocket = ref new Windows::Networking::Sockets::MessageWebSocket();
try
{
Concurrency::cancellation_token_source cancellationTokenSource;
Concurrency::cancellation_token cancellationToken = cancellationTokenSource.get_token();
auto connectTask = Concurrency::create_task(this->messageWebSocket->ConnectAsync(ref new Uri(L"wss://echo.websocket.org")), cancellationToken);
// This continuation task returns true should connectTask run to completion.
Concurrency::task< bool > taskRanToCompletion = connectTask.then([](void)
{
return true;
});
// This task returns false after the specified timeout. 5 seconds, in this example.
Concurrency::task< bool > taskTimedout = Concurrency::create_task([]() -> bool
{
Concurrency::task_completion_event< void > taskCompletionEvent;
// A call object that sets the task completion event.
auto call = std::make_shared< Concurrency::call< int > >([taskCompletionEvent](int)
{
taskCompletionEvent.set();
});
// A non-repeating timer that calls the call object when the timer fires.
auto nonRepeatingTimer = std::make_shared< Concurrency::timer < int > >(5000, 0, call.get(), false);
nonRepeatingTimer->start();
// A task that completes after the completion event is set.
Concurrency::task< void > taskWaitForCompletionEvent(taskCompletionEvent);
return taskWaitForCompletionEvent.then([]() {return false; }).get();
});
(taskRanToCompletion || taskTimedout).then([this, cancellationTokenSource](bool connectTaskRanToCompletion)
{
if (connectTaskRanToCompletion)
{
// connectTask ran to completion, so we know that the MessageWebSocket is connected.
// Add additional code here to use the MessageWebSocket.
}
else
{
// taskTimedout ran to completion, so we should cancel connectTask via the cancellation_token_source.
cancellationTokenSource.cancel();
}
});
}
catch (Platform::Exception^ ex)
{
Windows::Web::WebErrorStatus webErrorStatus = Windows::Networking::Sockets::WebSocketError::GetStatus(ex->HResult);
// Add additional code here to handle exceptions.
}
}
重要的 API
- DataReader
- DataWriter
- DataWriter.DetachStream
- MessageWebSocket
- MessageWebSocket.Closed
- MessageWebSocket.ConnectAsync
- MessageWebSocket.Control
- MessageWebSocket.Information
- MessageWebSocket.MessageReceived
- MessageWebSocket.OutputStream
- MessageWebSocketControl
- MessageWebSocketControl.MessageType
- MessageWebSocketInformation
- MessageWebSocketMessageReceivedEventArgs
- SocketMessageType
- StreamWebSocket
- StreamWebSocket.Closed
- StreamSocket.ConnectAsync
- StreamWebSocket.Control
- StreamWebSocket.Information
- StreamWebSocket.InputStream
- StreamWebSocket.OutputStream
- StreamWebSocketControl
- StreamWebSocketInformation
- WebErrorStatus
- WebSocketError.GetStatus
- Windows.Networking.Sockets