Skip to content

Instantly share code, notes, and snippets.

@haolian9
Last active February 21, 2022 13:05
Show Gist options
  • Save haolian9/4adb17e64e2db759ca874c2575b731ec to your computer and use it in GitHub Desktop.
Save haolian9/4adb17e64e2db759ca874c2575b731ec to your computer and use it in GitHub Desktop.
a simple async chat server in nim
import std/[asyncdispatch, asyncnet, nativesockets, asyncfutures]
import std/[tables, deques]
type Nursery = object
tasks: seq[Future[void]]
proc startSoon(self: ref Nursery, fut: Future[void]) =
asyncCheck fut
self.tasks.add fut
template with(self: ref Nursery, body: untyped): untyped =
try:
body
finally:
try:
await all self.tasks
except:
# TODO@haoliang implement cancellation
raise newException(Defect, "my battery is low, and it's getting dark.")
type
Addr = (string, Port)
Msg = tuple[text: string, source: Addr]
const
queueCap = 1
clientCap = 2
var
clients {.threadvar.}: Table[Addr, AsyncSocket]
queue {.threadvar.}: Deque[Msg]
proc readClientMsg(client: AsyncSocket) {.async.} =
while true:
let text = await client.recvLine
if text == "":
echo "a client exited"
clients.del client.getPeerAddr
break
while queue.len > queueCap:
await sleepAsync 50
let msg = (text: text, source: client.getPeerAddr)
echo "read from ", msg.source
queue.addFirst(msg)
proc broadcast {.async.} =
while true:
if queue.len < 1:
await sleepAsync 50
continue
let
msg = queue.popLast()
nursery = new Nursery
with nursery:
for a, c in clients.pairs:
if a == msg.source:
echo "no forward to self ", a
else:
nursery.startSoon c.send(msg.text & "\r\n")
proc acceptClients(server: AsyncSocket) {.async.} =
let nursery = new Nursery
with nursery:
while true:
let client = await server.accept
if clients.len >= clientCap:
stderr.write("there are too many clients already, closing this one aggressively.")
client.close
continue
clients[client.getPeerAddr] = client
nursery.startSoon client.readClientMsg
proc serve {.async.} =
let server = newAsyncSocket(AF_INET, SOCK_STREAM, IPPROTO_IP)
server.bindAddr(port = Port(0), address = "127.0.0.1")
defer: server.close
server.listen
echo "listening on ", server.getLocalAddr
let nursery = new Nursery
with nursery:
nursery.startSoon(broadcast())
nursery.startSoon(server.acceptClients)
when isMainModule:
waitFor serve()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment