Networking
Beyond reading the connection status (NetworkInfo) and downloading over HTTP
(HttpClient), the SDK exposes sockets, so a module can be a network client or a
server: a file transfer tool, a small web server, a remote-control listener, or a custom protocol.
Sockets live in SharpProspero.Platform. They are IPv4 and cover TCP and UDP, with a poller for
serving many connections from one thread and a resolver for connecting by host name.
Addresses
A SocketAddress is an IPv4 endpoint: four address octets and a port.
var any = SocketAddress.Any(8080); // every interface, port 8080
var local = SocketAddress.Loopback(9000); // 127.0.0.1:9000
var server = SocketAddress.Parse("192.168.1.10", 21);
A TCP client
Connect, send a request, read the reply, and dispose the connection.
using var conn = TcpConnection.Connect(SocketAddress.Parse("192.168.1.10", 80));
conn.SendAll("GET / HTTP/1.0\r\n\r\n"u8);
Span<byte> buffer = stackalloc byte[2048];
int read = conn.Receive(buffer); // 0 means the peer closed the connection
SendAll repeats until every byte is accepted. Send sends once and reports how many bytes went, for
callers that manage their own buffering.
A TCP server
Bind a listener, accept a client, serve it. This shape handles one client at a time.
using var listener = TcpListener.Listen(SocketAddress.Any(8080));
while (running)
{
using TcpConnection client = listener.Accept();
Span<byte> request = stackalloc byte[1024];
int read = client.Receive(request);
client.SendAll(response);
}
Serving many clients from one thread
To serve several connections at once without threads, set the sockets to non-blocking and drive them
from a SocketPoller. The poller reports which sockets are ready; register each with a token the
caller chooses, usually an index into its own table of connections.
using var listener = TcpListener.Listen(SocketAddress.Any(8080));
listener.Blocking = false;
using var poller = SocketPoller.Create();
poller.Add(listener.Handle, PollEvents.Read, token: 0);
Span<PollReady> ready = stackalloc PollReady[32];
while (running)
{
int count = poller.Wait(ready, timeoutMicroseconds: -1); // -1 waits until something is ready
for (int i = 0; i < count; i++)
{
if (ready[i].Token == 0)
{
TcpConnection client = listener.Accept();
client.Blocking = false;
// register client.Handle with its own token and keep it in a table
}
else if (ready[i].IsReadable)
{
// read from the connection the token maps to
}
}
}
SocketPoller.Abort unblocks a thread waiting in Wait, so a server loop can be told to stop.
An HTTP server
HttpServer builds a small HTTP/1.1 server on these sockets, so a module can serve a page or an API to
a phone or a computer on the same network — a remote control panel, a status page, or a file browser.
Start it on a port and call PollOnce each frame so it never blocks the loop; it answers one waiting
request and returns. A handler maps a request to a response.
using var server = HttpServer.Start(8080);
// In the frame loop, once per frame:
server.PollOnce(request => request.Path switch
{
"/" => HttpServerResponse.Html("<h1>Hello from C#</h1>"),
"/status" => HttpServerResponse.Json("{\"ok\":true}"),
_ => HttpServerResponse.NotFound(),
});
HttpServerRequest gives the Method, Path (percent-decoded), Query, Headers and Body.
HttpServerResponse has Text, Html, Json, Bytes, NotFound and Redirect builders, and its
StatusCode, ContentType, Headers and Body are settable for anything else. Each request is
answered and its connection closed, which keeps it simple and robust. To dedicate the loop to serving
instead of polling, call Run(handler, keepRunning). Bind to the loopback address only, with
Start(port, loopbackOnly: true), for a server just this console reaches.
UDP
Datagrams need no connection. Bind to receive, send to an explicit destination.
using var udp = UdpSocket.Bind(SocketAddress.Any(9000));
Span<byte> buffer = stackalloc byte[1500];
int read = udp.ReceiveFrom(buffer, out SocketAddress sender);
udp.SendTo(reply, sender);
Connecting by host name
When the target is a name rather than an address, resolve it first. A HostResolver owns a small
network pool for its lifetime.
using var dns = HostResolver.Create();
SocketAddress address = dns.Resolve("example.com", 80);
using var conn = TcpConnection.Connect(address);
Errors
A failed socket call raises a ProsperoException whose Code carries the network error, so a caller
can branch on a specific failure such as a refused connection or a timeout.