Use WebSockets

Use the RFC 6455 client honestly: ws:// only, borrowed asynchronous buffers, and explicit task completion.

AdvancedNet.WebSocketsControlled peer required

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)
Only ws:// is supported

wss:// 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.

This differs materially from managed array lifetime

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

  • CloseOutputAsync sends a close frame while retaining the receive side.
  • CloseAsync performs the close exchange and can block through its task.
  • Abort is the failure path when graceful protocol shutdown is no longer possible.
  • Dispose releases 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# / .NETSharp RuntimePorting consequence
wss:// through platform TLSws:// onlyProvide an audited secure transport elsewhere.
Memory<byte> survives async useBorrowed vector referenceKeep storage alive, fixed, and unsynchronized until completion.
awaitTask::Wait or continuationsChoose blocking/thread-affinity behavior explicitly.
DangerousDeflateOptionsNot reproducedNo permessage-deflate negotiation.

Test with a controlled peer

  1. Use a local RFC 6455 server with deterministic frames; never make public internet access a unit-test prerequisite.
  2. Test fragmented text and binary messages, ping/pong, normal close, abrupt close, invalid accept hash, cancellation, and oversize payload policy.
  3. Run under AddressSanitizer and ThreadSanitizer when investigating buffer ownership or teardown.
  4. Keep transport-security tests separate, because this component does not claim them.