# asyncnet

Dark Mode

- Manual
- Standard library
- Index

Search:
Group by:

This module implements a high-level asynchronous sockets API based on the asynchronous dispatcher defined in the `asyncdispatch` module.

# Asynchronous IO in Nim

Async IO in Nim consists of multiple layers (from highest to lowest):

- `asyncnet` module
- Async await
- `asyncdispatch` module (event loop)
- `selectors` module

Each builds on top of the layers below it. The selectors module is an abstraction for the various system `select()` mechanisms such as epoll or kqueue. If you wish you can use it directly, and some people have done so successfully. But you must be aware that on Windows it only supports `select()`.

The async dispatcher implements the proactor pattern and also has an implementation of IOCP. It implements the proactor pattern for other OS' via the selectors module. Futures are also implemented here, and indeed all the procedures return a future.

The final layer is the async await transformation. This allows you to write asynchronous code in a synchronous style and works similar to C#'s await. The transformation works by converting any async procedures into an iterator.

This is all single threaded, fully non-blocking and does give you a lot of control. In theory you should be able to work with any of these layers interchangeably (as long as you only care about non-Windows platforms).

For most applications using `asyncnet` is the way to go as it builds over all the layers, providing some extra features such as buffering.

SSL ===

SSL can be enabled by compiling with the `-d:ssl` flag.

You must create a new SSL context with the `newContext` function defined in the `net` module. You may then call `wrapSocket` on your socket using the newly created SSL context to get an SSL socket.

# Examples

## Chat server

The following example demonstrates a simple chat server.

    import asyncnet, asyncdispatch

    var clients {.threadvar.}: seq[AsyncSocket]

    proc processClient(client: AsyncSocket) {.async.} =
      while true:
        let line = await client.recvLine()
        if line.len == 0: break
        for c in clients:
          await c.send(line & "\c\L")

    proc serve() {.async.} =
      clients = @[]
      var server = newAsyncSocket()
      server.setSockOpt(OptReuseAddr, true)
      server.bindAddr(Port(12345))
      server.listen()

      while true:
        let client = await server.accept()
        clients.add client

        asyncCheck processClient(client)

    asyncCheck serve()
    runForever()

# Imports

asyncdispatch, nativesockets, net, os, openssl

# Types

    AsyncSocket = ref AsyncSocketDesc

  Source Edit

# Procs

    proc newAsyncSocket(fd: AsyncFD; domain: Domain = AF_INET;
                        sockType: SockType = SOCK_STREAM;
                        protocol: Protocol = IPPROTO_TCP; buffered = true): owned(
        AsyncSocket) {...}{.raises: [OSError], tags: [].}

  Creates a new `AsyncSocket` based on the supplied params.

  The supplied `fd`'s non-blocking state will be enabled implicitly.

  Note: This procedure will NOT register `fd` with the global async dispatcher. You need to do this manually. If you have used `newAsyncNativeSocket` to create `fd` then it's already registered.

  Source Edit

    proc newAsyncSocket(domain: Domain = AF_INET; sockType: SockType = SOCK_STREAM;
                        protocol: Protocol = IPPROTO_TCP; buffered = true): owned(
        AsyncSocket) {...}{.raises: [OSError, Exception], tags: [RootEffect].}

  Creates a new asynchronous socket.

  This procedure will also create a brand new file descriptor for this socket.

  Source Edit

    proc getLocalAddr(socket: AsyncSocket): (string, Port) {...}{.
        raises: [OSError, Exception], tags: [].}

  Get the socket's local address and port number.

  This is high-level interface for getsockname.

  Source Edit

    proc getPeerAddr(socket: AsyncSocket): (string, Port) {...}{.
        raises: [OSError, Exception], tags: [].}

  Get the socket's peer address and port number.

  This is high-level interface for getpeername.

  Source Edit

    proc newAsyncSocket(domain, sockType, protocol: cint; buffered = true): owned(
        AsyncSocket) {...}{.raises: [OSError, Exception], tags: [RootEffect].}

  Creates a new asynchronous socket.

  This procedure will also create a brand new file descriptor for this socket.

  Source Edit

    proc dial(address: string; port: Port; protocol = IPPROTO_TCP; buffered = true): owned(
        Future[AsyncSocket]) {...}{.raises: [Exception, ValueError, FutureError],
                               tags: [RootEffect].}

  Establishes connection to the specified `address`:`port` pair via the specified protocol. The procedure iterates through possible resolutions of the `address` until it succeeds, meaning that it seamlessly works with both IPv4 and IPv6. Returns AsyncSocket ready to send or receive data. Source Edit

    proc connect(socket: AsyncSocket; address: string; port: Port): owned(
        Future[void]) {...}{.raises: [Exception, FutureError], tags: [RootEffect].}

  Connects `socket` to server at `address:port`.

  Returns a `Future` which will complete when the connection succeeds or an error occurs.

  Source Edit

    proc recvInto(socket: AsyncSocket; buf: pointer; size: int;
                  flags = {SafeDisconn}): owned(Future[int]) {...}{.
        raises: [Exception, ValueError, FutureError], tags: [RootEffect].}

  Reads up to `size` bytes from `socket` into `buf`.

  For buffered sockets this function will attempt to read all the requested data. It will read this data in `BufferSize` chunks.

  For unbuffered sockets this function makes no effort to read all the data requested. It will return as much data as the operating system gives it.

  If socket is disconnected during the recv operation then the future may complete with only a part of the requested data.

  If socket is disconnected and no data is available to be read then the future will complete with a value of `0`.

  Source Edit

    proc recv(socket: AsyncSocket; size: int; flags = {SafeDisconn}): owned(
        Future[string]) {...}{.raises: [Exception, ValueError, FutureError],
                          tags: [RootEffect].}

  Reads up to `size` bytes from `socket`.

  For buffered sockets this function will attempt to read all the requested data. It will read this data in `BufferSize` chunks.

  For unbuffered sockets this function makes no effort to read all the data requested. It will return as much data as the operating system gives it.

  If socket is disconnected during the recv operation then the future may complete with only a part of the requested data.

  If socket is disconnected and no data is available to be read then the future will complete with a value of `""`.

  Source Edit

    proc send(socket: AsyncSocket; buf: pointer; size: int; flags = {SafeDisconn}): owned(
        Future[void]) {...}{.raises: [Exception, FutureError], tags: [RootEffect].}

  Sends `size` bytes from `buf` to `socket`. The returned future will complete once all data has been sent. Source Edit

    proc send(socket: AsyncSocket; data: string; flags = {SafeDisconn}): owned(
        Future[void]) {...}{.raises: [Exception, FutureError], tags: [RootEffect].}

  Sends `data` to `socket`. The returned future will complete once all data has been sent. Source Edit

    proc acceptAddr(socket: AsyncSocket; flags = {SafeDisconn}): owned(
        Future[tuple[address: string, client: AsyncSocket]]) {...}{.
        raises: [Exception, ValueError, OSError, FutureError], tags: [RootEffect].}

  Accepts a new connection. Returns a future containing the client socket corresponding to that connection and the remote address of the client. The future will complete when the connection is successfully accepted. Source Edit

    proc accept(socket: AsyncSocket; flags = {SafeDisconn}): owned(
        Future[AsyncSocket]) {...}{.raises: [Exception, ValueError, OSError, FutureError],
                               tags: [RootEffect].}

  Accepts a new connection. Returns a future containing the client socket corresponding to that connection. The future will complete when the connection is successfully accepted. Source Edit

    proc recvLineInto(socket: AsyncSocket; resString: FutureVar[string];
                      flags = {SafeDisconn}; maxLength = MaxLineLength): owned(
        Future[void]) {...}{.raises: [Exception, FutureError], tags: [RootEffect].}

  Reads a line of data from `socket` into `resString`.

  If a full line is read `\r\L` is not added to `line`, however if solely `\r\L` is read then `line` will be set to it.

  If the socket is disconnected, `line` will be set to `""`.

  If the socket is disconnected in the middle of a line (before `\r\L` is read) then line will be set to `""`. The partial line will be lost.

  The `maxLength` parameter determines the maximum amount of characters that can be read. `resString` will be truncated after that.

  Warning: The `Peek` flag is not yet implemented.

  Warning: `recvLineInto` on unbuffered sockets assumes that the protocol uses `\r\L` to delimit a new line.

  Source Edit

    proc recvLine(socket: AsyncSocket; flags = {SafeDisconn};
                  maxLength = MaxLineLength): owned(Future[string]) {...}{.
        raises: [Exception, ValueError, FutureError], tags: [RootEffect].}

  Reads a line of data from `socket`. Returned future will complete once a full line is read or an error occurs.

  If a full line is read `\r\L` is not added to `line`, however if solely `\r\L` is read then `line` will be set to it.

  If the socket is disconnected, `line` will be set to `""`.

  If the socket is disconnected in the middle of a line (before `\r\L` is read) then line will be set to `""`. The partial line will be lost.

  The `maxLength` parameter determines the maximum amount of characters that can be read. The result is truncated after that.

  Warning: The `Peek` flag is not yet implemented.

  Warning: `recvLine` on unbuffered sockets assumes that the protocol uses `\r\L` to delimit a new line.

  Source Edit

    proc listen(socket: AsyncSocket; backlog = SOMAXCONN) {...}{.tags: [ReadIOEffect],
        raises: [OSError].}

  Marks `socket` as accepting connections. `Backlog` specifies the maximum length of the queue of pending connections.

  Raises an OSError error upon failure.

  Source Edit

    proc bindAddr(socket: AsyncSocket; port = Port(0); address = "") {...}{.
        tags: [ReadIOEffect], raises: [ValueError, OSError].}

  Binds `address`:`port` to the socket.

  If `address` is "" then ADDR_ANY will be bound.

  Source Edit

    proc connectUnix(socket: AsyncSocket; path: string): owned(Future[void]) {...}{.
        raises: [], tags: [].}

  Binds Unix socket to `path`. This only works on Unix-style systems: Mac OS X, BSD and Linux Source Edit

    proc bindUnix(socket: AsyncSocket; path: string) {...}{.raises: [], tags: [].}

  Binds Unix socket to `path`. This only works on Unix-style systems: Mac OS X, BSD and Linux Source Edit

    proc close(socket: AsyncSocket) {...}{.raises: [Exception, SslError, OSError],
                                      tags: [RootEffect].}

  Closes the socket. Source Edit

    proc wrapSocket(ctx: SslContext; socket: AsyncSocket) {...}{.
        raises: [SslError, OSError], tags: [].}

  Wraps a socket in an SSL context. This function effectively turns `socket` into an SSL socket.

  Disclaimer: This code is not well tested, may be very unsafe and prone to security vulnerabilities.

  Source Edit

    proc wrapConnectedSocket(ctx: SslContext; socket: AsyncSocket;
                             handshake: SslHandshakeType; hostname: string = "") {...}{.
        raises: [SslError, OSError], tags: [].}

  Wraps a connected socket in an SSL context. This function effectively turns `socket` into an SSL socket. `hostname` should be specified so that the client knows which hostname the server certificate should be validated against.

  This should be called on a connected socket, and will perform an SSL handshake immediately.

  Disclaimer: This code is not well tested, may be very unsafe and prone to security vulnerabilities.

  Source Edit

    proc getPeerCertificates(socket: AsyncSocket): seq[Certificate] {...}{.
        raises: [Exception], tags: [].}

  Returns the certificate chain received by the peer we are connected to through the given socket. The handshake must have been completed and the certificate chain must have been verified successfully or else an empty sequence is returned. The chain is ordered from leaf certificate to root certificate. Source Edit

    proc getSockOpt(socket: AsyncSocket; opt: SOBool; level = SOL_SOCKET): bool {...}{.
        tags: [ReadIOEffect], raises: [OSError].}

  Retrieves option `opt` as a boolean value. Source Edit

    proc setSockOpt(socket: AsyncSocket; opt: SOBool; value: bool;
                    level = SOL_SOCKET) {...}{.tags: [WriteIOEffect], raises: [OSError].}

  Sets option `opt` to a boolean value specified by `value`. Source Edit

    proc isSsl(socket: AsyncSocket): bool {...}{.raises: [], tags: [].}

  Determines whether `socket` is a SSL socket. Source Edit

    proc getFd(socket: AsyncSocket): SocketHandle {...}{.raises: [], tags: [].}

  Returns the socket's file descriptor. Source Edit

    proc isClosed(socket: AsyncSocket): bool {...}{.raises: [], tags: [].}

  Determines whether the socket has been closed. Source Edit

# Exports

SOBool