Class | GServer |
In: |
lib/gserver.rb
|
Parent: | 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.
connecting disconnecting starting stopping
The above methods are only called if auditing is enabled.
You can also override log and error if, for example, you wish to use a more sophisticated logging system.
DEFAULT_HOST | = | "127.0.0.1" |
audit | [RW] | |
debug | [RW] | |
host | [R] | |
maxConnections | [R] | |
port | [R] | |
stdlog | [RW] |
# File lib/gserver.rb, line 102 102: def GServer.in_service?(port, host = DEFAULT_HOST) 103: @@services.has_key?(host) and 104: @@services[host].has_key?(port) 105: end
# File lib/gserver.rb, line 171 171: def initialize(port, host = DEFAULT_HOST, maxConnections = 4, 172: stdlog = $stderr, audit = false, debug = false) 173: @tcpServerThread = nil 174: @port = port 175: @host = host 176: @maxConnections = maxConnections 177: @connections = [] 178: @connectionsMutex = Mutex.new 179: @connectionsCV = ConditionVariable.new 180: @stdlog = stdlog 181: @audit = audit 182: @debug = debug 183: end
# File lib/gserver.rb, line 96 96: def GServer.stop(port, host = DEFAULT_HOST) 97: @@servicesMutex.synchronize { 98: @@services[host][port].stop 99: } 100: end
# File lib/gserver.rb, line 127 127: def join 128: @tcpServerThread.join if @tcpServerThread 129: end
# File lib/gserver.rb, line 185 185: def start(maxConnections = -1) 186: raise "running" if !stopped? 187: @shutdown = false 188: @maxConnections = maxConnections if maxConnections > 0 189: @@servicesMutex.synchronize { 190: if GServer.in_service?(@port,@host) 191: raise "Port already in use: #{host}:#{@port}!" 192: end 193: @tcpServer = TCPServer.new(@host,@port) 194: @port = @tcpServer.addr[1] 195: @@services[@host] = {} unless @@services.has_key?(@host) 196: @@services[@host][@port] = self; 197: } 198: @tcpServerThread = Thread.new { 199: begin 200: starting if @audit 201: while !@shutdown 202: @connectionsMutex.synchronize { 203: while @connections.size >= @maxConnections 204: @connectionsCV.wait(@connectionsMutex) 205: end 206: } 207: client = @tcpServer.accept 208: @connections << Thread.new(client) { |myClient| 209: begin 210: myPort = myClient.peeraddr[1] 211: serve(myClient) if !@audit or connecting(myClient) 212: rescue => detail 213: error(detail) if @debug 214: ensure 215: begin 216: myClient.close 217: rescue 218: end 219: @connectionsMutex.synchronize { 220: @connections.delete(Thread.current) 221: @connectionsCV.signal 222: } 223: disconnecting(myPort) if @audit 224: end 225: } 226: end 227: rescue => detail 228: error(detail) if @debug 229: ensure 230: begin 231: @tcpServer.close 232: rescue 233: end 234: if @shutdown 235: @connectionsMutex.synchronize { 236: while @connections.size > 0 237: @connectionsCV.wait(@connectionsMutex) 238: end 239: } 240: else 241: @connections.each { |c| c.raise "stop" } 242: end 243: @tcpServerThread = nil 244: @@servicesMutex.synchronize { 245: @@services[@host].delete(@port) 246: } 247: stopping if @audit 248: end 249: } 250: self 251: end
# File lib/gserver.rb, line 107 107: def stop 108: @connectionsMutex.synchronize { 109: if @tcpServerThread 110: @tcpServerThread.raise "stop" 111: end 112: } 113: end
# File lib/gserver.rb, line 134 134: def connecting(client) 135: addr = client.peeraddr 136: log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " + 137: "#{addr[2]}<#{addr[3]}> connect") 138: true 139: end
# File lib/gserver.rb, line 141 141: def disconnecting(clientPort) 142: log("#{self.class.to_s} #{@host}:#{@port} " + 143: "client:#{clientPort} disconnect") 144: end
# File lib/gserver.rb, line 158 158: def error(detail) 159: log(detail.backtrace.join("\n")) 160: end
# File lib/gserver.rb, line 162 162: def log(msg) 163: if @stdlog 164: @stdlog.puts("[#{Time.new.ctime}] %s" % msg) 165: @stdlog.flush 166: end 167: end
# File lib/gserver.rb, line 148 148: def starting() 149: log("#{self.class.to_s} #{@host}:#{@port} start") 150: end