Chat server
You are encouraged to solve this task according to the task description, using any language you may know.
Write a server for a minimal text based chat. People should be able to connect via ‘telnet’, sign on with a nickname, and type messages which will then be seen by all other connected users. Arrivals and departures of chat members should generate appropriate notification messages.
PicoLisp
<lang PicoLisp>#!/usr/bin/picolisp /usr/lib/picolisp/lib.l
(de chat Lst
(out *Sock (mapc prin Lst) (prinl) ) )
(setq *Port (port 4004))
(loop
(setq *Sock (listen *Port)) (NIL (fork) (close *Port)) (close *Sock) )
(out *Sock
(prin "Please enter your name: ") (flush) )
(in *Sock (setq *Name (line T)))
(tell 'chat "+++ " *Name " arrived +++")
(task *Sock
(in @ (ifn (eof) (tell 'chat *Name "> " (line T)) (tell 'chat "--- " *Name " left ---") (bye) ) ) )
(wait)</lang> After starting the above script, connect to the chat server from two terminals:
Terminal 1 | Terminal 2 ---------------------------------+--------------------------------- $ telnet localhost 4004 | Trying ::1... | Trying 127.0.0.1... | Connected to localhost. | Escape character is '^]'. | Please enter your name: Ben | | $ telnet localhost 4004 | Trying ::1... | Trying 127.0.0.1... | Connected to localhost. | Escape character is '^]'. | Please enter your name: Tom +++ Tom arrived +++ | Hi Tom | | Ben> Hi Tom | Hi Ben Tom> Hi Ben | | How are you? Tom> How are you? | Thanks, fine! | | Ben> Thanks, fine! | See you! Tom> See you! | | ^] | telnet> quit --- Tom left --- | | Connection closed. | $
Python
<lang python>#!/usr/bin/env python
import socket import thread import time
HOST = "" PORT = 4004
def accept(conn):
""" Call the inner func in a thread so as not to block. Wait for a name to be entered from the given connection. Once a name is entered, set the connection to non-blocking and add the user to the users dict. """ def threaded(): while True: conn.send("Please enter your name: ") try: name = conn.recv(1024).strip() except socket.error: continue if name in users: conn.send("Name entered is already in use.\n") elif name: conn.setblocking(False) users[name] = conn broadcast(name, "+++ %s arrived +++" % name) break thread.start_new_thread(threaded, ())
def broadcast(name, message):
""" Send a message to all users from the given name. """ print message for to_name, conn in users.items(): if to_name != name: try: conn.send(message + "\n") except socket.error: pass
- Set up the server socket.
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.setblocking(False) server.bind((HOST, PORT)) server.listen(1) print "Listening on %s" % ("%s:%s" % server.getsockname())
- Main event loop.
users = {} while True:
try: # Accept new connections. while True: try: conn, addr = server.accept() except socket.error: break accept(conn) # Read from connections. for name, conn in users.items(): try: message = conn.recv(1024) except socket.error: continue if not message: # Empty string is given on disconnect. del users[name] broadcast(name, "--- %s leaves ---" % name) else: broadcast(name, "%s> %s" % (name, message.strip())) time.sleep(.1) except (SystemExit, KeyboardInterrupt): break</lang>
Ruby
<lang Ruby>require 'gserver'
class ChatServer < GServer
def initialize *args super
#Keep a list for broadcasting messages @chatters = []
#We'll need this for thread safety @mutex = Mutex.new end
#Send message out to everyone but sender def broadcast message, sender = nil #Need to use \r\n for our Windows friends message = message.strip << "\r\n"
#Mutex for safety - GServer uses threads @mutex.synchronize do @chatters.each do |chatter| begin chatter.puts message unless chatter == sender rescue @chatters.delete chatter end end end end
#Handle each connection def serve io io.print 'Name: ' name = io.gets
#They might disconnect return if name.nil?
name.strip!
broadcast "--+ #{name} has joined +--"
#Add to our list of connections @mutex.synchronize do @chatters << io end
#Get and broadcast input until connection returns nil loop do message = io.gets
if message broadcast "#{name}> #{message}", io else break end end
broadcast "--+ #{name} has left +--" end
end
- Start up the server on port 7777
- Accept connections for any IP address
- Allow up to 100 connections
- Send information to stderr
- Turn on informational messages
ChatServer.new(7000, '0.0.0.0', 100, $stderr, true).start.join </lang>
Tcl
<lang tcl>package require Tcl 8.6
- Write a message to everyone except the sender of the message
proc writeEveryoneElse {sender message} {
dict for {who ch} $::cmap {
if {$who ne $sender} { puts $ch $message }
}
}
- How to read a line (up to 256 chars long) in a coroutine
proc cgets {ch var} {
upvar 1 $var v while {[gets $ch v] < 0} {
if {[eof $ch] || [chan pending input $ch] > 256} { return false } yield
} return true
}
- The chatting, as seen by one user
proc chat {ch addr port} {
### CONNECTION CODE ### #Log "connection from ${addr}:${port} on channel $ch" fconfigure $ch -buffering none -blocking 0 -encoding utf-8 fileevent $ch readable [info coroutine] global cmap try {
### GET THE NICKNAME OF THE USER ### puts -nonewline $ch "Please enter your name: " if {![cgets $ch name]} { return } #Log "Mapping ${addr}:${port} to ${name} on channel $ch" dict set cmap $name $ch writeEveryoneElse $name "+++ $name arrived +++"
### MAIN CHAT LOOP ### while {[cgets $ch line]} { writeEveryoneElse $name "$name> $line" }
} finally {
### DISCONNECTION CODE ### if {[info exists name]} { writeEveryoneElse $name "--- $name left ---" dict unset cmap $name } close $ch #Log "disconnection from ${addr}:${port} on channel $ch"
}
}
- Service the socket by making corouines running [chat]
socket -server {coroutine c[incr count] chat} 4004 set ::cmap {}; # Dictionary mapping nicks to channels vwait forever; # Run event loop</lang>