Use DNS, TCP, and UDP
Move from name resolution to an owned native transport without hiding IPv4, lifetime, or blocking behavior.
Goal and components
Resolve a host deliberately, then choose TCP or UDP with a clear ownership and address-family policy. Name and address primitives belong to Net; socket clients belong to Net.Sockets.
set(SHARP_RUNTIME_COMPONENTS Net.Sockets)
target_link_libraries(app PRIVATE SharpRuntime::Net.Sockets)
The socket component brings its declared Net dependency. Do not select the All aggregate merely to reach these headers.
Resolve without hiding the family
#include <System/Net/Dns.hpp>
#include <System/Net/Sockets/AddressFamily.hpp>
using System::Net::Dns;
using System::Net::Sockets::AddressFamily;
const auto addresses =
Dns::GetHostAddresses("localhost", AddressFamily::InterNetwork);
for (const auto& address : addresses) {
const std::string text = address.ToString();
(void)text;
}
Dns supports synchronous IPv4 and IPv6 resolution on POSIX and Windows. Passing InterNetwork, InterNetworkV6, or Unspecified changes the resolver hint. Results preserve resolver order while removing exact binary duplicates. Emscripten throws PlatformNotSupportedException.
Open a TCP client
#include <System/Net/Sockets/TcpClient.hpp>
System::Net::Sockets::TcpClient client;
client.Connect("127.0.0.1", 8080);
auto stream = client.GetStream();
// Use Stream operations while both client and stream remain in scope.
client.Close();
TcpClient owns a native descriptor and is neither copyable nor implicitly shareable. GetStream() returns the same cached shared_ptr<NetworkStream> on repeated calls. Port validation must treat the client, cached stream, shutdown policy, and outstanding operations as one lifetime design.
TcpClient, TcpListener, and UdpClient currently build AF_INET/sockaddr_in paths. An IPv6 endpoint is rejected rather than silently narrowed. The lower-level Socket surface has broader address-family support, so select it when IPv6 is a requirement and verify the exact operations you need.
Send and receive a UDP datagram
#include <System/Net/Sockets/UdpClient.hpp>
using System::Net::IPEndPoint;
using System::Net::Sockets::UdpClient;
UdpClient sender;
sender.Connect("127.0.0.1", 9000);
const std::vector<SharpRuntime::bytecs> payload{0x53, 0x52};
const auto sent = sender.Send(payload,
static_cast<SharpRuntime::intcs>(payload.size()));
(void)sent;
// A receiver normally binds its local port in the constructor.
// Receive blocks and replaces remote with the sender endpoint.
IPEndPoint remote{System::Net::IPAddress::Any, 0};
// std::vector<SharpRuntime::bytecs> reply = receiver.Receive(remote);
The final line is intentionally commented because a complete runnable example needs a cooperating peer and a timeout policy. UdpClient::Receive blocks; cancellation is not part of this synchronous convenience API. Validate datagram length and preserve message boundaries instead of treating UDP as a stream.
Map failures at the boundary
| Operation | Representative failure | Application decision |
|---|---|---|
| Resolve name | SocketException(HostNotFound) | Retry another configured host, report configuration, or stop. |
| Validate port | ArgumentOutOfRangeException | Reject input before network work. |
| Connect TCP | SocketException | Classify refusal, routing, timeout, and cancellation policy outside the type. |
| Use unsupported platform | PlatformNotSupportedException | Choose an alternate backend at configure time. |
Do not turn every socket error into an empty result. C# ports often rely on exception type, socket error code, and retry scope as observable behavior.
C# comparison
| C# / .NET | Sharp Runtime | Important difference |
|---|---|---|
await Dns.GetHostAddressesAsync | Dns::GetHostAddresses | The current DNS surface is synchronous. |
using TcpClient | Automatic destructor or explicit Close | Native lifetime is lexical; copies are deleted. |
| IPv4/IPv6 convenience clients | Convenience clients currently IPv4-only | Use lower-level Socket for an audited IPv6 path. |
| GC keeps referenced arrays alive | Caller-owned native buffers | Async lower-level operations need explicit shared lifetime. |
Verification checklist
- Compile the consumer against
SharpRuntime::Net.Sockets, not accidental transitive targets. - Test numeric literals separately from resolver-backed names.
- Exercise refusal, invalid port, early peer close, partial TCP reads/writes, and UDP truncation policy.
- Run integration tests on the actual OS and network namespace used in deployment.
- For internet protocols, add framing, deadlines, authentication, and transport security above these primitives.