# std.Io.net Reference (Zig 0.16.0) Cross-platform networking abstractions for TCP/IP connections, address handling, and DNS resolution. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html Zig 0.16 moves networking under `std.Io.net`. New networking code should accept/use `std.Io`, and stream/socket close/read/write operations should use the `io` parameter. Older `std.net` examples below are historical; translate them to `std.Io.net` before using them in new code. High-level HTTP clients also store `io`: ```zig var client: std.http.Client = .{ .allocator = allocator, .io = io, }; defer client.deinit(); ``` ## Table of Contents - [TCP Client](#tcp-client) - [TCP Server](#tcp-server) - [Address Types](#address-types) - [Stream I/O](#stream-io) - [DNS Resolution](#dns-resolution) - [Unix Sockets](#unix-sockets) - [Common Patterns](#common-patterns) ## TCP Client ### Connect by Hostname ```zig const std = @import("std"); const net = std.net; pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Connect to host:port (handles DNS resolution) const stream = try net.tcpConnectToHost(allocator, "example.com", 80); defer stream.close(); // Create buffered reader/writer var read_buf: [4096]u8 = undefined; var write_buf: [1024]u8 = undefined; var reader = stream.reader(&read_buf); var writer = stream.writer(&write_buf); // Write request try writer.interface.writeAll("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); try writer.interface.flush(); // Read response while (reader.interface().take(4096)) |chunk| { std.debug.print("{s}", .{chunk}); } else |err| switch (err) { error.EndOfStream => {}, else => return err, } } ``` ### Connect by Address ```zig // Parse and connect to IP address directly (no DNS) const address = try net.Address.parseIp4("192.168.1.1", 8080); const stream = try net.tcpConnectToAddress(address); defer stream.close(); ``` ### Connect with IPv6 ```zig // IPv6 address const addr6 = try net.Address.parseIp6("::1", 8080); const stream = try net.tcpConnectToAddress(addr6); defer stream.close(); // IPv6 with scope ID (link-local) const link_local = try net.Address.resolveIp6("fe80::1%eth0", 8080); ``` ## TCP Server ### Basic Server ```zig const std = @import("std"); const net = std.net; pub fn main() !void { // Create address to listen on const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080); // Start listening var server = try address.listen(.{ .reuse_address = true, }); defer server.deinit(); std.debug.print("Listening on port {d}\n", .{server.listen_address.getPort()}); // Accept loop while (true) { const conn = try server.accept(); defer conn.stream.close(); // Handle connection try handleClient(conn.stream, conn.address); } } fn handleClient(stream: net.Stream, client_addr: net.Address) !void { var read_buf: [4096]u8 = undefined; var write_buf: [1024]u8 = undefined; var reader = stream.reader(&read_buf); var writer = stream.writer(&write_buf); // Read request const request = reader.interface().takeDelimiter('\n') catch |err| switch (err) { error.EndOfStream => return, else => return err, } orelse return; std.debug.print("Request from client: {s}\n", .{request}); // Send response try writer.interface.writeAll("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello"); try writer.interface.flush(); } ``` ### Listen Options ```zig const server = try address.listen(.{ // Allow address reuse (SO_REUSEADDR + SO_REUSEPORT on POSIX) .reuse_address = true, // Connection backlog (default 128) .kernel_backlog = 256, // Non-blocking accept (O_NONBLOCK) .force_nonblocking = false, }); ``` ### Server on Any Available Port ```zig // Listen on port 0 to let OS assign an available port const address = net.Address.initIp4(.{ 127, 0, 0, 1 }, 0); var server = try address.listen(.{}); defer server.deinit(); // Get the assigned port const port = server.listen_address.getPort(); std.debug.print("Listening on port {d}\n", .{port}); ``` ## Address Types ### Address Union ```zig pub const Address = extern union { any: posix.sockaddr, in: Ip4Address, in6: Ip6Address, un: posix.sockaddr.un, // Unix socket (if supported) }; ``` ### Creating Addresses ```zig // IPv4 from bytes const addr4 = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); // IPv6 from bytes const addr6 = net.Address.initIp6( .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, // ::1 8080, // port 0, // flowinfo 0, // scope_id ); // Unix socket const unix = try net.Address.initUnix("/tmp/my.sock"); ``` ### Parsing Addresses ```zig // Parse IPv4 const addr4 = try net.Address.parseIp4("192.168.1.1", 8080); // Parse IPv6 const addr6 = try net.Address.parseIp6("2001:db8::1", 8080); // Parse either (tries IPv4 first, then IPv6) const addr = try net.Address.parseIp("::1", 8080); // Parse IP:port format // IPv4: "192.168.1.1:8080" // IPv6: "[::1]:8080" (brackets required) const addr_port = try net.Address.parseIpAndPort("[::1]:8080"); // Resolve with interface lookup (for link-local IPv6) const resolved = try net.Address.resolveIp6("fe80::1%eth0", 8080); ``` ### Address Methods ```zig var addr = net.Address.initIp4(.{ 127, 0, 0, 1 }, 8080); // Get/set port (native endian) const port = addr.getPort(); // 8080 addr.setPort(9090); // Get socket length for syscalls const socklen = addr.getOsSockLen(); // Compare addresses if (addr.eql(other_addr)) { // addresses match } // Format for printing var buf: [64]u8 = undefined; var writer: std.Io.Writer = .fixed(&buf); try addr.format(&writer); const formatted = writer.buffered(); // "127.0.0.1:8080" ``` ### Ip4Address ```zig const Ip4Address = extern struct { sa: posix.sockaddr.in, pub fn parse(buf: []const u8, port: u16) !Ip4Address; pub fn init(addr: [4]u8, port: u16) Ip4Address; pub fn getPort(self: Ip4Address) u16; pub fn setPort(self: *Ip4Address, port: u16) void; pub fn format(self: Ip4Address, w: *std.Io.Writer) !void; }; ``` ### Ip6Address ```zig const Ip6Address = extern struct { sa: posix.sockaddr.in6, pub fn parse(buf: []const u8, port: u16) !Ip6Address; pub fn resolve(buf: []const u8, port: u16) !Ip6Address; // handles %interface pub fn init(addr: [16]u8, port: u16, flowinfo: u32, scope_id: u32) Ip6Address; pub fn getPort(self: Ip6Address) u16; pub fn setPort(self: *Ip6Address, port: u16) void; pub fn format(self: Ip6Address, w: *std.Io.Writer) !void; }; ``` ## Stream I/O ### Stream Type ```zig pub const Stream = struct { handle: Handle, // fd on POSIX, SOCKET on Windows pub fn close(s: Stream) void; pub fn reader(stream: Stream, buffer: []u8) Reader; pub fn writer(stream: Stream, buffer: []u8) Writer; }; ``` ### Reading from Stream ```zig const stream = try net.tcpConnectToHost(allocator, "example.com", 80); defer stream.close(); var buf: [4096]u8 = undefined; var reader = stream.reader(&buf); const r = reader.interface(); // Read bytes const data = r.take(100) catch |err| switch (err) { error.EndOfStream => &.{}, error.ReadFailed => return reader.getError().?, }; // Read until delimiter const line = r.takeDelimiter('\n') catch |err| switch (err) { error.EndOfStream => null, error.StreamTooLong => return error.LineTooLong, error.ReadFailed => return reader.getError().?, } orelse return; // Discard bytes _ = try r.discard(.limited(100)); // Stream to writer _ = try r.streamRemaining(&output_writer); ``` ### Writing to Stream ```zig var buf: [1024]u8 = undefined; var writer = stream.writer(&buf); const w = &writer.interface; // Write bytes try w.writeAll("Hello, World!"); // Formatted output try w.print("Count: {d}\n", .{42}); // MUST flush before close try w.flush(); ``` ### Error Handling ```zig var reader = stream.reader(&buf); const r = reader.interface(); const data = r.take(100) catch |err| switch (err) { error.EndOfStream => { // Connection closed normally return; }, error.ReadFailed => { // Get underlying error const read_err = reader.getError().?; switch (read_err) { error.ConnectionResetByPeer => return error.Disconnected, error.SocketNotConnected => return error.Disconnected, else => return read_err, } }, }; ``` ## DNS Resolution ### Get Address List ```zig const std = @import("std"); const net = std.net; pub fn main() !void { var gpa: std.heap.DebugAllocator(.{}) = .init; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Resolve hostname to addresses const list = try net.getAddressList(allocator, "example.com", 80); defer list.deinit(); // Canonical name (if available) if (list.canon_name) |name| { std.debug.print("Canonical name: {s}\n", .{name}); } // Iterate addresses for (list.addrs) |addr| { var buf: [64]u8 = undefined; var w: std.Io.Writer = .fixed(&buf); try addr.format(&w); std.debug.print("Address: {s}\n", .{w.buffered()}); } } ``` ### Connect with Fallback `tcpConnectToHost` automatically tries all resolved addresses: ```zig // Tries each resolved address until one connects const stream = net.tcpConnectToHost(allocator, "example.com", 80) catch |err| switch (err) { error.ConnectionRefused => return error.ServerDown, error.UnknownHostName => return error.DnsError, error.TemporaryNameServerFailure => return error.DnsError, else => return err, }; ``` ## Unix Sockets ### Check Platform Support ```zig if (net.has_unix_sockets) { // Unix sockets available } ``` ### Connect to Unix Socket ```zig const stream = try net.connectUnixSocket("/var/run/app.sock"); defer stream.close(); var buf: [4096]u8 = undefined; var reader = stream.reader(&buf); var writer = stream.writer(&buf); // ... use like TCP ``` ### Unix Socket Server ```zig const address = try net.Address.initUnix("/tmp/my.sock"); var server = try address.listen(.{ .reuse_address = true }); defer server.deinit(); // Remove socket file on cleanup defer std.fs.deleteFileAbsolute("/tmp/my.sock") catch {}; while (true) { const conn = try server.accept(); defer conn.stream.close(); // handle connection... } ``` ## Common Patterns ### Echo Server ```zig const std = @import("std"); const net = std.net; pub fn main() !void { const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 7); // echo port var server = try address.listen(.{ .reuse_address = true }); defer server.deinit(); while (true) { const conn = try server.accept(); defer conn.stream.close(); var buf: [4096]u8 = undefined; var reader = conn.stream.reader(&buf); var writer = conn.stream.writer(&buf); // Echo back everything received _ = reader.interface().streamRemaining(&writer.interface) catch {}; writer.interface.flush() catch {}; } } ``` ### Simple HTTP GET ```zig fn httpGet(allocator: Allocator, host: []const u8, path: []const u8) ![]u8 { const stream = try net.tcpConnectToHost(allocator, host, 80); defer stream.close(); var write_buf: [1024]u8 = undefined; var writer = stream.writer(&write_buf); const w = &writer.interface; try w.print("GET {s} HTTP/1.1\r\n", .{path}); try w.print("Host: {s}\r\n", .{host}); try w.writeAll("Connection: close\r\n\r\n"); try w.flush(); var read_buf: [4096]u8 = undefined; var reader = stream.reader(&read_buf); var response: std.ArrayList(u8) = .empty; defer response.deinit(allocator); while (true) { const chunk = reader.interface().take(4096) catch |err| switch (err) { error.EndOfStream => break, error.ReadFailed => return reader.getError().?, }; try response.appendSlice(allocator, chunk); } return response.toOwnedSlice(allocator); } ``` ### Non-blocking Accept with Timeout ```zig const std = @import("std"); const net = std.net; const posix = std.posix; fn acceptWithTimeout(server: *net.Server, timeout_ms: i32) !?net.Server.Connection { var pfd = [1]posix.pollfd{.{ .fd = server.stream.handle, .events = posix.POLL.IN, .revents = undefined, }}; const ready = try posix.poll(&pfd, timeout_ms); if (ready == 0) return null; // timeout return try server.accept(); } ``` ### Address Validation ```zig fn isValidIpAddress(str: []const u8) bool { _ = net.Address.parseIp(str, 0) catch return false; return true; } fn isValidHostname(hostname: []const u8) bool { return net.isValidHostName(hostname); } ``` ### Dual-Stack Server (IPv4 + IPv6) ```zig // Listen on IPv6 with dual-stack (accepts both IPv4 and IPv6) const address = net.Address.initIp6( .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // :: 8080, 0, 0, ); var server = try address.listen(.{ .reuse_address = true }); defer server.deinit(); // IPv4 clients appear as IPv4-mapped IPv6 addresses (::ffff:x.x.x.x) ``` ### Connection Pool Pattern ```zig const Connection = struct { stream: net.Stream, in_use: bool, }; const Pool = struct { connections: std.ArrayList(Connection), allocator: Allocator, pub fn acquire(self: *Pool, address: net.Address) !net.Stream { // Find free connection for (self.connections.items) |*conn| { if (!conn.in_use) { conn.in_use = true; return conn.stream; } } // Create new connection const stream = try net.tcpConnectToAddress(address); try self.connections.append(self.allocator, .{ .stream = stream, .in_use = true, }); return stream; } pub fn release(self: *Pool, stream: net.Stream) void { for (self.connections.items) |*conn| { if (conn.stream.handle == stream.handle) { conn.in_use = false; return; } } } pub fn deinit(self: *Pool) void { for (self.connections.items) |conn| { conn.stream.close(); } self.connections.deinit(self.allocator); } }; ``` ## Error Types ### Connection Errors ```zig pub const TcpConnectToHostError = GetAddressListError || TcpConnectToAddressError; pub const TcpConnectToAddressError = posix.SocketError || posix.ConnectError; // Includes: ConnectionRefused, NetworkUnreachable, ConnectionTimedOut, etc. ``` ### DNS Errors ```zig pub const GetAddressListError = error{ TemporaryNameServerFailure, NameServerFailure, AddressFamilyNotSupported, UnknownHostName, HostLacksNetworkAddresses, // ... and others }; ``` ### Address Parse Errors ```zig pub const IPv4ParseError = error{ Overflow, InvalidEnd, InvalidCharacter, Incomplete, NonCanonical, // e.g., leading zeros like "01.02.03.04" }; pub const IPv6ParseError = error{ Overflow, InvalidEnd, InvalidCharacter, Incomplete, InvalidIpv4Mapping, }; ```