Chat server

From Rosetta Code
Revision as of 14:47, 30 December 2010 by rosettacode>Dkf (whitespace/cleanup)
Task
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
  1. 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())

  1. 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>

Tcl

Works with: Tcl version 8.6

<lang tcl>package require Tcl 8.6

  1. 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 }

   }

}

  1. 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

}

  1. 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"

   }

}

  1. 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>