class GServer
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.
Constants
- DEFAULT_HOST
Attributes
Set to true to cause the callbacks connecting, disconnecting, starting, and stopping to be called during the server's lifecycle
Set to true to show more detailed logging
Host on which to bind, as a String
Maximum number of connections to accept at a time, as a FixNum
Port on which to listen, as a FixNum
IO Device on which log messages should be written
Public Class Methods
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 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
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 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
# File lib/gserver.rb, line 97 def GServer.stop(port, host = DEFAULT_HOST) @@servicesMutex.synchronize { @@services[host][port].stop } end
Public Instance Methods
Return the current number of connected clients
# File lib/gserver.rb, line 134 def connections @connections.size end
Join with the server thread
# File lib/gserver.rb, line 139 def join @tcpServerThread.join if @tcpServerThread end
# File lib/gserver.rb, line 87 def serve(io) end
Schedule a shutdown for the server
# File lib/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 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
# File lib/gserver.rb, line 115 def stop @connectionsMutex.synchronize { if @tcpServerThread @tcpServerThread.raise "stop" end } end
Returns true if the server has stopped.
# File lib/gserver.rb, line 124 def stopped? @tcpServerThread == nil end
Protected Instance Methods
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 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
# File lib/gserver.rb, line 173 def disconnecting(clientPort) log("#{self.class.to_s} #{@host}:#{@port} " + "client:#{clientPort} disconnect") 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 lib/gserver.rb, line 204 def log(msg) if @stdlog @stdlog.puts("[#{Time.new.ctime}] %s" % msg) @stdlog.flush end end
Called when the server is starting up, if auditing is enabled.
# File lib/gserver.rb, line 181 def starting() log("#{self.class.to_s} #{@host}:#{@port} start") end
Called when the server is shutting down, if auditing is enabled.
# File lib/gserver.rb, line 186 def stopping() log("#{self.class.to_s} #{@host}:#{@port} stop") end