-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
More comments and nicer code for ChatApp.
- Loading branch information
Showing
5 changed files
with
173 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# The ChatApp source code | ||
|
||
This directory contains the ChatApp project, which is the project that is | ||
created as part of Chapter 3 of the Nim in Action book. | ||
|
||
To compile run: | ||
|
||
``` | ||
nim c src/client | ||
nim c src/server | ||
``` | ||
|
||
You can then run the ``server`` in one terminal by executing ``./src/server``. | ||
|
||
After doing so you can execute multiple clients in different terminals and have | ||
them communicate via the server. | ||
|
||
To execute a client, make sure to specify the server address and user name | ||
on the command line: | ||
|
||
```bash | ||
./src/client localhost Peter | ||
``` | ||
|
||
You should then be able to start typing in messages and sending them | ||
by pressing the Enter key. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,54 @@ | ||
import os, threadpool, asyncnet, asyncdispatch, protocol | ||
import os, threadpool, asyncdispatch, asyncnet | ||
import protocol | ||
|
||
proc connect(socket: AsyncSocket, serverAddr: string) {.async.} = | ||
## Connects the specified AsyncSocket to the specified address. | ||
## Then receives messages from the server continuously. | ||
echo("Connecting to ", serverAddr) | ||
# Pause the execution of this procedure until the socket connects to | ||
# the specified server. | ||
await socket.connect(serverAddr, 7687.Port) | ||
echo("Connected!") | ||
while true: | ||
# Pause the execution of this procedure until a new message is received | ||
# from the server. | ||
let line = await socket.recvLine() | ||
# Parse the received message using ``parseMessage`` defined in the | ||
# protocol module. | ||
let parsed = parseMessage(line) | ||
# Display the message to the user. | ||
echo(parsed.username, " said ", parsed.message) | ||
|
||
echo("Chat application started") | ||
# Ensure that the correct amount of command line arguments was specified. | ||
if paramCount() < 2: | ||
# Terminate the client early with an error message if there was not | ||
# enough command line arguments specified by the user. | ||
quit("Please specify the server address, e.g. ./client localhost username") | ||
|
||
if paramCount() == 0: | ||
quit("Please specify the server address") | ||
|
||
# Retrieve the first command line argument. | ||
let serverAddr = paramStr(1) | ||
# Retrieve the second command line argument. | ||
let username = paramStr(2) | ||
# Initialise a new asynchronous socket. | ||
var socket = newAsyncSocket() | ||
|
||
# Execute the ``connect`` procedure in the background asynchronously. | ||
asyncCheck connect(socket, serverAddr) | ||
# Execute the ``readInput`` procedure in the background in a new thread. | ||
var messageFlowVar = spawn stdin.readLine() | ||
while true: | ||
# Check if the ``readInput`` procedure returned a new line of input. | ||
if messageFlowVar.isReady(): | ||
let message = createMessage("Anonymous", ^messageFlowVar) | ||
asyncCheck socket.send(message) | ||
# If a new line of input was returned, we can safely retrieve it | ||
# without blocking. | ||
# The ``createMessage`` is then used to create a message based on the | ||
# line of input. The message is then sent in the background asynchronously. | ||
asyncCheck socket.send(createMessage(username, ^messageFlowVar)) | ||
# Execute the ``readInput`` procedure again, in the background in a | ||
# new thread. | ||
messageFlowVar = spawn stdin.readLine() | ||
|
||
# Execute the asyncdispatch event loop, to continue the execution of | ||
# asynchronous procedures. | ||
asyncdispatch.poll() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
--threads:on |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,84 @@ | ||
import asyncdispatch, asyncnettype Client = ref object socket: AsyncSocket netAddr: string id: int connected: bool Server = ref object socket: AsyncSocket clients: seq[Client]proc newServer(): Server = Server(socket: newAsyncSocket(), clients: @[])proc `$`(client: Client): string = $client.id & "(" & client.netAddr & ")"proc processMessages(server: Server, client: Client) {.async.} = while true: let line = await client.socket.recvLine() if line.len == 0: echo(client, " disconnected!") client.connected = false client.socket.close() return echo(client, " sent: ", line)proc loop(server: Server, port = 7687) {.async.} = server.socket.bindAddr(port.Port) server.socket.listen() while true: let (netAddr, clientSocket) = await server.socket.acceptAddr() echo("Accepted connection from ", netAddr) let client = Client( socket: clientSocket, netAddr: netAddr, id: server.clients.len, connected: true ) server.clients.add(client) asyncCheck processMessages(server, client)var server = newServer()waitFor loop(server) | ||
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) |