2011-04-27

How to write a simple echo and chat server in Ruby 1.8

This blog post is an introduction to TCP socket server programming and thread programming in Ruby 1.8. It also illustrates how compact and expressive Ruby code can be. The intended audience is Ruby beginners.

require 'socket'  # TCPServer
ss = TCPServer.new(1233)
loop {
  Thread.start(ss.accept) { |s|
    begin
      while line = s.gets;  # Returns nil on EOF.
        (s << "You wrote: #{line.inspect}\r\n").flush
      end
    rescue
      bt = $!.backtrace * "\n  "
      ($stderr << "error: #{$!.inspect}\n  #{bt}\n").flush
    ensure
      s.close
    end
  }
}

Use telnet 127.0.0.1 1233 to connect to the echo server as a new participant, and then type your chat message(s) terminated by Enter (newline). The echo server sends back each message it receives with the You wrote boilerplate around it. Multiple independent clients can connect to the chat server at a time.

The chat server:

require 'socket'  # TCPServer
require 'thread'  # Queue
ssock = TCPServer.new(1234)
msgs = Queue.new
participants = []
Thread.start {  # Send chat messages to participants.
  while msg = msgs.pop;  # Always true.
    participants.each { |s|
      (s << msg).flush rescue IOError
    }
  end
}
loop {
  Thread.start(ssock.accept) { |sock|
    participants << sock
    begin
      while line = sock.gets;  # Returns nil on EOF.
        msgs << ": #{line.chomp!}\r\n"
      end
    rescue
      bt = $!.backtrace * "\n  "
      ($stderr << "error: #{$!.inspect}\n  #{bt}\n").flush
    ensure
      participants.delete sock
      sock.close
    end
  }
}

Use telnet 127.0.0.1 1234 to connect to the chat server as a new participant, and then type your chat message(s) terminated by Enter (newline). The chat server sends each message (with a colon prepended) to all connected participants.

The Ruby source files above demonstrate the following:

  • how to create a thread
  • how to use thread-local variables (e.g. sock)
  • how to report exceptions in a thread
  • how to ignore IOError
  • how to read a line from a socket
  • how to use the Queue class for synchronization between threads
  • how to clean up using an ensure block
  • how to pass values to a newly created thread

No comments: