std/udp
import std/udp to use it. Source: src/std/udp.iyi at v0.18.0.
A native UDP socket with numeric IP resolution (IPv4 dotted-decimal, IPv6, and localhost).
Address resolution: getaddrinfo is the standard route in libc, but on Linux it pulls in libc.so, dynamic NSS modules (libnss_dns, libnss_files), and resolver state, which destroys the zero-undefined-symbol dependency floor (SPEC.md III.9). By parsing numeric IPv4 dotted-decimal and IPv6 strings (and mapping "localhost" to 127.0.0.1) in pure iyi arithmetic without libc, Linux programs retain raw syscall purity (zero undefined symbols) and Darwin programs only link standard socket symbols provided by libSystem.
Concurrency: every receive is MSG_DONTWAIT, and when nothing is queued the calling task parks on the descriptor the way std/socket does (SPEC.md III.4.2) - under the runtime on the poller, without one on a poll. So receive_from answers T | Cancelled: ! passes a cancelled task through, .or_panic refuses it at the top level, and a socket closed by another task under a parked receive answers it too. The ? variants answer nil when nothing is queued right now, and never park. There are no kernel timeouts and no non-blocking flag: a wait that must end is poll_read(ms) followed by receive_from?.
Bytesalias
pub alias Bytes = Std::Slice::BytesDatagramstruct
An incoming datagram with payload bytes and remote peer origin.
pub struct Datagram
def [](i : Int32) : Bytes | String | Int32
def data : Bytes
def host : String
def initialize(data : Bytes, host : String, port : Int32)
def port : Int32
# Decodes payload bytes as a String.
def text : String
def to_s : String
def to_tuple : Tuple(Bytes, String, Int32)
endIPv4Addressalias
The address types, their parsers and the sockaddr_in/sockaddr_in6 writers are std/socket's; this module had copies, and the copies had drifted: its IPv6 parser read no dotted tail and its printer compressed only :: and ::1.
pub alias IPv4Address = Std::Socket::IPv4AddressIPv6Addressalias
pub alias IPv6Address = Std::Socket::IPv6AddressUdpSocketclass
pub class UdpSocket
lib LibWs2_32
struct PollFd
end
# Both are kept on the caller's frame rather than the heap: the kernel
# reads and writes them after a post, while the fiber is parked, and a
# heap chunk whose only reference is a local the posting call has
# finished with is a chunk the collector can hand to somebody else
# first.
struct WSABuf
end
end
alias SockLen = Int32
def self.__is_eagain(res : Int64) : ::Bool
# Never: Winsock has no EINTR. A call either answers, or answers
# WSAEWOULDBLOCK, or is posted and completed.
def self.__is_eintr(res : Int64) : ::Bool
# A datagram that did not fit is an error here where it is a silent
# truncation everywhere else: WSAEMSGSIZE (10040), with the buffer
# already filled, which is why the caller answers its capacity.
def self.__is_msgsize(res : Int64) : ::Bool
# Never: the report is turned off in `__sys_socket`.
def self.__is_refused(res : Int64) : ::Bool
# WSAEINVAL (10022): Winsock's answer to a receive, or an address asked,
# on a socket that has none yet - one `client` made and nothing sent
# from. POSIX has nothing queued there and port 0; Windows panicked.
def self.__is_unbound(res : Int64) : ::Bool
def self.__sys_bind(fd : Int32, addr : Pointer(UInt8), len : Int32) : Int32
# A datagram socket's `connect` only records the peer, so there is no
# handshake to wait for and nothing to post.
def self.__sys_connect(fd : Int32, addr : Pointer(UInt8), len : Int32) : Int32
# Winsock's error number, negated, which is the shape the POSIX arms
# answer the kernel's errno in; the two predicates at the bottom are the
# only things that read which number it was.
def self.__sys_error : Int64
def self.__sys_get_buffer_size(fd : Int32, level : Int32, optname : Int32) : Int32
def self.__sys_getsockname(fd : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32
def self.__sys_handle(fd : Int32) : UInt64
# The wait a cancelled post still owes the kernel. `CancelIoEx` is a
# request, and on a socket tied to a completion port the operation is not
# over when it returns: measured here, the status stays STATUS_PENDING
# and the descriptor, the caller's address and the OVERLAPPED are the
# kernel's until the completion packet is taken *off the port* — the
# poller's `GetQueuedCompletionStatus` is what writes them. The same
# measurement on an unassociated socket had the status written by
# `CancelIoEx` itself, which is why this is not obvious. So
# `WSAGetOverlappedResult`'s own wait flag cannot be the wait either: it
# waits on the OVERLAPPED's event, the scheduler's OVERLAPPED has none,
# and the socket handle it falls back to is never signalled for a socket
# on a port — it hung forever on the first cancelled receive tried.
#
# What ends the operation is the poller, so the frame parks once more and
# lets it. `IyiScheduler.cancel` has already unregistered this fiber,
# which is what would have left the packet to be dropped; parking puts it
# back, and the wake means the dequeue has happened. Asked first, because
# a completion the poller delivered *before* the cancel is already over
# and no second packet is coming — parking on one would never wake.
def self.__sys_overlapped_await(s : UInt64, ov : Pointer(UInt8)) : Int64
# What the posted operation ended with, read off the operation itself:
# the completion port's byte count cannot tell a failure from a datagram
# of no bytes, which is a legal datagram. Nothing waits here, which is
# what the 0 says.
def self.__sys_overlapped_result(s : UInt64, ov : Pointer(UInt8)) : Int64
# One descriptor, one event, and a bound on the wait, which is what the
# epoll and kqueue arms do with theirs.
def self.__sys_poll_read(fd : Int32, timeout_ms : Int32) : ::Bool
# Winsock's address length is an `int*` where POSIX has a `socklen_t*`:
# the same width and the same bytes, so the callers keep one signature.
def self.__sys_recvfrom(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int64
# The receive that waits, which on Windows is a posted one: see the note
# in `receive_into` for why the readable-wait cannot be used here. The
# buffer descriptor and the flags stay on this frame for the whole
# operation, and the address and its length are the caller's, which
# `receive_from` reads after this returns.
def self.__sys_recvfrom_posted(fd : Int32, buf : Pointer(UInt8), capacity : Int32, addr : Pointer(UInt8), addrlen : Pointer(UInt32)) : Int32 | Cancelled
def self.__sys_send(fd : Int32, buf : Pointer(UInt8), len : UInt64) : Int64
def self.__sys_sendto(fd : Int32, buf : Pointer(UInt8), len : UInt64, flags : Int32, addr : Pointer(UInt8), addrlen : Int32) : Int64
def self.__sys_set_buffer_size(fd : Int32, level : Int32, optname : Int32, size : Int32) : Int32
def self.__sys_socket(domain : Int32 = 2) : Int32
def self.__unbound_now? : ::Bool
# Binds a UDP socket to the specified host and port.
# Automatically detects whether host is an IPv4 or IPv6 address.
#
# Without SO_REUSEADDR, on every platform. It waives TIME_WAIT for a
# listener, and a datagram port never has one; what it does to a
# datagram socket on Linux is let a second socket bind the same address
# and port while the first holds it, and unicast then reaches only one
# of them. The POSIX arms asked for it and the win32 arm did not, so a
# second bind of a held port failed on Windows, measured, and went
# through on Linux [INFERENCE: not run here].
def self.bind(host : String, port : Int32) : UdpSocket
# Binds a UDP socket to 0.0.0.0 and the specified port.
def self.bind(port : Int32) : UdpSocket
# Binds an IPv6 UDP socket to [::] and the specified port.
def self.bind6(port : Int32) : UdpSocket
# `bind`, with a refused bind answered rather than raised: the port
# held, reserved (Windows' excluded ranges answer "access denied"), or
# the address not this machine's - so a program can pick another. An
# error's `message` is the sentence `bind` raises.
def self.bind?(host : String, port : Int32) : UdpSocket | SocketError
# Creates an unbound UDP client socket.
def self.client(family : Int32 = 2) : UdpSocket
# Creates an unbound IPv6 UDP client socket.
def self.client_v6 : UdpSocket
# Closes the socket file descriptor.
def close : Nil
def closed? : ::Bool
# Connects the UDP socket to a remote host and port (sets default peer).
def connect(host : String, port : Int32) : Nil
def family : Int32
def fd : Int32
def self.format_address(addr : Pointer(UInt8)) : Tuple(String, Int32)
def initialize(fd : Int32 = -1, family : Int32 = 2)
# Reads back the locally bound IP address.
def local_address : String
# Reads back the locally bound port assigned by the OS.
def local_port : Int32
def self.make_sockaddr(host : String, port : Int32) : Tuple(Pointer(UInt8), SockLen, Int32)
# Creates an unbound UDP socket.
def self.open(family : Int32 = 2) : UdpSocket
# Polls whether the socket is readable within timeout_ms milliseconds.
# Returns true if a datagram is ready to be read, false on timeout.
def poll_read(timeout_ms : Int32 = 0) : ::Bool
# Receives payload bytes from the connected peer, parking until some arrive.
def receive(max_bytes : Int32 = 65536) : Bytes | Cancelled
# Payload bytes queued now from the connected peer, or nil.
def receive?(max_bytes : Int32 = 65536) : Bytes | ::Nil
# Returns the current receive buffer size (SO_RCVBUF).
def receive_buffer_size : Int32
# Sets the receive buffer size (SO_RCVBUF).
def receive_buffer_size=(size : Int32) : Nil
# Receives a Datagram struct containing data, host, and port.
def receive_datagram(max_bytes : Int32 = 65536) : Datagram | Cancelled
# A Datagram of what is queued now, or nil.
def receive_datagram?(max_bytes : Int32 = 65536) : Datagram | ::Nil
# Receives a datagram into a newly allocated Bytes slice up to max_bytes.
# Answers {payload, peer_host, peer_port}, parking until one arrives.
def receive_from(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | Cancelled
# Receives a datagram into a caller-supplied Bytes buffer.
# Answers {subslice_of_received_bytes, peer_host, peer_port}.
def receive_from(buffer : Bytes) : Tuple(Bytes, String, Int32) | Cancelled
# What is queued now, or nil. Never parks.
def receive_from?(max_bytes : Int32 = 65536) : Tuple(Bytes, String, Int32) | ::Nil
# Into a buffer: what is queued now, or nil. Never parks.
def receive_from?(buffer : Bytes) : Tuple(Bytes, String, Int32) | ::Nil
# Why a call failed: `std/socket`'s sentence for the platform's
# number (`SocketError#reason`), and the refusals only a datagram meets
# named as well. Each refusal was a fixed sentence that dropped the
# number, so a broadcast, a datagram past 65,507 bytes, a port in use
# and an IPv6 address from an IPv4 socket all said only "cannot send to
# H:P" or "cannot bind UDP socket to H:P" - measured on Windows.
def self.refusal(res : Int64, sending : ::Bool) : String
# Sends bytes to the connected peer.
def send(bytes : Bytes) : Int32
# Sends string to the connected peer.
def send(str : String) : Int32
# Returns the current send buffer size (SO_SNDBUF).
def send_buffer_size : Int32
# Sets the send buffer size (SO_SNDBUF).
def send_buffer_size=(size : Int32) : Nil
# Sends a raw buffer to a destination host and port.
def send_to(buffer : Pointer(UInt8), count : Int32, host : String, port : Int32) : Int32
# Sends bytes to a destination host and port.
def send_to(bytes : Bytes, host : String, port : Int32) : Int32
# Sends string contents as a datagram to a destination host and port.
def send_to(str : String, host : String, port : Int32) : Int32
def to_unsafe : Int32
# Creates an unbound UDP socket.
def self.unbound(family : Int32 = 2) : UdpSocket
end
Datagram sockets: UDP networking for iyi