Skip to content

Commit 952af19

Browse files
committed
An implementation with agents
1 parent 8aeea27 commit 952af19

File tree

2 files changed

+57
-28
lines changed

2 files changed

+57
-28
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
target/
2+
.nrepl-port

src/chat_server/core.clj

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,68 @@
11
(ns chat-server.core
22
(:require [clj-sockets.core :as socket]
3-
[chat-server.repl :as repl])
3+
[chat-server.repl :as repl]
4+
[clojure.string :as string])
45
(:gen-class))
56

6-
(def clients (ref {}))
7+
(def clients (ref []))
78

9+
(defn write-line [client message]
10+
(socket/write-line (:socket client) message)
11+
client)
812

9-
(defn serve-client
10-
[nick client]
11-
(doseq [line (socket/read-lines client)]
12-
(println "got message from user:" line)
13-
(let [other-clients (vals (dissoc @clients nick))]
14-
(case
15-
(first (clojure.string/split line #" "))
16-
;; Instructor Note: explain why a doall is needed here
17-
"MSG" (doall (map #(socket/write-line % (str nick ": " (subs line 4))) other-clients))
13+
(defn error
14+
[socket message]
15+
(socket/write-line socket (str "ERROR: " message)))
1816

19-
;;TODO: Process other kinds of command here
17+
(defn nick-exists?
18+
[nick]
19+
(let [nicks (map #(-> % deref :nick) @clients)]
20+
(some #{nick} nicks)))
2021

21-
;; else
22-
(socket/write-line client "ERROR: I don't understand")))))
22+
(defn set-nick [client nick]
23+
(dosync
24+
(if (nick-exists? nick)
25+
(error (:socket @client) "nick already exists")
26+
(send-off client assoc :nick nick))))
2327

24-
(defn new-client
28+
(defn send-message [client message]
29+
(doseq [d-client @clients]
30+
(when-not (= client d-client)
31+
(send-off d-client write-line (str (:nick @client) ": " message)))))
32+
33+
(defn terminate-client!
34+
[client]
35+
(socket/close-socket (:socket @client))
36+
(dosync
37+
(alter clients (partial remove #{client}))))
38+
39+
(defn listen-client
2540
[client]
26-
(let [command (socket/read-line client)]
27-
(println "got message:" command)
28-
(if-let [[_ nick] (re-matches #"USER (.*)" command)]
29-
;; Instructor note: explain why we have to use a transaction here to make sure checking if user exists and adding them happens atomically
30-
(if (dosync
31-
(when-not (get @clients nick)
32-
(alter clients assoc nick client)))
33-
34-
(serve-client nick client)
35-
36-
(do
37-
(socket/write-line client "ERROR: Nick already taken")
38-
(socket/close-socket client))))))
41+
(let [{:keys [socket nick channels] :or {channels []}} @client]
42+
(loop [line (socket/read-line socket)]
43+
(let [[command & words] (string/split line #" ")]
44+
(case command
45+
"USER" (set-nick client (string/join "-" words))
46+
"MSG" (send-message client (string/join " " words))
47+
"QUIT" (terminate-client! client)
48+
(error socket "I don't understand")))
49+
(when-not (.isClosed socket)
50+
(recur (socket/read-line socket))))))
51+
52+
(defn new-client
53+
[s]
54+
(loop [line (socket/read-line s)]
55+
(if-let [[_ nick] (re-matches #"USER (.*)" line)]
56+
(if-let [client (dosync
57+
(when-not (nick-exists? nick)
58+
(let [client (agent {:socket s :nick nick :channels []})]
59+
(alter clients conj client)
60+
client)))]
61+
(listen-client client)
62+
(do (error s "nick is already taken, try another")
63+
(recur (socket/read-line s))))
64+
(do (error s "first set a nick with USER")
65+
(recur (socket/read-line s))))))
3966

4067
(defn -main
4168
"The hello world of chat servers"

0 commit comments

Comments
 (0)