# net

> Open TCP and UDP sockets, listen for connections, and resolve host names.

This is the Markdown version of https://osl-lang.org/packages/net. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
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](/packages/requests.md), for web servers use [serve](/packages/serve.md), and for URLs use [url](/packages/url.md).

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:

```osl
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()
```

Output:

```text
echo: hello
server saw hello
```

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:

```osl
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")
```

Output:

```text
23
["first line","second line"]
```

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:

```osl
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()
```

Output:

```text
true TimeoutError
```

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:

```osl
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
)
```

Output:

```text
true NetworkError
failed: NetworkError
```

## 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}`:

```osl
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()
```

Output:

```text
ping
true
pong
TimeoutError
```

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[]`:

```osl
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()
```

Output:

```text
4 261
server read 4 bytes
```

## 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:

```osl
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 > 0
```

Output:

```text
["127.0.0.1"]
443
80
true
```

A 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.

**Quiz.** You call `conn.read(1024)` and get 5 bytes, but the other side wrote a 20 byte message. Is that a bug?

- [ ] Yes, `read` should always return the whole message.
- [x] No. TCP is a stream with no message boundaries, so a read returns what has arrived so far.
- [ ] Yes, the connection dropped data.

Answer: Read in a loop, and mark where a message ends with a newline or a length.

### Exercise: Shout over UDP

Send the text `hello` from `sender` to `receiver` and print what the receiver gets, upper-cased.

Starting code:

```osl
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()

log "?"

receiver.close().unwrap()
sender.close().unwrap()
```

It should print:

```text
HELLO
```

Hint 1: `sender.write("hello", receiver.localAddr()).unwrap()` sends a datagram.

Hint 2: `receiver.read(100).unwrap().data.toUpper()`.

A solution:

```osl
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("hello", receiver.localAddr()).unwrap()
log receiver.read(100).unwrap().data.toUpper()

receiver.close().unwrap()
sender.close().unwrap()
```

## API reference

## API reference

Every member of `std:net`, generated from the source of OSL.

### 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.TCPConn`

**Deprecated.** 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) object`

**Deprecated.** 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() string`

localAddr 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() string`

remoteAddr returns the peer's address, such as "93.184.216.34:80".

#### `tCPConn.setTimeout(any seconds) boolean`

**Deprecated.** 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) boolean`

**Deprecated.** 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() string`

localAddr 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() string`

localAddr 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.
