December 10, 2006

Build a Chat Server in Minutes with Ruby and GServer
Post by Peter Cooper

GServer is easily one of my favorite Ruby libraries. Even better, it's in the standard library so there's no installation involved. You can get started straight away.

While tools like Mongrel, lighttpd, and Apache tend to get most of the attention, GServer works at a lower level by allowing you to create server applications of your own. For example:

require 'gserver'

class BasicServer < GServer
  def serve(io)
    io.puts("Hello world!")
  end
end

server = BasicServer.new(1234)
server.start
sleep 10
server.shutdown

This basic snippet creates a GServer based server and lets it run for 10 seconds before shutting down. During those ten seconds you can telnet to localhost on port 1234 (or try http://127.0.0.1:1234/ in your Web browser) and you'll see "Hello world!" in response.

This is only the start. GServer uses threads, so multiple users can connect to the same server at once. GServer has logging features you can enable. It's extremely flexible.

Just for fun, here's a scrappy 'chat server' written using GServer for your amusement:

require 'gserver'

class ChatServer < GServer
  def initialize(*args)
    super(*args)
    
    # Keep an overall record of the client IDs allocated
    # and the lines of chat
    @@client_id = 0
    @@chat = []
  end
  
  def serve(io)
    # Increment the client ID so each client gets a unique ID
    @@client_id += 1
    my_client_id = @@client_id
    my_position = @@chat.size
    
    io.puts("Welcome to the chat, client #{@@client_id}!")
    
    # Leave a message on the chat queue to signify this client
    # has joined the chat
    @@chat << [my_client_id, ""]
    
    loop do 
      # Every 5 seconds check to see if we are receiving any data 
      if IO.select([io], nil, nil, 2)
        # If so, retrieve the data and process it..
        line = io.gets
      
        # If the user says 'quit', disconnect them
        if line =~ /quit/
          @@chat << [my_client_id, ""]
          break
        end

        # Shut down the server if we hear 'shutdown'
        self.stop if line =~ /shutdown/
      
        # Add the client's text to the chat array along with the
        # client's ID
        @@chat << [my_client_id, line]      
      else
        # No data, so print any new lines from the chat stream
        @@chat[my_position..-1].each_with_index do |line, index|
          io.puts("#{line[0]} says: #{line[1]}")
        end
        
        # Move the position to one past the end of the array
        my_position = @@chat.size
      end
    end
    
  end
end

server = ChatServer.new(1234)
server.start

loop do
  break if server.stopped?
end

puts "Server has been terminated"

Update January 4th, 2010

An e-mail from John Kennedy:

I noticed that there were 2 places you used a polling technique when you could have blocked.  I though I would mention this because this would greatly improve the efficiency of your code.  Polling can hurt your performance when you scale up.

The first place was:
    loop do 
      # Every 5 seconds check to see if we are receiving any data 
      if IO.select([io], nil, nil, 2)
        # If so, retrieve the data and process it..
        line = io.gets

Instead of looping and checking every 5 seconds, you could just put:
    line = io.readline

That would block until a line was available.

Also, at the end of the script, you have a loop that constantly checks if the server is still running. 
  loop do
    break if server.stopped?
    ..
  end

Instead of this, you could just write:  
  server.join

That would join the current executing thread to the Server's Thread.

Anyways, I hope that was helpful.  I just thought that it might benefit the people that come across your old post.
John J Kennedy.