summary refs log tree commit diff stats
path: root/tests/niminaction/Chapter3/ChatApp/src/server.nim
blob: 31da74d16854999587046f5d411e770b84264bc2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
discard """
action: compile
"""

import asyncdispatch, asyncnet

type
  Client = ref object
    socket: AsyncSocket
    netAddr: string
    id: int
    connected: bool

  Server = ref object
    socket: AsyncSocket
    clients: seq[Client]

proc newServer(): Server =
  ## Constructor for creating a new ``Server``.
  Server(socket: newAsyncSocket(), clients: @[])

proc `$`(client: Client): string =
  ## Converts a ``Client``'s information into a string.
  $client.id & "(" & client.netAddr & ")"

proc processMessages(server: Server, client: Client) {.async.} =
  ## Loops while ``client`` is connected to this server, and checks
  ## whether as message has been received from ``client``.
  while true:
    # Pause execution of this procedure until a line of data is received from
    # ``client``.
    let line = await client.socket.recvLine()

    # The ``recvLine`` procedure returns ``""`` (i.e. a string of length 0)
    # when ``client`` has disconnected.
    if line.len == 0:
      echo(client, " disconnected!")
      client.connected = false
      # When a socket disconnects it must be closed.
      client.socket.close()
      return

    # Display the message that was sent by the client.
    echo(client, " sent: ", line)

    # Send the message to other clients.
    for c in server.clients:
      # Don't send it to the client that sent this or to a client that is
      # disconnected.
      if c.id != client.id and c.connected:
        await c.socket.send(line & "\c\l")

proc loop(server: Server, port = 7687) {.async.} =
  ## Loops forever and checks for new connections.

  # Bind the port number specified by ``port``.
  server.socket.bindAddr(port.Port)
  # Ready the server socket for new connections.
  server.socket.listen()
  echo("Listening on localhost:", port)

  while true:
    # Pause execution of this procedure until a new connection is accepted.
    let (netAddr, clientSocket) = await server.socket.acceptAddr()
    echo("Accepted connection from ", netAddr)

    # Create a new instance of Client.
    let client = Client(
      socket: clientSocket,
      netAddr: netAddr,
      id: server.clients.len,
      connected: true
    )
    # Add this new instance to the server's list of clients.
    server.clients.add(client)
    # Run the ``processMessages`` procedure asynchronously in the background,
    # this procedure will continuously check for new messages from the client.
    asyncCheck processMessages(server, client)

# Check whether this module has been imported as a dependency to another
# module, or whether this module is the main module.
when isMainModule:
  # Initialise a new server.
  var server = newServer()
  echo("Server initialised!")
  # Execute the ``loop`` procedure. The ``waitFor`` procedure will run the
  # asyncdispatch event loop until the ``loop`` procedure finishes executing.
  waitFor loop(server)