Bug #4705 ยป 0003-clean-up-and-add-docs-for-GServer.patch
| lib/gserver.rb | ||
|---|---|---|
| # Documentation:: Gavin Sinclair | ||
| # Licence::       Freeware. | ||
| # | ||
| # See the class GServer for documentation. | ||
| # A generic server, featuring thread pool management, | ||
| # simple logging, and multi-server management. | ||
| # See GServer for more documentation. | ||
| # | ||
| require "socket" | ||
| ... | ... | |
| # you the effort.  All events are optionally logged, but you can provide your | ||
| # own event handlers if you wish. | ||
| # | ||
| # === Example | ||
| # == 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+: | ||
| ... | ... | |
| # other methods as well if you wish, perhaps to collect statistics, or emit | ||
| # more detailed logging. | ||
| # | ||
| #   connecting | ||
| #   disconnecting | ||
| #   starting | ||
| #   stopping | ||
| # * #connecting | ||
| # * #disconnecting | ||
| # * #starting | ||
| # * #stopping | ||
| # | ||
| # The above methods are only called if auditing is enabled. | ||
| # 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 | ||
| # You can also override #log and #error if, for example, you wish to use a | ||
| # more sophisticated logging system. | ||
| # | ||
| class GServer | ||
| ... | ... | |
|   @@services = {}   # Hash of opened ports, i.e. services | ||
|   @@servicesMutex = Mutex.new | ||
|   # 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 | ||
|   def GServer.stop(port, host = DEFAULT_HOST) | ||
|     @@servicesMutex.synchronize { | ||
|       @@services[host][port].stop | ||
|     } | ||
|   end | ||
|   # 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. | ||
|   def GServer.in_service?(port, host = DEFAULT_HOST) | ||
|     @@services.has_key?(host) and | ||
|       @@services[host].has_key?(port) | ||
|   end | ||
|   # Stop the server | ||
|   def stop | ||
|     @connectionsMutex.synchronize  { | ||
|       if @tcpServerThread | ||
| ... | ... | |
|     } | ||
|   end | ||
|   # Returns true if the server has stopped. | ||
|   def stopped? | ||
|     @tcpServerThread == nil | ||
|   end | ||
|   # Schedule a shutdown for the server | ||
|   def shutdown | ||
|     @shutdown = true | ||
|   end | ||
|   # Return the current number of connected clients | ||
|   def connections | ||
|     @connections.size | ||
|   end | ||
|   # Join with the server thread | ||
|   def join | ||
|     @tcpServerThread.join if @tcpServerThread | ||
|   end | ||
|   attr_reader :port, :host, :maxConnections | ||
|   attr_accessor :stdlog, :audit, :debug | ||
|   # Port on which to listen, as a FixNum | ||
|   attr_reader :port | ||
|   # Host on which to bind, as a String | ||
|   attr_reader :host | ||
|   # Maximum number of connections to accept at at ime, as a FixNum | ||
|   attr_reader :maxConnections | ||
|   # IO Device on which log messages should be written | ||
|   attr_accessor :stdlog | ||
|   # Set to true to cause the callbacks #connecting, #disconnecting, #starting, and #stopping to | ||
|   # be called during the server's lifecycle | ||
|   attr_accessor :audit | ||
|   # Set to true to show more detailed logging | ||
|   attr_accessor :debug | ||
|   # Called when a client connects, if auditing is enabled. | ||
|   # | ||
|   # +client+:: a TCPSocket instances representing the client that connected | ||
|   # | ||
|   # Return true to allow this client to connect, false to prevent it. | ||
|   def connecting(client) | ||
|     addr = client.peeraddr | ||
|     log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " + | ||
| ... | ... | |
|     true | ||
|   end | ||
|   # Called when a client disconnects, if audition is enabled. | ||
|   # | ||
|   # +clientPort+:: the port of the client that is connecting | ||
|   def disconnecting(clientPort) | ||
|     log("#{self.class.to_s} #{@host}:#{@port} " + | ||
|       "client:#{clientPort} disconnect") | ||
| ... | ... | |
|   protected :connecting, :disconnecting | ||
|   # Called when the server is starting up, if auditing is enabled. | ||
|   def starting() | ||
|     log("#{self.class.to_s} #{@host}:#{@port} start") | ||
|   end | ||
|   # Called when the server is shutting down, if auditing is enabled. | ||
|   def stopping() | ||
|     log("#{self.class.to_s} #{@host}:#{@port} stop") | ||
|   end | ||
|   protected :starting, :stopping | ||
|   # Called if #debug is true whenever an unhandled exception is raised. | ||
|   # This implementation simply logs the backtrace. | ||
|   # | ||
|   # +detail+:: The Exception that was caught | ||
|   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 | ||
|   def log(msg) | ||
|     if @stdlog | ||
|       @stdlog.puts("[#{Time.new.ctime}] %s" % msg) | ||
| ... | ... | |
|   protected :error, :log | ||
|   # 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 | ||
|   def initialize(port, host = DEFAULT_HOST, maxConnections = 4, | ||
|     stdlog = $stderr, audit = false, debug = false) | ||
|     @tcpServerThread = nil | ||
| ... | ... | |
|     @debug = debug | ||
|   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. | ||
|   def start(maxConnections = -1) | ||
|     raise "running" if !stopped? | ||
|     raise "server is already running" if !stopped? | ||
|     @shutdown = false | ||
|     @maxConnections = maxConnections if maxConnections > 0 | ||
|     @@servicesMutex.synchronize  { | ||