Janet 1.17.1-e1c4fc2 Documentation
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 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 example directory of the Janet source
code which show how to create simple servers and clients.
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
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
reading and writing bytes, much like a file - there are many parallels to the
but streams do not block and do not support buffering.
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
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.
A TCP client needs to call
net/connect to get a socket, and then can
read from and write to that socket with
The third argument to
net/connect should be
:stream, although it is optional
as stream sockets are the default - the other option is
(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
for scoped resource control.
net/ module provides a few ways to create a TCP server, but
in all cases a few steps are required.
- Create a server value with
- Repeatedly accept connections to the server with
- Handle and eventually close each connection.
There are several functions that bundle different steps together -
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
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.
A UDP client will generally create a fake connection to a server with
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/read to send and receive datagrams from the server (or
(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)
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
(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))
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
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
will also work on functions in the
net/ module. Similarly, a function that blocks the
event loop will prevent any networking code from progressing.