Use WebSockets
Use the RFC 6455 client honestly: ws:// only, borrowed asynchronous buffers, and explicit task completion.
Scope and component
Net.WebSockets implements an RFC 6455 client over Sharp Runtime sockets. It performs the HTTP upgrade, validates Sec-WebSocket-Accept, masks client frames, handles ping/pong, and supports the close handshake.
set(SHARP_RUNTIME_COMPONENTS Net.WebSockets)
target_link_libraries(app PRIVATE SharpRuntime::Net.WebSockets)
ws:// is supportedwss:// throws PlatformNotSupportedException. There is no TLS, certificate validation, or per-message deflate in this component. Do not send credentials or sensitive data over an untrusted network with this transport.
Connect and wait explicitly
#include <System/Net/WebSockets/ClientWebSocket.hpp>
#include <System/Uri.hpp>
using System::Net::WebSockets::ClientWebSocket;
ClientWebSocket socket;
auto connect = socket.ConnectAsync(System::Uri("ws://127.0.0.1:8080/events"));
connect.Wait();
// State is Open only after successful task completion.
const auto state = socket.getStateProperty();
(void)state;
Sharp Runtime has no C++ language-level await integration. Wait() blocks and propagates task failure. In event-driven code, attach a continuation with an explicit executor/lifetime policy instead of blocking a UI or service loop.
Keep asynchronous buffers alive
#include <System/Net/WebSockets/WebSocketMessageType.hpp>
std::vector<SharpRuntime::bytecs> outbound{'h', 'e', 'l', 'l', 'o'};
auto send = socket.SendAsync(
outbound,
System::Net::WebSockets::WebSocketMessageType::Text);
send.Wait(); // outbound must remain alive and unmoved until here
std::vector<SharpRuntime::bytecs> inbound(4096);
auto receive = socket.ReceiveAsync(inbound);
const auto result = receive.Wait(); // inbound remains stable until completion
The implementation starts real background work and captures the vectors by reference. Until the task completes, do not destroy, move, resize, append to, or concurrently access the buffer. Reserve/resize before starting the operation; holding the vector in a stable owner does not permit mutation that reallocates it.
A C# async call keeps its managed buffer reachable. A native reference does not extend object lifetime. Letting a local vector leave scope before Wait is a use-after-free, not a canceled receive.
Honor message boundaries
One receive result can be a fragment rather than a complete application message. Inspect its message type, byte count, close state, and end-of-message flag; append only the reported bytes and continue until the message ends. Set a maximum assembled size before allocating from untrusted traffic.
// Behavioral outline; application error/size handling omitted.
std::vector<SharpRuntime::bytecs> chunk(4096);
std::vector<SharpRuntime::bytecs> message;
for (;;) {
const auto part = socket.ReceiveAsync(chunk).Wait();
// Append exactly part.Count bytes after checking the current public
// property names in the generated API inventory.
// Break on close or EndOfMessage according to your protocol.
}
This fragment-processing block is deliberately labelled an outline: the result property spellings and close policy belong in the application’s compile-verified protocol wrapper.
Close, abort, and destroy
CloseOutputAsyncsends a close frame while retaining the receive side.CloseAsyncperforms the close exchange and can block through its task.Abortis the failure path when graceful protocol shutdown is no longer possible.Disposereleases transport state; the destructor calls it.
Do not destroy the socket while connect/send/receive work still references it. Serialize application sends, coordinate receiver termination, complete outstanding tasks, and only then destroy the client.
C# comparison
| C# / .NET | Sharp Runtime | Porting consequence |
|---|---|---|
wss:// through platform TLS | ws:// only | Provide an audited secure transport elsewhere. |
Memory<byte> survives async use | Borrowed vector reference | Keep storage alive, fixed, and unsynchronized until completion. |
await | Task::Wait or continuations | Choose blocking/thread-affinity behavior explicitly. |
DangerousDeflateOptions | Not reproduced | No permessage-deflate negotiation. |
Test with a controlled peer
- Use a local RFC 6455 server with deterministic frames; never make public internet access a unit-test prerequisite.
- Test fragmented text and binary messages, ping/pong, normal close, abrupt close, invalid accept hash, cancellation, and oversize payload policy.
- Run under AddressSanitizer and ThreadSanitizer when investigating buffer ownership or teardown.
- Keep transport-security tests separate, because this component does not claim them.