Skip to content

Commit 3609e30

Browse files
committed
Basic server skeleton
1 parent 7cca8fc commit 3609e30

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

README.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,30 @@ This is a skeleton project for a chat server we'll use to build a chat client an
77

88
To start we'll work in groups on implementing a very basic server that listens on a socket and routes messages, and a client that can talk to it.
99

10-
Start by implementing the protocol below. Don't worry about concurrency yet! The library included in this skeleton project `clj-sockets` is synchronous, so no need to read up on Clojure concurrency primitives or core.async channels. Start with a synchronous server, and we'll make it async next week.
10+
Start by implementing the protocol below. We are using a single `ref` to handle shared state. In the future, we will port this code over to use `core.async`
1111

1212
### The Protocol
1313
- It's a line based protocol. Newline signifies end of message
14-
- channel and nicknames can contain only `[a-z|A-Z|0-9|-+]`,
15-
- channel names must begin with #,
14+
- nicknames can contain only `[a-z|A-Z|0-9|-+]`,
1615
- usernames can begin with any character other then #
1716
- After connecting, each user sends `USER nickname` as their first command
18-
- At first there's only one channel, `#general`, we'll add more later!
19-
- Messaging a user is done with `MSG nickanme message`
20-
- Messaging a channel is done with `MSG #channel message`
17+
- Messaging is done with `MSG message`
18+
2119

2220
### Goals for Week 1
23-
- Get a feel for working with a project larger then one function
24-
- Write a couple of tests (either with [clojure.test](https://clojure.github.io/clojure/clojure.test-api.html) or [midje](https://github.com/marick/Midje)).
25-
- Try to practice REPL-driven development
26-
- Think about how to organize code into namespaces
27-
- Have fun!!!
21+
The skeleton code implements a simple chat server. This week you should:
22+
- Understand the server code (we'll go over it together)
23+
- Talk to the server using [netcat](http://en.wikipedia.org/wiki/Netcat)
24+
- Start by writing a separate program to function as a client. It should allow the user to specify a server/port and nickname as command line args (Run `lein new app chat-client` to create a new app skeleton)
25+
- Implement private messaging in the server
26+
- Bonus points if the client can connect to multiple servers at once
27+
28+
29+
## Goals for the future
30+
- Add support for channels, which start with #
31+
- Automatically place users in #general and allow users to join and leave channels with `JOIN #channel` and `LEAVE #channel`
32+
- Update messaging syntax to be `MSG #channel message`
33+
- Devise a mechanism for the client to be on multiple channels at once
34+
- Add support for changing nicknames
35+
- Migrate to core.aync
36+
- Replace sockets with websockets and write a web UI in cljs

src/chat_server/.#.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/chat_server/core.clj

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,45 @@
11
(ns chat-server.core
2-
(:require [clj-sockets.core :as socket :refer []])
2+
(:require [clj-sockets.core :as socket])
33
(:gen-class))
44

5+
(def clients (ref {}))
6+
7+
8+
(defn serve-client
9+
[nick client]
10+
(doseq [line (socket/read-lines client)]
11+
(let [other-clients (vals (dissoc @clients nick))]
12+
(case (first (clojure.string/split line #" "))
13+
;; Instructor Note: explain why a doall is needed here
14+
"MSG" (doall (map #(socket/write-line % (str nick ": " (subs line 4))) other-clients))
15+
16+
;;TODO: Process other kinds of command here
17+
18+
;; else
19+
(socket/write-line client "ERROR: I don't understand")))))
20+
21+
(defn new-client
22+
[client]
23+
(let [command (socket/read-line client)]
24+
(if-let [[_ nick] (re-matches #"USER (.*)" command)]
25+
;; Instructor note: explain why we have to use a transaction here to make sure checking if user exists and adding them happens atomically
26+
(if (dosync
27+
(when-not (get @clients nick)
28+
(alter clients assoc nick client)))
29+
30+
(serve-client nick client)
31+
32+
(do
33+
(socket/write-line client "ERROR: Nick already taken")
34+
(socket/close-socket client))))))
35+
536
(defn -main
6-
"I don't do a whole lot ... yet."
37+
"The hello world of chat servers"
738
[& args]
8-
(println "Hello, World!"))
39+
(let [port 1234
40+
server-socket (socket/create-server port)]
41+
(println "Listening on port" port)
42+
(loop [client (socket/listen server-socket)]
43+
;; TODO: improve debugging inside this future
44+
(future (new-client client))
45+
(recur (socket/listen server-socket)))))

0 commit comments

Comments
 (0)