Chat server: Difference between revisions

From Rosetta Code
Content added Content deleted
(Add Ruby example)
Line 143: Line 143:
except (SystemExit, KeyboardInterrupt):
except (SystemExit, KeyboardInterrupt):
break</lang>
break</lang>

=={{header|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>


=={{header|Tcl}}==
=={{header|Tcl}}==

Revision as of 08:17, 6 January 2011

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>

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

  1. Start up the server on port 7777
  2. Accept connections for any IP address
  3. Allow up to 100 connections
  4. Send information to stderr
  5. Turn on informational messages

ChatServer.new(7000, '0.0.0.0', 100, $stderr, true).start.join </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>