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.
Example
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.
Advanced
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.
- C
- D
- E
- I
- J
- L
- N
- S
| DEFAULT_HOST | = | "127.0.0.1" |
| [RW] | audit | Set to true to cause the callbacks connecting, disconnecting, starting, and stopping to be called during the server's lifecycle |
| [RW] | debug | Set to true to show more detailed logging |
| [R] | host | Host on which to bind, as a String |
| [R] | maxConnections | Maximum number of connections to accept at a time, as a Fixnum |
| [R] | port | Port on which to listen, as a Fixnum |
| [RW] | stdlog | IO Device on which log messages should be written |
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.
Source: show
# File lib/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
Source: show
# File lib/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
Source: show
# File lib/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
Source: show
# File lib/gserver.rb, line 134 def connections @connections.size end
Join with the server thread
Source: show
# File lib/gserver.rb, line 139 def join @tcpServerThread.join if @tcpServerThread end
Schedule a shutdown for the server
Source: show
# File lib/gserver.rb, line 129 def shutdown @shutdown = true end
Start the server if it isn't already running
- maxConnections
-
override
maxConnectionsgiven to the constructor. A negative value indicates that the value from the constructor should be used.
Source: show
# File lib/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
Stop the server
Source: show
# File lib/gserver.rb, line 115 def stop @connectionsMutex.synchronize { if @tcpServerThread @tcpServerThread.raise "stop" end } end
Returns true if the server has stopped.
Source: show
# File lib/gserver.rb, line 124 def stopped? @tcpServerThread == nil 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.
Source: show
# File lib/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
Source: show
# File lib/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
Source: show
# File lib/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
Source: show
# File lib/gserver.rb, line 204 def log(msg) if @stdlog @stdlog.puts("[#{Time.new.ctime}] %s" % msg) @stdlog.flush end end