Files
2026-03-09 23:09:46 -04:00

131 lines
4.5 KiB
Clojure

;;; 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))))