I'm not sure what to do to run the code normally. Any help is appreciated!
Server's code:
import net, strutils, os, threadpool, locks, tables
# Global shared state for all clients
var
clients: Table[int, Socket] # Client ID -> Socket mapping
clientLock: Lock # Thread-safe access to clients
nextClientId = 0
initLock(clientLock)
proc broadcast(message: string, excludeId: int = -1) =
## Send message to all connected clients except excludeId
withLock clientLock:
var toRemove: seq[int] = @[] # Track disconnected clients
for id, client in clients:
if id != excludeId:
try:
client.send(message & "\n")
except OSError:
# Mark for removal
toRemove.add(id)
# Cleanup disconnected clients
for id in toRemove:
clients.del(id)
proc handleClient(client: Socket, clientId: int, address: string) =
## Thread function to handle a single client
try:
# Add client to shared table
withLock clientLock:
clients[clientId] = client
echo "[SERVER] Client ", clientId, " connected from ", address
# Send welcome message
client.send("Connection established. Your ID: " & $clientId & "\n")
client.send("Type 'exit' to quit\n\n")
# Notify other clients
broadcast("User " & $clientId & " joined the chat", clientId)
# Chat loop
while true:
let clientMsg = client.recvLine()
if clientMsg == "" or clientMsg == "exit":
echo "[SERVER] Client ", clientId, " disconnected"
break
echo "[CLIENT ", clientId, "] ", clientMsg
# Broadcast to all other clients
let broadcastMsg = "[" & $clientId & "]: " & clientMsg
broadcast(broadcastMsg, clientId)
except OSError as e:
echo "[ERROR Client ", clientId, "] ", e.msg
finally:
# Cleanup - remove from clients table
withLock clientLock:
if clientId in clients:
clients.del(clientId)
# Notify other clients
broadcast("User " & $clientId & " left the chat")
client.close()
echo "[SERVER] Client ", clientId, " cleanup complete"
proc main() =
let server = newSocket()
server.bindAddr(Port(8080))
server.listen()
echo "[MULTI-CLIENT CHAT SERVER]"
echo "Listening on port 8080"
echo "Ready for multiple connections..."
echo ""
while true:
var client: Socket
var address = ""
# Wait for new connection
server.acceptAddr(client, address)
# Assign unique ID with lock protection
var currentId: int
withLock clientLock:
inc nextClientId
currentId = nextClientId
# Spawn new thread for this client
echo "[SERVER] Spawning thread for client ", currentId, " from ", address
spawn handleClient(client, currentId, address)
when isMainModule:
main()
Client's code:
import net, strutils, os, threadpool
proc receiveMessages(client: Socket) {.thread, gcsafe.} =
## Thread to receive messages from server
try:
while true:
let msg = client.recvLine()
if msg == "":
echo "\n[Server disconnected]"
break
# Display incoming message
echo "\n" & msg
stdout.write("[YOU] ")
stdout.flushFile()
except OSError as e:
echo "\n[Receive error] ", e.msg
except:
echo "\n[Receive thread terminated]"
proc main() =
let client = newSocket()
try:
echo "Connecting to 127.0.0.1:8080..."
client.connect("127.0.0.1", Port(8080))
let welcome = client.recvLine()
echo welcome
# Start receiver thread
var recvThread: Thread[Socket]
createThread(recvThread, receiveMessages, client)
echo "\nConnected! Type messages below:\n"
# Main sending loop
while true:
stdout.write("[YOU] ")
stdout.flushFile()
let msg = stdin.readLine()
if msg == "exit":
client.send("exit\n")
break
client.send(msg & "\n")
# Wait for receiver thread to finish
joinThread(recvThread)
except OSError as e:
echo "[CONNECTION ERROR] ", e.msg
echo "Make sure server is running: nim c --threads:on -r server.nim"
finally:
client.close()
echo "\nDisconnected"
main()