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.