Class: GServer
- Inherits:
-
Object
- Object
- GServer
- Defined in:
- lib/gserver.rb
Overview
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_s)
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.
-
#connecting
-
#disconnecting
-
#starting
-
#stopping
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.
Constant Summary collapse
- DEFAULT_HOST =
"127.0.0.1"
- @@services =
Hash of opened ports, i.e. services
{}
- @@servicesMutex =
Mutex.new
Instance Attribute Summary collapse
-
#audit ⇒ Object
Set to true to cause the callbacks #connecting, #disconnecting, #starting, and #stopping to be called during the server's lifecycle.
-
#debug ⇒ Object
Set to true to show more detailed logging.
-
#host ⇒ Object
readonly
Host on which to bind, as a String.
-
#maxConnections ⇒ Object
readonly
Maximum number of connections to accept at at ime, as a FixNum.
-
#port ⇒ Object
readonly
Port on which to listen, as a FixNum.
-
#stdlog ⇒ Object
IO Device on which log messages should be written.
Class Method Summary collapse
-
.in_service?(port, host = DEFAULT_HOST) ⇒ Boolean
Check if a server is running on the given port and host.
-
.stop(port, host = DEFAULT_HOST) ⇒ Object
Stop the server running on the given port, bound to the given host.
Instance Method Summary collapse
-
#connections ⇒ Object
Return the current number of connected clients.
-
#initialize(port, host = DEFAULT_HOST, maxConnections = 4, stdlog = $stderr, audit = false, debug = false) ⇒ GServer
constructor
Create a new server.
-
#join ⇒ Object
Join with the server thread.
- #serve(io) ⇒ Object
-
#shutdown ⇒ Object
Schedule a shutdown for the server.
-
#start(maxConnections = -1)) ⇒ Object
Start the server if it isn't already running.
-
#stop ⇒ Object
Stop the server.
-
#stopped? ⇒ Boolean
Returns true if the server has stopped.
Constructor Details
#initialize(port, host = DEFAULT_HOST, maxConnections = 4, stdlog = $stderr, audit = false, debug = false) ⇒ GServer
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
222 223 224 225 226 227 228 229 230 231 232 233 234 |
# 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 |
Instance Attribute Details
#audit ⇒ Object
Set to true to cause the callbacks #connecting, #disconnecting, #starting, and #stopping to be called during the server's lifecycle
153 154 155 |
# File 'lib/gserver.rb', line 153 def audit @audit end |
#debug ⇒ Object
Set to true to show more detailed logging
155 156 157 |
# File 'lib/gserver.rb', line 155 def debug @debug end |
#host ⇒ Object (readonly)
Host on which to bind, as a String
146 147 148 |
# File 'lib/gserver.rb', line 146 def host @host end |
#maxConnections ⇒ Object (readonly)
Maximum number of connections to accept at at ime, as a FixNum
148 149 150 |
# File 'lib/gserver.rb', line 148 def maxConnections @maxConnections end |
#port ⇒ Object (readonly)
Port on which to listen, as a FixNum
144 145 146 |
# File 'lib/gserver.rb', line 144 def port @port end |
#stdlog ⇒ Object
IO Device on which log messages should be written
150 151 152 |
# File 'lib/gserver.rb', line 150 def stdlog @stdlog end |
Class Method Details
.in_service?(port, host = DEFAULT_HOST) ⇒ Boolean
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.
109 110 111 112 |
# 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 |
.stop(port, host = DEFAULT_HOST) ⇒ Object
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
97 98 99 100 101 |
# File 'lib/gserver.rb', line 97 def GServer.stop(port, host = DEFAULT_HOST) @@servicesMutex.synchronize { @@services[host][port].stop } end |
Instance Method Details
#connections ⇒ Object
Return the current number of connected clients
134 135 136 |
# File 'lib/gserver.rb', line 134 def connections @connections.size end |
#join ⇒ Object
Join with the server thread
139 140 141 |
# File 'lib/gserver.rb', line 139 def join @tcpServerThread.join if @tcpServerThread end |
#serve(io) ⇒ Object
87 88 |
# File 'lib/gserver.rb', line 87 def serve(io) end |
#shutdown ⇒ Object
Schedule a shutdown for the server
129 130 131 |
# File 'lib/gserver.rb', line 129 def shutdown @shutdown = true end |
#start(maxConnections = -1)) ⇒ Object
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.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# 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 @connections << Thread.new(client) { |myClient| 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 ⇒ Object
Stop the server
115 116 117 118 119 120 121 |
# File 'lib/gserver.rb', line 115 def stop @connectionsMutex.synchronize { if @tcpServerThread @tcpServerThread.raise "stop" end } end |
#stopped? ⇒ Boolean
Returns true if the server has stopped.
124 125 126 |
# File 'lib/gserver.rb', line 124 def stopped? @tcpServerThread == nil end |