131 lines
4.5 KiB
Plaintext
131 lines
4.5 KiB
Plaintext
;;; TCP Chat Client — connects to the TCP chat server
|
|
;;;
|
|
;;; Usage:
|
|
;;; mix clje.run --no-halt examples/tcp_chat_client.clje -- <username>
|
|
;;;
|
|
;;; The server must be running first:
|
|
;;; mix clje.run --no-halt examples/tcp_chat_server.clje
|
|
;;;
|
|
;;; Commands:
|
|
;;; Type a message and press Enter to send
|
|
;;; /who — list online users
|
|
;;; /quit — disconnect and exit
|
|
|
|
(ns TcpChatClient
|
|
(:require [clje.core :refer :all]
|
|
[IO] [String] [System] [erlang] [gen_tcp]))
|
|
|
|
;; ── Receiver process ──────────────────────────────────────────────
|
|
;; Listens for TCP messages from the server and prints them.
|
|
|
|
(defn receiver [socket parent]
|
|
(receive
|
|
[:tcp _sock data]
|
|
(let [line (String/trim data)]
|
|
(cond
|
|
(String/starts-with? line "MSG:")
|
|
(let [rest (String/slice line 4 (String/length line))
|
|
parts (String/split rest ":" (list #el[:parts 2]))
|
|
from (hd parts)
|
|
text (hd (tl parts))]
|
|
(IO/puts (str from "> " text))
|
|
(TcpChatClient/receiver socket parent))
|
|
|
|
(String/starts-with? line "SYS:")
|
|
(do
|
|
(IO/puts (str "* " (String/slice line 4 (String/length line))))
|
|
(TcpChatClient/receiver socket parent))
|
|
|
|
(String/starts-with? line "JOIN:")
|
|
(do
|
|
(IO/puts (str "* " (String/slice line 5 (String/length line)) " joined"))
|
|
(TcpChatClient/receiver socket parent))
|
|
|
|
(String/starts-with? line "QUIT:")
|
|
(do
|
|
(IO/puts (str "* " (String/slice line 5 (String/length line)) " left"))
|
|
(TcpChatClient/receiver socket parent))
|
|
|
|
:else
|
|
(do
|
|
(IO/puts line)
|
|
(TcpChatClient/receiver socket parent))))
|
|
|
|
[:tcp_closed _sock]
|
|
(do
|
|
(IO/puts "* Connection closed by server.")
|
|
(System/halt 0))
|
|
|
|
[:tcp_error _sock _reason]
|
|
(do
|
|
(IO/puts "* Connection error.")
|
|
(System/halt 1))))
|
|
|
|
;; ── Input loop ────────────────────────────────────────────────────
|
|
;; Reads from stdin and sends to the server.
|
|
|
|
(defn send-line [socket line]
|
|
(gen_tcp/send socket line))
|
|
|
|
(defn input-loop [socket]
|
|
(let [line (IO/gets "")]
|
|
(cond
|
|
(== line :eof)
|
|
(do
|
|
(TcpChatClient/send-line socket "QUIT\n")
|
|
(gen_tcp/close socket)
|
|
(System/halt 0))
|
|
|
|
:else
|
|
(let [trimmed (String/trim line)]
|
|
(cond
|
|
(== trimmed "/quit")
|
|
(do
|
|
(TcpChatClient/send-line socket "QUIT\n")
|
|
(gen_tcp/close socket)
|
|
(IO/puts "Goodbye!")
|
|
(System/halt 0))
|
|
|
|
(== trimmed "")
|
|
(TcpChatClient/input-loop socket)
|
|
|
|
:else
|
|
(do
|
|
(TcpChatClient/send-line socket (str "MSG:" trimmed "\n"))
|
|
(TcpChatClient/input-loop socket)))))))
|
|
|
|
;; ── Connect ───────────────────────────────────────────────────────
|
|
|
|
(defn start [username]
|
|
(case (gen_tcp/connect (erlang/binary-to-list "127.0.0.1") 4040
|
|
(list :binary #el[:active true] #el[:packet :line]))
|
|
[:ok socket]
|
|
(do
|
|
;; Send JOIN
|
|
(TcpChatClient/send-line socket (str "JOIN:" username "\n"))
|
|
|
|
;; Spawn receiver and hand it the socket
|
|
(let [me *self*
|
|
recv-pid (spawn (fn [] (TcpChatClient/receiver socket me)))]
|
|
(gen_tcp/controlling-process socket recv-pid)
|
|
|
|
;; Run input loop in the main process
|
|
(IO/puts (str "Connected as " username ". Type a message or /quit to exit."))
|
|
(TcpChatClient/input-loop socket)))
|
|
|
|
[:error reason]
|
|
(do
|
|
(IO/puts (str "Could not connect: " reason))
|
|
(IO/puts "Is the server running? Start it with:")
|
|
(IO/puts " mix clje.run --no-halt examples/tcp_chat_server.clje")
|
|
(System/halt 1))))
|
|
|
|
;; ── Entry point ─────────────────────────────────────────────────────
|
|
|
|
(let [args (System/argv)]
|
|
(if (== (count args) 0)
|
|
(do
|
|
(IO/puts "Usage: mix clje.run --no-halt examples/tcp_chat_client.clje -- <username>")
|
|
(System/halt 1))
|
|
(TcpChatClient/start (hd args))))
|