Janet 1.32.1-cc5beda Documentation
(Other Versions: 1.31.0 1.29.1 1.28.0 1.27.0 1.26.0 1.25.1 1.24.0 1.23.0 1.22.0 1.21.0 1.20.0 1.19.0 1.18.1 1.17.1 1.16.1 1.15.0 1.13.1 1.12.2 1.11.1 1.10.1 1.9.1 1.8.1 1.7.0 1.6.0 1.5.1 1.5.0 1.4.0 1.3.1 )

Networking

The internet is a huge part of our daily lives in the 21st century, and the situation is no different for programmers. Janet has basic support for connecting to the internet out of the box with the net/ module.

The net/ module provides a socket-based networking interface that is portable, simple, and general. The net module provides both client and server abstractions for TCP sockets (streams), UDP (datagrams), IPv4, IPv6, DNS, and unix domain sockets on supported platforms. All operations on sockets are non-blocking, so servers can handle multiple clients on a single thread. For more general information on socket programming, see Beej's Guide to Network Programming, which gives a thorough overview in C. There are also example programs in the example directory of the Janet source code which show how to create simple servers and clients.

The net/ module does not provide any protocol abstractions over sockets - this means no TLS and no HTTP out of the box. Such protocols need to be provided in third-party libraries.

Generic Stream Type

Most functions dealing with non-blocking IO in Janet will either accept or create an abstract type of core/stream. Streams are wrappers around file descriptors on Posix platforms, and HANDLEs on Windows. Most streams provide a number of generic methods such as :read, :write, and :chunk for reading and writing bytes, much like a file - there are many parallels to the file/ module, but streams do not block and do not support buffering.

In the net/ module, sockets and servers are both instances of core/stream. However, they have different capabilities - for example, you can only call net/accept on a server, and only call net/read on a socket.

Clients and Servers

In the following sections, we use "client" to mean a program that initiates communication with another program across a network, the "server", which can respond to this communication (or ignore it).

TCP (Stream sockets)

Stream sockets provide an ordered, reliable sequence of bytes across a network. However, in order to send messages a connection must be established. These are the most common kind of socket and the default if a socket type is not specified in most functions.

Client

A TCP client needs to call net/connect to get a socket, and then can read from and write to that socket with net/read and net/write. The third argument to net/connect should be :stream, although it is optional as stream sockets are the default - the other option is :datagram.

(with [conn (net/connect "127.0.0.1" "8000" :stream)]
  (printf "Connected to %q!" conn)
  (:write conn "Echo...")
  (print "Wrote to connection...")
  (def res (:read conn 1024))
  (pp res))

Because streams support a :close method, they can be used as the target of a with macro for scoped resource control.

Server

The net/ module provides a few ways to create a TCP server, but in all cases a few steps are required.

  1. Create a server value with net/listen.
  2. Repeatedly accept connections to the server with net/accept.
  3. Handle and eventually close each connection.

There are several functions that bundle different steps together - net/accept-loop combines steps 2 and 3 into a single call, while net/server can combine steps 1, 2 and 3 into one call. However, the long form way is still very short and is no less expressive.

# Create a server on localhost and listen on port 8000.
(def my-server (net/listen "127.0.0.1" "8000"))

# Handle exactly 10 connections 1-by-1
(repeat 10
 (def connection (net/accept my-server))
 (defer (:close connection)
  (net/write connection "Hello from server!")))

# Close server
(:close my-server)

In many cases, the programmer wants to handle more than 1 connection at a time. The ev/ module can be used to great effect here to handle each new connection in a separate fiber, so that the accepting loop can accept the next connection as quickly as possible.

# Create a server on localhost and listen on port 8000.
(def my-server (net/listen "127.0.0.1" "8000"))

(defn handler 
  "Handle a connection in a separate fiber."
  [connection]
  (defer (:close connection)
    (def msg (ev/read connection 100))
    (ev/sleep 10)
    (net/write connection (string "Echo: " msg))))

# Handle connections as soon as they arrive
(forever
 (def connection (net/accept my-server))
 (ev/call handler connection))

There are many possible variations, but all servers will follow this same basic structure.

UDP (Datagram sockets)

Datagram sockets provide an out-of-order, fast way to send datagrams across a network. Datagram sockets also provide the benefit of not needing a connection - one can send packets to an address without checking if that address is listening.

Client

A UDP client will generally create a fake connection to a server with net/connect, where the third argument is :datagram. The connection is "fake" because datagrams are connection-less, but we can use this connection to encapsulate the remote address of the server. The programmer can then use net/write and net/read to send and receive datagrams from the server (or :write and :read).

(def conn (net/connect "127.0.0.1" "8009" :datagram))
(:write conn (string/format "%q" (os/cryptorand 16)))
(def x (:read conn 1024))
(pp x)

Server

A Datagram server is a bit simpler than a stream server since there is no need to manage connections. Instead, the program listens for packets with net/recv-from and sends packets with net/send-to.

(def server (net/listen "127.0.0.1" "8009" :datagram))
(while true
  (def buf @"")
  (def who (:recv-from server 1024 buf))
  (printf "got %q from %v, echoing!" buf who)
  (:send-to server who buf))

DNS

Janet uses the Posix C function getaddrinfo to look up IP addresses from domain names. This is currently a blocking operation which can be bad in highly concurrent applications. However, this also affords us access to various features supported by the operating system like easy and cross platform IPv4, IPv6, and unix domain socket support.

Functions that do DNS will usually take host and port arguments, where host is a host address and port further specifies the address on the same machine. In IPv4 and IPv6, host should be an IP address or domain name, and port should be a 16-bit number.

Unix domain sockets

Supporting platforms allow unix domain sockets by accepting :unix as the host, and a file name as the port. On linux, abstract unix domain sockets are supported if the port argument begins with "@".

# Connect to a unix domain socket
(net/connect :unix "/tmp/my.socket")

# Connect to an abstract unix domain socket (linux only)
(net/connect :unix "@my.socket")

Relationship to the ev/ module.

All non-blocking operations in the net/ module avoid blocking by yielding to the event loop. As such, functions in the ev/ module for manipulating fibers, such as ev/cancel will also work on functions in the net/ module. Similarly, a function that blocks the event loop will prevent any networking code from progressing.