Maintenance of Ruby 2.0.0 ended on February 24, 2016. Read more
Object
GServer implements a generic server, featuring
thread pool management, simple logging, and multi-server management. See
HttpServer in xmlrpc/httpserver.rb
in the Ruby standard
library for an example of GServer in action.
Any kind of application-level server can be implemented using this class.
It accepts multiple simultaneous connections from clients, up to an
optional maximum number. Several services (i.e. one service per
TCP port) can be run simultaneously, and stopped at any time through the
class method GServer.stop(port)
. All the threading issues are
handled, saving you the effort. All events are optionally logged, but you
can provide your own event handlers if you wish.
Using GServer is simple. Below we implement a
simple time server, run it, query it, and shut it down. Try this code in
irb
:
require 'gserver' # # A server that returns the time in seconds since 1970. # class TimeServer < GServer def initialize(port=10001, *args) super(port, *args) end def serve(io) io.puts(Time.now.to_i) end end # Run the server with logging enabled (it's a separate thread). server = TimeServer.new server.audit = true # Turn logging on. server.start # *** Now point your browser to http://localhost:10001 to see it working *** # See if it's still running. GServer.in_service?(10001) # -> true server.stopped? # -> false # Shut the server down gracefully. server.shutdown # Alternatively, stop it immediately. GServer.stop(10001) # or, of course, "server.stop".
All the business of accepting connections and exception handling is taken care of. All we have to do is implement the method that actually serves the client.
As the example above shows, the way to use GServer is to subclass it to create a specific
server, overriding the serve
method. You can override other
methods as well if you wish, perhaps to collect statistics, or emit more
detailed logging.
The above methods are only called if auditing is enabled, via audit=.
You can also override log and error if, for example, you wish to use a more sophisticated logging system.
Set to true to cause the callbacks connecting, disconnecting, starting, and stopping to be called during the server's lifecycle
Check if a server is running on the given port and host
port
port, as a FixNum, of the server to check
host
host on which to find the server to check
Returns true if a server is running on that port and host.
# File gserver.rb, line 109 def GServer.in_service?(port, host = DEFAULT_HOST) @@services.has_key?(host) and @@services[host].has_key?(port) end
Create a new server
port
the port, as a FixNum, on which to listen
host
the host to bind to
maxConnections
the maximum number of simultaneous connections to accept
stdlog
IO device on which to log messages
audit
if true, lifecycle callbacks will be called. See audit
debug
if true, error messages are logged. See debug
# File gserver.rb, line 222 def initialize(port, host = DEFAULT_HOST, maxConnections = 4, stdlog = $stderr, audit = false, debug = false) @tcpServerThread = nil @port = port @host = host @maxConnections = maxConnections @connections = [] @connectionsMutex = Mutex.new @connectionsCV = ConditionVariable.new @stdlog = stdlog @audit = audit @debug = debug end
Stop the server running on the given port, bound to the given host
port
port, as a FixNum, of the server to stop
host
host on which to find the server to stop
# File gserver.rb, line 97 def GServer.stop(port, host = DEFAULT_HOST) @@servicesMutex.synchronize { @@services[host][port].stop } end
Return the current number of connected clients
# File gserver.rb, line 134 def connections @connections.size end
Join with the server thread
# File gserver.rb, line 139 def join @tcpServerThread.join if @tcpServerThread end
Schedule a shutdown for the server
# File gserver.rb, line 129 def shutdown @shutdown = true end
Start the server if it isn't already running
maxConnections
override maxConnections
given to the constructor. A negative
value indicates that the value from the constructor should be used.
# File gserver.rb, line 241 def start(maxConnections = -1) raise "server is already running" if !stopped? @shutdown = false @maxConnections = maxConnections if maxConnections > 0 @@servicesMutex.synchronize { if GServer.in_service?(@port,@host) raise "Port already in use: #{host}:#{@port}!" end @tcpServer = TCPServer.new(@host,@port) @port = @tcpServer.addr[1] @@services[@host] = {} unless @@services.has_key?(@host) @@services[@host][@port] = self; } @tcpServerThread = Thread.new { begin starting if @audit while !@shutdown @connectionsMutex.synchronize { while @connections.size >= @maxConnections @connectionsCV.wait(@connectionsMutex) end } client = @tcpServer.accept Thread.new(client) { |myClient| @connections << Thread.current begin myPort = myClient.peeraddr[1] serve(myClient) if !@audit or connecting(myClient) rescue => detail error(detail) if @debug ensure begin myClient.close rescue end @connectionsMutex.synchronize { @connections.delete(Thread.current) @connectionsCV.signal } disconnecting(myPort) if @audit end } end rescue => detail error(detail) if @debug ensure begin @tcpServer.close rescue end if @shutdown @connectionsMutex.synchronize { while @connections.size > 0 @connectionsCV.wait(@connectionsMutex) end } else @connections.each { |c| c.raise "stop" } end @tcpServerThread = nil @@servicesMutex.synchronize { @@services[@host].delete(@port) } stopping if @audit end } self end
Called when a client connects, if auditing is enabled.
client
a TCPSocket instance representing the client that connected
Return true to allow this client to connect, false to prevent it.
# File gserver.rb, line 162 def connecting(client) addr = client.peeraddr log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " + "#{addr[2]}<#{addr[3]}> connect") true end
Called when a client disconnects, if audition is enabled.
clientPort
the port of the client that is connecting
# File gserver.rb, line 173 def disconnecting(clientPort) log("#{self.class.to_s} #{@host}:#{@port} " + "client:#{clientPort} disconnect") end
Called if debug is true whenever an unhandled exception is raised. This implementation simply logs the backtrace.
detail
the Exception that was caught
# File gserver.rb, line 196 def error(detail) log(detail.backtrace.join("\n")) end
Log a message to stdlog, if it's defined. This implementation outputs the timestamp and message to the log.
msg
the message to log
# File gserver.rb, line 204 def log(msg) if @stdlog @stdlog.puts("[#{Time.new.ctime}] %s" % msg) @stdlog.flush end end