This module implements a high-level asynchronous sockets API based on the asynchronous dispatcher defined in the asyncdispatch
module.
Async IO in Nim consists of multiple layers (from highest to lowest):
asyncnet
moduleasyncdispatch
module (event loop)selectors
moduleEach 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 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.
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()
AsyncSocket = ref AsyncSocketDesc
proc newAsyncSocket(fd: AsyncFD; domain: Domain = AF_INET; sockType: SockType = SOCK_STREAM; protocol: Protocol = IPPROTO_TCP; buffered = true): 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.
proc newAsyncSocket(domain: Domain = AF_INET; sockType: SockType = SOCK_STREAM; protocol: Protocol = IPPROTO_TCP; buffered = true): AsyncSocket {...}{. raises: [OSError, Exception], tags: [RootEffect].}
Creates a new asynchronous socket.
This procedure will also create a brand new file descriptor for this socket.
proc newAsyncSocket(domain, sockType, protocol: cint; buffered = true): AsyncSocket {...}{. raises: [OSError, Exception], tags: [RootEffect].}
Creates a new asynchronous socket.
This procedure will also create a brand new file descriptor for this socket.
proc dial(address: string; port: Port; protocol = IPPROTO_TCP; buffered = true): Future[ AsyncSocket] {...}{.raises: [FutureError], tags: [RootEffect].}
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. proc connect(socket: AsyncSocket; address: string; port: Port): Future[void] {...}{. raises: [FutureError], tags: [RootEffect].}
Connects socket
to server at address:port
.
Returns a Future
which will complete when the connection succeeds or an error occurs.
proc recvInto(socket: AsyncSocket; buf: pointer; size: int; flags = {SafeDisconn}): Future[ int] {...}{.raises: [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
.
proc recv(socket: AsyncSocket; size: int; flags = {SafeDisconn}): Future[string] {...}{. raises: [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 ""
.
proc send(socket: AsyncSocket; buf: pointer; size: int; flags = {SafeDisconn}): Future[ void] {...}{.raises: [FutureError], tags: [RootEffect].}
size
bytes from buf
to socket
. The returned future will complete once all data has been sent. proc send(socket: AsyncSocket; data: string; flags = {SafeDisconn}): Future[void] {...}{. raises: [FutureError], tags: [RootEffect].}
data
to socket
. The returned future will complete once all data has been sent. proc acceptAddr(socket: AsyncSocket; flags = {SafeDisconn}): Future[ tuple[address: string, client: AsyncSocket]] {...}{. raises: [Exception, ValueError, OSError, FutureError], tags: [RootEffect].}
proc accept(socket: AsyncSocket; flags = {SafeDisconn}): Future[AsyncSocket] {...}{. raises: [Exception, ValueError, OSError, FutureError], tags: [RootEffect].}
proc recvLineInto(socket: AsyncSocket; resString: FutureVar[string]; flags = {SafeDisconn}; maxLength = MaxLineLength): Future[void] {...}{. raises: [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.
proc recvLine(socket: AsyncSocket; flags = {SafeDisconn}; maxLength = MaxLineLength): Future[ string] {...}{.raises: [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.
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 EOS error upon failure.
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.
proc close(socket: AsyncSocket) {...}{.raises: [SslError, OSError, Exception], tags: [RootEffect].}
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.
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.
proc getSockOpt(socket: AsyncSocket; opt: SOBool; level = SOL_SOCKET): bool {...}{. tags: [ReadIOEffect], raises: [OSError].}
opt
as a boolean value. proc setSockOpt(socket: AsyncSocket; opt: SOBool; value: bool; level = SOL_SOCKET) {...}{. tags: [WriteIOEffect], raises: [OSError].}
opt
to a boolean value specified by value
. proc isSsl(socket: AsyncSocket): bool {...}{.raises: [], tags: [].}
socket
is a SSL socket. proc getFd(socket: AsyncSocket): SocketHandle {...}{.raises: [], tags: [].}
proc isClosed(socket: AsyncSocket): bool {...}{.raises: [], tags: [].}
© 2006–2018 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/asyncnet.html