Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Networking

The current networking module provides safe ownership wrappers around blocking IPv4 TCP sockets. It exposes address values, a listener, and a stream; descriptors close through Drop, and all fallible operations return a Result carrying IoError.

Addresses

Ipv4Addr::new accepts four octets. SocketAddrV4::new combines an address and an integer port. The convenience constructors localhost and any use loopback and all-interface addresses respectively.

> stdlib::net::Ipv4Addr
> stdlib::net::SocketAddrV4

main = !->
    loopback: Ipv4Addr = Ipv4Addr::new 127, 0, 0, 1
    address: SocketAddrV4 = SocketAddrV4::new loopback, 8080
    local: SocketAddrV4 = SocketAddrV4::localhost 9000
    wildcard: SocketAddrV4 = SocketAddrV4::any 7000
    address.ip.a.println!
    address.port.println!
    local.ip.d.println!
    wildcard.ip.a.println!

The output is 127, 8080, 1, and 0. Ports are checked to fit the valid range 0..=65535 when a listener or stream is created. A port of 0 asks the operating system to select an available port for a listener.

Connecting

TcpStream::connect returns Result TcpStream, IoError. This deterministic example uses an invalid port to show the library-owned validation error without depending on another process listening on a particular port.

> stdlib::io::IoError
> stdlib::net::Ipv4Addr
> stdlib::net::SocketAddrV4
> stdlib::net::TcpStream

connect_checked: () -> Result TcpStream, IoError
connect_checked = ->
    address = SocketAddrV4::localhost 70000
    TcpStream::connect address

main = ->
    match connect_checked!
        Result::Ok _ => 1
        Result::Err IoError::InvalidAddress =>
            "invalid port".println!
            0
        Result::Err _ => 2

The output is invalid port. For a valid address, connect performs a blocking IPv4 TCP connection and can return an operating-system error when no server is listening. The returned stream owns its descriptor; move it to the component that owns the connection and do not access it after that move.

Listening and accepting

TcpListener::bind creates a blocking listener, local_addr! reports the selected address, and accept! waits for a client and returns a new owned TcpStream. Binding port zero makes the setup reproducible without reserving a fixed port.

> stdlib::io::IoError
> stdlib::net::SocketAddrV4
> stdlib::net::TcpListener

open_listener: () -> Result SocketAddrV4, IoError
open_listener = ->
    listener = TcpListener::bind SocketAddrV4::localhost 0?
    listener.local_addr!

main = ->
    match open_listener!
        Result::Ok address =>
            address.port > 0 .println!
            address.ip.a as I64 .println!
            0
        Result::Err _ => 1

The output is true and 127. A listener created with port zero must be queried with local_addr! before a client can use the selected port. accept! is intentionally omitted from this setup-only example because it blocks until a client connects; the complete round-trip below exercises it with an in-process client.

Reading, writing, and shutdown

TcpStream implements the generic Read and Write traits and also has direct recv, send, shutdown, and shutdown_write methods. This complete round-trip writes a native subslice, half-closes the client write side, then receives the sent bytes on the server side.

> stdlib::io::IoError
> stdlib::io::Write
> stdlib::net::SocketAddrV4
> stdlib::net::TcpListener
> stdlib::net::TcpStream

roundtrip: () -> Result I64, IoError
roundtrip = ->
    listener = TcpListener::bind SocketAddrV4::localhost 0?
    address = listener.local_addr!?
    client = TcpStream::connect address?
    server = listener.accept!?
    bytes: [U8; 5] = [112, 105, 110, 103, 120]
    written = client.write_all (&bytes[..4])?
    client.shutdown_write!?
    mut received: [U8; 4] = [0; 4]
    mut total = 0
    while total < 4
        count = server.recv (&mut received[total..])?
        if count == 0
            failure: Result I64, IoError = Result::Err IoError::InvalidInput
            return failure
        total = total + count
    server.shutdown!?
    written.println!
    total.println!
    received[0] as I64 .println!
    received[3] as I64 .println!
    Result::Ok 0

main = ->
    match roundtrip!
        Result::Ok code => code
        Result::Err _ => 1

On success, the output is 4, 4, 112, and 103, corresponding to ping. write_all sends the four-byte prefix, while each recv fills only the still-empty suffix of the destination. The loop handles arbitrary short reads. This example uses InvalidInput to report an early end of stream because the current IoError has no dedicated unexpected-EOF variant.

shutdown_write! prevents further client writes while permitting reads; it also lets the peer observe end of stream after draining the sent bytes. shutdown! disables both directions. Neither operation releases descriptor ownership: the stream’s Drop still closes the descriptor. Both shutdown calls return Result (), IoError, so their errors are propagated here.

Use write_all (&buffer[..count]) to forward a received prefix and write_all (&buffer[start..end]) to send a middle portion. The slice carries its own length; there is no separate prefix-count argument. send, like the trait method write, performs a single write and may return a short count. write_str writes all string bytes. recv takes a shared stream handle and a mutable buffer; the trait method read requires a mutable stream receiver as well. For a nonempty receive buffer, Ok 0 means the peer has ended its sending direction.

Ownership and errors

Listeners and streams are ordinary owned values. Moving an accepted stream into a worker transfers descriptor responsibility. Arc can share ownership, but it does not serialize complete messages: concurrent writers can interleave their writes. Use a single writer or explicit synchronization when ordering matters. IoError::InvalidAddress covers an invalid port, while IoError::Os code carries an operating-system failure such as a refused connection or failed bind. Complete-write helpers can also return IoError::WriteZero.

Do not read a TCP message by assuming one recv equals one application message. TCP is a byte stream: implement framing, handle partial reads and writes, and define a shutdown path before adding protocol logic.

Current scope and limits

Networking is intentionally small:

  • IPv4 only;
  • blocking calls only;
  • TCP only;
  • no TLS;
  • no async runtime;
  • no socket-timeout abstraction.

Threads can provide limited blocking concurrency, but there are no channels or nonblocking task APIs. A production service also needs protocol framing, resource limits, backpressure, and platform-aware error handling.

For a complete request/response example, continue to Building an HTTP Server. The separate rock_http package implements bounded HTTP parsing and response framing over these safe TCP APIs; it is a local dependency, not a module inside stdlib.