net
Open TCP and UDP sockets, listen for connections, and resolve host names.
import "std:net"std:net is the low-level network layer: TCP connections, TCP listeners, UDP sockets and name lookups. Reach for it when you speak a protocol that isn't HTTP, such as a game server, a line-based text protocol or a custom binary format. For web requests use requests, for web servers use serve, and for URLs use url.
Every example runs both ends in one program over the loopback address. Asking for port 0 lets the system pick a free one, and localAddr() tells you which.
A TCP echo#
net.listen("tcp", address) returns a listener. listener.accept() waits for one connection, so serve in an async function and connect from the main thread. net.connect opens the client side:
import "std:net"
auto listener = net.listen("tcp", "127.0.0.1:0").unwrap()
string address = listener.localAddr()
async serveOne() string (
auto conn = listener.accept().unwrap()
string data = conn.read(1024).unwrap()
conn.write("echo: " ++ data).unwrap()
conn.close().unwrap()
return data
)
auto server = serveOne()
auto client = net.connect("tcp", address, {timeout: 2000}).unwrap()
client.write("hello").unwrap()
log client.read(1024).unwrap()
client.close().unwrap()
log "server saw" server.wait()
listener.close().unwrap()Change "hello" to something longer than 1024 characters, or shorter than one, and see how read behaves.
read(maxBytes) waits for data and returns up to that many bytes as text. It returns whatever has arrived, which may be less than the other side wrote in one go, because TCP is a stream and has no message boundaries. write sends all of its data or fails.
Reading until the end#
A peer that closes its side ends the stream, and the next read fails with a StateError. A loop that collects data until then reads a whole reply:
import "std:net"
auto listener = net.listen("tcp", "127.0.0.1:0").unwrap()
async serveOne() int (
auto conn = listener.accept().unwrap()
conn.write("first line\nsecond line\n").unwrap()
conn.close().unwrap()
return 1
)
auto server = serveOne()
auto client = net.connect("tcp", listener.localAddr()).unwrap()
string text = ""
while true (
auto part = client.read(8)
if part.isErr() break
text = text ++ part.unwrap()
)
client.close().unwrap()
server.wait()
listener.close().unwrap()
log text.len
log text.trim().split("\n")A protocol with several messages on one connection needs its own framing: a newline, a length prefix or a fixed size. Collect bytes and cut them at the boundaries.
Timeouts#
connect, read and write take a timeout option in milliseconds. A read that times out fails with a TimeoutError, and the connection stays usable:
import "std:net"
auto listener = net.listen("tcp", "127.0.0.1:0").unwrap()
auto client = net.connect("tcp", listener.localAddr()).unwrap()
auto quiet = client.read(10, {timeout: 100})
log quiet.isErr() quiet.unwrapErr().kind
client.close().unwrap()
listener.close().unwrap()Without a timeout, a read waits for as long as it takes. Closing a connection from another thread wakes a waiting read with an error, which is how you stop a reader.
Connection errors#
net.connect returns an error with a kind: NetworkError when the connection is refused, NotFoundError for a host name that doesn't exist and TimeoutError when it takes too long:
import "std:net"
auto refused = net.connect("tcp", "127.0.0.1:1", {timeout: 1000})
log refused.isErr() refused.unwrapErr().kind
switch net.connect("tcp", "127.0.0.1:1") (
case ok(conn)
log "connected to" conn.remoteAddr()
case err(e)
log "failed:" e.kind
)UDP#
UDP sends separate datagrams with no connection. Bind a socket with net.listenUDP, send with write(data, address) and receive with read, which gives {data, addr}:
import "std:net"
auto receiver = net.listenUDP("udp", "127.0.0.1:0").unwrap()
auto sender = net.listenUDP("udp", "127.0.0.1:0").unwrap()
sender.write("ping", receiver.localAddr()).unwrap()
auto packet = receiver.read(100).unwrap()
log packet.data
log packet.addr == sender.localAddr()
receiver.write("pong", packet.addr).unwrap()
log sender.read(100).unwrap().data
log receiver.read(100, {timeout: 100}).unwrapErr().kind
receiver.close().unwrap()
sender.close().unwrap()Datagrams can be lost, duplicated or arrive out of order, and a datagram bigger than maxBytes is cut. Use UDP when losing one doesn't matter, and TCP when it does.
Binary data#
read and write work on text. For raw bytes, readBytes(maxBytes) returns byte[] and write also accepts byte[]:
import "std:net"
auto listener = net.listen("tcp", "127.0.0.1:0").unwrap()
async serveOne() int (
auto conn = listener.accept().unwrap()
byte[] data = conn.readBytes(16).unwrap()
conn.write(data).unwrap()
conn.close().unwrap()
return data.len
)
auto server = serveOne()
auto client = net.connect("tcp", listener.localAddr()).unwrap()
byte[] payload = [1, 2, 3, 255]
client.write(payload).unwrap()
byte[] back = client.readBytes(16).unwrap()
int total = 0
for b in back (
total = total + b
)
log back.len total
log "server read" server.wait() "bytes"
client.close().unwrap()
listener.close().unwrap()Looking up names#
net.lookupIP, net.lookupHost, net.lookupPort and net.addressInfo ask the system's resolver. Names that are already addresses resolve to themselves, so these examples need no network:
import "std:net"
log net.lookupIP("127.0.0.1").unwrap()
log net.lookupPort("https", "tcp").unwrap()
log net.lookupPort("http", "tcp").unwrap()
log net.lookupHost("localhost").unwrap().len > 0A name that doesn't resolve fails with a NotFoundError. A lookup waits on a real resolver, so expect it to take time and handle the failure.
You call conn.read(1024) and get 5 bytes, but the other side wrote a 20 byte message. Is that a bug?
Shout over UDP
Send the text hello from sender to receiver and print what the receiver gets, upper-cased.
HELLO
API reference#
25 members, generated from the std:net source of OSL 9.3.0.
Functions
net.addressInfo(string hostname) result<{hostname: string, hosts: string[], ips: string[]}, error>addressInfo resolves hostname and returns {hostname, hosts, ips}: the name asked for, its addresses as net.lookupHost returns them, and its IP addresses as net.lookupIP returns them. Fails like net.lookupHost.
net.connect(string network, string address, object? options) result<*net.TCPConn, error>connect opens a stream connection to address, such as "example.com:80" or "127.0.0.1:8080" (a socket path for "unix"). network is "tcp", "tcp4", "tcp6" or "unix". Options: timeout (milliseconds to wait for the connection, default 5000). Fails with NetworkError when the connection is refused or unreachable, NotFoundError when the host doesn't resolve, TimeoutError when it takes too long and ValueError for a malformed address or unknown network.
net.dial(any network, any address) *net.TCPConnDeprecated. Deprecated: use net.connect(network, address) instead, which reports why the connection failed.
dial opens a stream connection, or returns null when it fails.
net.getAddressInfo(any hostname) objectDeprecated. Deprecated: use net.addressInfo(hostname) instead, which fails with a typed error.
getAddressInfo resolves hostname into {hostname, hosts, ips}, with empty arrays when it fails.
net.listen(string network, string address) result<*net.Listener, error>listen starts accepting stream connections on address, such as "127.0.0.1:0" (port 0 picks a free port; read it back with localAddr()). network is "tcp", "tcp4", "tcp6" or "unix". Fails with NetworkError when the address is in use or can't be bound and ValueError for a malformed address or unknown network.
net.listenUDP(string network, string address) result<*net.UDPConn, error>listenUDP binds a UDP socket to address, such as "127.0.0.1:0". network is "udp", "udp4" or "udp6". Fails with NetworkError when the address can't be bound and ValueError for a malformed address or unknown network.
net.lookupHost(string hostname) result<string[], error>lookupHost resolves hostname to its addresses, as strings such as "93.184.216.34". Fails with NotFoundError when the host doesn't exist, TimeoutError when the resolver doesn't answer and NetworkError otherwise.
net.lookupIP(string hostname) result<string[], error>lookupIP resolves hostname to its IPv4 and IPv6 addresses. Fails like net.lookupHost.
net.lookupPort(string service, string network) result<int, error>lookupPort returns the port number of a service name such as "https" on network ("tcp" or "udp"); a numeric service is returned as a number. Fails with NotFoundError for an unknown service.
*net.TCPConn
tCPConn.close() result<unit, error>close closes the connection; a read waiting in another thread then fails with StateError. Closing twice is a no-op.
tCPConn.localAddr() stringlocalAddr returns this end's address, such as "127.0.0.1:51514".
tCPConn.read(int maxBytes, object? options) result<string, error>read waits for data and returns up to maxBytes bytes of it as a string. Once the peer has closed the connection and everything it sent has been read, it fails with StateError, so a read loop ends with string chunk = conn.read(4096) catch (err) ( break ). Options: timeout (milliseconds to wait for data; by default it waits indefinitely). Fails with TimeoutError when the timeout passes and NetworkError when the connection breaks. Throws ValueError when maxBytes is less than 1 and StateError on a connection closed with close(). Reads are capped at 16 MiB.
tCPConn.readBytes(int maxBytes, object? options) result<byte[], error>readBytes is read returning byte[]. It takes the same options and fails the same ways, including StateError at the end of the stream.
tCPConn.remoteAddr() stringremoteAddr returns the peer's address, such as "93.184.216.34:80".
tCPConn.setTimeout(any seconds) booleanDeprecated. Deprecated: pass {timeout: milliseconds} to read or write instead.
setTimeout sets a deadline seconds from now for every later read and write, and reports whether it worked.
tCPConn.write(any data, object? options) result<unit, error>write sends data, a string or byte[], in full. Options: timeout (milliseconds; by default it waits indefinitely). Fails with TimeoutError when the timeout passes and NetworkError when the connection breaks. Throws TypeError for other data types and StateError on a closed connection.
tCPConn.writeBytes(byte[] data) booleanDeprecated. Deprecated: use conn.write(data) instead, which accepts byte[] and fails with a typed error.
writeBytes sends bytes and reports whether it worked.
*net.Listener
listener.accept() result<*net.TCPConn, error>accept waits for the next incoming connection and returns it. Fails with StateError when the listener is closed while waiting, which is how another thread stops an accept loop. Throws StateError when the listener was already closed.
listener.close() result<unit, error>close stops accepting connections and wakes any accept() call waiting on this listener. Closing twice is a no-op.
listener.localAddr() stringlocalAddr returns the address the listener is bound to, such as "127.0.0.1:41234".
*net.UDPConn
uDPConn.close() result<unit, error>close closes the socket; a read waiting in another thread then fails with StateError. Closing twice is a no-op.
uDPConn.localAddr() stringlocalAddr returns the address the socket is bound to, such as "127.0.0.1:41234".
uDPConn.read(int maxBytes, object? options) result<{addr: string, data: string}, error>read waits for the next datagram and returns {data, addr}: its payload as a string, truncated to maxBytes, and the sender's address. Options: timeout (milliseconds; by default it waits indefinitely). Fails with TimeoutError when the timeout passes and StateError when the socket is closed while waiting. Throws ValueError when maxBytes is less than 1.
uDPConn.readBytes(int maxBytes, object? options) result<{addr: string, data: byte[]}, error>readBytes is read with the payload as byte[]: it returns {data, addr}.
uDPConn.write(any data, string address) result<unit, error>write sends data, a string or byte[], as one datagram to address, such as "127.0.0.1:9000". Fails with NotFoundError when the host doesn't resolve, ValueError for a malformed address and NetworkError when sending fails. Throws TypeError for other data types and StateError on a closed socket.