Modules

Socket

moduleSource
macOSLinuxWindowswasm

#import "Socket";

The system's network sockets (Berkeley sockets on Unix, Winsock on Windows) for TCP, UDP and Unix-domain use.

The module exposes the native calls (socket, bind, listen, connect, send, recv, select, ...) with their native signatures, and a small set of portable helpers around them: socket_init, set_blocking, set_keepalive, close_and_reset, the accept and bind overloads, FD_SET and its siblings, and to_string for addresses. For HTTP, TLS or an event loop use Extensions/Http, Extensions/Tls and Extensions/Io instead.

Calls report failure the way the system does: -1 (or INVALID_SOCKET) and an error code from get_last_socket_error(). Nothing here allocates except the to_string calls, which use context.allocator. A socket is owned by whoever created or accepted it and is released with close_and_reset. Sockets start blocking; set_blocking(s, false) makes recv, send, accept and connect return at once with SOCKET_WOULDBLOCK, and reads and writes may then transfer fewer bytes than requested. Addresses go through sockaddr_in (IPv4) and sockaddr_in6 (IPv6); ports in these records are in network order (htons) while the helper bind overloads take host order. Windows needs socket_init before the first call, and the descriptor-set and control-message helpers differ by platform (CMSG_* exist on Unix only). Under WASI only accept, recv, send and shutdown on a socket the host pre-opened work; the calls that would create or set up a socket return -1 with ENOSYS.

Example
assert(socket_init());

listener := socket(AF_INET, .SOCK_STREAM, .TCP);
defer close_and_reset(*listener);

// Port 0 lets the OS choose; getsockname tells which one it was.
assert(bind(listener, "127.0.0.1", 0) == 0);
assert(listen(listener, 4) == 0);

address: sockaddr_in;
length: socklen_t = size_of(sockaddr_in);
assert(getsockname(listener, cast(*sockaddr) *address, *length) == 0);

client := socket(AF_INET, .SOCK_STREAM, .TCP);
defer close_and_reset(*client);
assert(connect(client, cast(*sockaddr) *address, length) == 0);

server, peer := accept(listener);
defer close_and_reset(*server);

assert(send(client, "ping".data, 4, cast(MSG) 0) == 4);

buffer: [16] u8;
got := recv(server, buffer.data, buffer.count, cast(MSG) 0);
assert(got == 4 && string.{got, buffer.data} == "ping");

Doesn't run in the browser (skipped on wasm).

Procedures#

Socket#

append_address2 overloads
(builder: *String_Builder, address: u32)

Appends an IPv4 address to builder as dotted decimal, such as 127.0.0.1.

builder: String_Builder;
defer free_buffers(*builder);

append_address(*builder, htonl(INADDR_LOOPBACK));
text := builder_to_string(*builder);
defer free(text);

assert(text == "127.0.0.1");

Doesn't run in the browser (skipped on wasm).

to_string2 overloads
(addr: sockaddr_in) -> string

Formats an IPv4 endpoint as address:port, such as 127.0.0.1:8080.

address: sockaddr_in;
address.sin_family = AF_INET;
address.sin_port = htons(8080);

#if OS == .WINDOWS address.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK);
else address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

text := to_string(address);
defer free(text);

assert(text == "127.0.0.1:8080");

// An all-zero record is the unspecified address on port 0.
empty: sockaddr_in;
zero := to_string(empty);
defer free(zero);

assert(zero == "0.0.0.0:0");

Doesn't run in the browser (skipped on wasm).

(s: Socket) -> (Socket, sockaddr_in)

Takes the next pending connection from the listening socket s and returns it with the peer's IPv4 address.

assert(socket_init());

listener := socket(AF_INET, .SOCK_STREAM, .TCP);
defer close_and_reset(*listener);

assert(bind(listener, "127.0.0.1", 0) == 0);
assert(listen(listener, 4) == 0);

local: sockaddr_in;
length: socklen_t = size_of(sockaddr_in);
assert(getsockname(listener, cast(*sockaddr) *local, *length) == 0);

client := socket(AF_INET, .SOCK_STREAM, .TCP);
defer close_and_reset(*client);
assert(connect(client, cast(*sockaddr) *local, length) == 0);

peer, from := accept(listener);
defer close_and_reset(*peer);

assert(peer != INVALID_SOCKET);
assert(from.sin_family == AF_INET);

Doesn't run in the browser (skipped on wasm).

(s: Socket) -> (Socket, sockaddr_in6)

The IPv6 form of accept: takes the next pending connection from s and returns it with the peer's IPv6 address.

assert(socket_init());

listener := socket(AF_INET6, .SOCK_STREAM, .TCP);

// Hosts without IPv6 or without ::1 have nothing to show.
if listener != INVALID_SOCKET {
    defer close_and_reset(*listener);

    if bind(listener, "::1", 0, AF_INET6) == 0 {
        assert(listen(listener, 4) == 0);

        local: sockaddr_in6;
        length: socklen_t = size_of(sockaddr_in6);
        assert(getsockname(listener, cast(*sockaddr) *local, *length) == 0);

        client := socket(AF_INET6, .SOCK_STREAM, .TCP);
        defer close_and_reset(*client);
        assert(connect(client, cast(*sockaddr) *local, length) == 0);

        peer, from := accept_v6(listener);
        defer close_and_reset(*peer);

        assert(peer != INVALID_SOCKET);
        assert(from.sin6_family == AF_INET6);
    }
}

Doesn't run in the browser (skipped on wasm).

bind3 overloads
(s: Socket, address: string, port: u16, family: type_of(sockaddr_in.sin_family) = AF_INET) -> s32

Binds s to a numeric address written as text and a port, and returns 0 on success.

assert(socket_init());

s := socket(AF_INET, .SOCK_STREAM, .TCP);
defer close_and_reset(*s);

assert(bind(s, "127.0.0.1", 0) == 0);
assert(bind(s, "not an address", 0) == -1);
assert(bind(s, "::1", 0) == -1);

Doesn't run in the browser (skipped on wasm).

Winsock control#

(x: u8, y: u8, $T: Type) -> s32

Builds a Winsock control code for ioctlsocket from the group character x, the command number y and the type of the argument T (its size is encoded in the code). FIONBIO is IOW(102, 126, u32).

Socket Windows#

socket_init2 overloads
() -> bool

Starts Winsock 2.2 and returns whether it is ready.

() -> W.OS_Error_Code

The error code of the last failed socket call on this thread: WSAGetLastError on Windows, errno on Unix.

set_blocking2 overloads
(s: Socket, blocking: bool) -> bool

Switches s between blocking and non-blocking mode and returns whether it worked.

set_keepalive2 overloads
(s: Socket, keepalive: bool, idle_seconds_before_keepalive: s32 = 0, keepalive_interval_seconds: s32 = 0, num_keepalive_attempts: s32 = 0) -> bool

Turns TCP keep-alive probes on s on or off and returns whether it worked.

close_and_reset2 overloads
(s: *Socket, loc := #caller_location)

Closes the socket s points to and sets it to INVALID_SOCKET.

FD_SET2 overloads
(fd: Socket, set: *fd_set)

Adds fd to set. A socket already in the set is not added twice, and a full set (fd_array.count sockets, 64 by default) ignores further sockets without an error.

FD_CLR2 overloads
(fd: Socket, set: *fd_set)

Removes fd from set, shifting the later sockets down. A socket that is not in the set is ignored.

Socket fields Windows#

(fields: SCOPE_ID) -> u32

Reads the zone part of an IPv6 scope id from fields.

(fields: *SCOPE_ID, value: u32)

Sets the zone part of an IPv6 scope id in fields, keeping the other bits.

(fields: *SCOPE_ID, value: u32)

Sets the level part of an IPv6 scope id in fields, keeping the other bits.

Socket POSIX#

(fd: s32, set: *fd_set) -> bool

Whether fd can be an index into set: the set is not null and fd lies in 0 ..< size_of(fd_set) * 8.

(fd: s32, set: *fd_set) -> s32

Returns 1 when descriptor fd is in set and 0 when it is not. A null set, a negative descriptor and one past the set's capacity (usually 1024) count as not in the set. After select returns, use it to ask which descriptors are ready.

set: fd_set;

FD_SET(7, *set);
assert(FD_ISSET(7, *set) == 1);
assert(FD_ISSET(8, *set) == 0);
assert(FD_ISSET(100000, *set) == 0);

Doesn't run in the browser (skipped on wasm).

Socket cmsg#

(mhdr: *msghdr) -> *cmsghdr

Returns the first control message of message, or null when its control buffer is smaller than one header.

message: msghdr;
assert(CMSG_FIRSTHDR(*message) == null);

buffer: [64] u8;
message.msg_control = buffer.data;
message.msg_controllen = xx buffer.count;
assert(cast(*u8) CMSG_FIRSTHDR(*message) == buffer.data);

Doesn't run in the browser (only on linux, macos).

(cmsg: *cmsghdr) -> *u8

Returns the payload of control message cmsg: the first byte after its header, rounded up to the platform's alignment.

(size: int) -> int

Rounds size up to the alignment of control message data: 4 bytes on macOS, 8 elsewhere. A negative size, or one too large to round, fails an assertion.

assert(CMSG_ALIGN(0) == 0);
assert(CMSG_ALIGN(1) % 4 == 0);
assert(CMSG_ALIGN(CMSG_ALIGN(5)) == CMSG_ALIGN(5));

Doesn't run in the browser (only on linux, macos).

(size: int) -> u32

The value for a control message's cmsg_len when its payload is size bytes: the aligned header plus size, without padding after the payload. Fails an assertion for a negative size or one that does not fit in a u32.

// A descriptor-passing message carries one s32.
assert(CMSG_LEN(size_of(s32)) == cast(u32) (CMSG_ALIGN(size_of(cmsghdr)) + size_of(s32)));

Doesn't run in the browser (only on linux, macos).

(size: int) -> u32

The buffer space one control message with a size-byte payload needs: the aligned header plus the aligned payload. Use it for msg_controllen and for sizing the buffer. Fails an assertion for a negative size or one that does not fit.

assert(CMSG_SIZE(0) >= cast(u32) size_of(cmsghdr));
assert(CMSG_SIZE(size_of(s32)) >= CMSG_LEN(size_of(s32)));

Doesn't run in the browser (only on linux, macos).

Socket fields macOS#

(fields: tcphdr) -> u32

Reads the header length of a TCP header, in 32-bit words (4 bits) from fields.

(fields: *tcphdr, value: u32)

Sets the header length of a TCP header, in 32-bit words (4 bits) in fields, keeping the other bits.

(fields: tcphdr) -> u32

Reads the 4 reserved bits of a TCP header, which are zero in valid segments from fields.

(fields: *tcphdr, value: u32)

Sets the 4 reserved bits of a TCP header, which are zero in valid segments in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open heuristics_disable flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open heuristics_disable flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open onebyte_proxy flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open onebyte_proxy flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open recv_blackhole flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open recv_blackhole flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open send_blackhole flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open send_blackhole flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open syn_data_acked flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open syn_data_acked flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open syn_data_rcv flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open syn_data_rcv flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open syn_data_sent flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open syn_data_sent flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> bool

Reads the TCP Fast Open syn_loss flag of a connection's info from fields.

(fields: *tcp_connection_info, value: bool)

Sets the TCP Fast Open syn_loss flag of a connection's info in fields, keeping the other bits.

(fields: tcp_connection_info) -> u32

Reads the padding bits at the end of the connection info, which carry no information from fields.

(fields: *tcp_connection_info, value: u32)

Sets the padding bits at the end of the connection info, which carry no information in fields, keeping the other bits.

Socket fields TCP Unix#

(fields: *tcphdr, value: bool)

Sets the ACK flag of a TCP header in fields, keeping the other bits.

(fields: tcphdr) -> u16

Reads the header length of a TCP header, in 32-bit words (4 bits) from fields.

(fields: *tcphdr, value: u16)

Sets the header length of a TCP header, in 32-bit words (4 bits) in fields, keeping the other bits.

(fields: *tcphdr, value: bool)

Sets the FIN flag of a TCP header in fields, keeping the other bits.

(fields: *tcphdr, value: bool)

Sets the PSH flag of a TCP header in fields, keeping the other bits.

(fields: tcphdr) -> u16

Reads the first group of reserved bits of a TCP header, which are zero in valid segments from fields.

(fields: *tcphdr, value: u16)

Sets the first group of reserved bits of a TCP header, which are zero in valid segments in fields, keeping the other bits.

(fields: tcphdr) -> u16

Reads the second group of reserved bits of a TCP header, which are zero in valid segments from fields.

(fields: *tcphdr, value: u16)

Sets the second group of reserved bits of a TCP header, which are zero in valid segments in fields, keeping the other bits.

(fields: *tcphdr, value: bool)

Sets the RST flag of a TCP header in fields, keeping the other bits.

(fields: *tcphdr, value: bool)

Sets the SYN flag of a TCP header in fields, keeping the other bits.

(fields: *tcphdr, value: bool)

Sets the URG flag of a TCP header in fields, keeping the other bits.

(fields: tcphdr) -> u8

Reads the header length of a TCP header, in 32-bit words (4 bits) from fields.

(fields: *tcphdr, value: u8)

Sets the header length of a TCP header, in 32-bit words (4 bits) in fields, keeping the other bits.

(fields: tcphdr) -> u8

Reads the 4 reserved bits of a TCP header, which are zero in valid segments from fields.

(fields: *tcphdr, value: u8)

Sets the 4 reserved bits of a TCP header, which are zero in valid segments in fields, keeping the other bits.

(fields: tcp_info) -> u8

Reads the window scale shift the connection uses for what it receives (4 bits) from fields.

(fields: *tcp_info, value: u8)

Sets the window scale shift the connection uses for what it receives in fields, keeping the other bits.

(fields: tcp_info) -> u8

Reads the window scale shift the connection uses for what it sends (4 bits) from fields.

(fields: *tcp_info, value: u8)

Sets the window scale shift the connection uses for what it sends in fields, keeping the other bits.

Types#

in_addrstructmacOS
:: struct

An IPv4 address: s_addr holds the four octets in network byte order, the first octet at the lowest address.

SHUTenum
:: enum

The directions shutdown can close: RD stops receiving, WR stops sending (the peer then reads end of stream) and RDWR closes both. The SHUT_-prefixed names are the same values.

sockaddrstruct
:: struct

The generic socket address header: a family and 14 bytes of family-specific data. Cast a sockaddr_in or sockaddr_in6 to *sockaddr for calls that take any address.

lingerstruct
:: struct

The SO_LINGER option value: l_onoff non-zero makes close wait up to l_linger seconds for queued data to be sent.

ip_mreqnstruct
:: struct

The IP_ADD_MEMBERSHIP option value that names the interface by index: the multicast group, the local address and the interface number.

:: struct

The ancillary data IP_PKTINFO reports for a received datagram: the interface it arrived on, the local address it was sent to and the header's destination address.

Constants#

:: SOCKET

A socket handle: Winsock's unsigned pointer-sized SOCKET. Unix uses an s32 file descriptor. Compare a handle against INVALID_SOCKET, never against a number.

:: WSAEWOULDBLOCK

The error get_last_socket_error() reports when a non-blocking call has nothing to do yet (WSAEWOULDBLOCK).

:: WSAEWOULDBLOCK

The error get_last_socket_error() reports when a non-blocking connect has started and is still in progress. Winsock uses WSAEWOULDBLOCK for this, so it equals SOCKET_WOULDBLOCK here, unlike the Unix targets.

:: #run IOW(102, 126, u32)

Official Winsock2 FIONBIO encoding; the character literal form is incomplete.