source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/gserver.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 6.4 KB
Line 
1#
2# Copyright (C) 2001 John W. Small All Rights Reserved
3#
4# Author:: John W. Small
5# Documentation:: Gavin Sinclair
6# Licence:: Freeware.
7#
8# See the class GServer for documentation.
9#
10
11require "socket"
12require "thread"
13
14#
15# GServer implements a generic server, featuring thread pool management,
16# simple logging, and multi-server management. See HttpServer in
17# <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for an example of
18# GServer in action.
19#
20# Any kind of application-level server can be implemented using this class.
21# It accepts multiple simultaneous connections from clients, up to an optional
22# maximum number. Several _services_ (i.e. one service per TCP port) can be
23# run simultaneously, and stopped at any time through the class method
24# <tt>GServer.stop(port)</tt>. All the threading issues are handled, saving
25# you the effort. All events are optionally logged, but you can provide your
26# own event handlers if you wish.
27#
28# === Example
29#
30# Using GServer is simple. Below we implement a simple time server, run it,
31# query it, and shut it down. Try this code in +irb+:
32#
33# require 'gserver'
34#
35# #
36# # A server that returns the time in seconds since 1970.
37# #
38# class TimeServer < GServer
39# def initialize(port=10001, *args)
40# super(port, *args)
41# end
42# def serve(io)
43# io.puts(Time.now.to_i)
44# end
45# end
46#
47# # Run the server with logging enabled (it's a separate thread).
48# server = TimeServer.new
49# server.audit = true # Turn logging on.
50# server.start
51#
52# # *** Now point your browser to http://localhost:10001 to see it working ***
53#
54# # See if it's still running.
55# GServer.in_service?(10001) # -> true
56# server.stopped? # -> false
57#
58# # Shut the server down gracefully.
59# server.shutdown
60#
61# # Alternatively, stop it immediately.
62# GServer.stop(10001)
63# # or, of course, "server.stop".
64#
65# All the business of accepting connections and exception handling is taken
66# care of. All we have to do is implement the method that actually serves the
67# client.
68#
69# === Advanced
70#
71# As the example above shows, the way to use GServer is to subclass it to
72# create a specific server, overriding the +serve+ method. You can override
73# other methods as well if you wish, perhaps to collect statistics, or emit
74# more detailed logging.
75#
76# connecting
77# disconnecting
78# starting
79# stopping
80#
81# The above methods are only called if auditing is enabled.
82#
83# You can also override +log+ and +error+ if, for example, you wish to use a
84# more sophisticated logging system.
85#
86class GServer
87
88 DEFAULT_HOST = "127.0.0.1"
89
90 def serve(io)
91 end
92
93 @@services = {} # Hash of opened ports, i.e. services
94 @@servicesMutex = Mutex.new
95
96 def GServer.stop(port, host = DEFAULT_HOST)
97 @@servicesMutex.synchronize {
98 @@services[host][port].stop
99 }
100 end
101
102 def GServer.in_service?(port, host = DEFAULT_HOST)
103 @@services.has_key?(host) and
104 @@services[host].has_key?(port)
105 end
106
107 def stop
108 @connectionsMutex.synchronize {
109 if @tcpServerThread
110 @tcpServerThread.raise "stop"
111 end
112 }
113 end
114
115 def stopped?
116 @tcpServerThread == nil
117 end
118
119 def shutdown
120 @shutdown = true
121 end
122
123 def connections
124 @connections.size
125 end
126
127 def join
128 @tcpServerThread.join if @tcpServerThread
129 end
130
131 attr_reader :port, :host, :maxConnections
132 attr_accessor :stdlog, :audit, :debug
133
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
140
141 def disconnecting(clientPort)
142 log("#{self.class.to_s} #{@host}:#{@port} " +
143 "client:#{clientPort} disconnect")
144 end
145
146 protected :connecting, :disconnecting
147
148 def starting()
149 log("#{self.class.to_s} #{@host}:#{@port} start")
150 end
151
152 def stopping()
153 log("#{self.class.to_s} #{@host}:#{@port} stop")
154 end
155
156 protected :starting, :stopping
157
158 def error(detail)
159 log(detail.backtrace.join("\n"))
160 end
161
162 def log(msg)
163 if @stdlog
164 @stdlog.puts("[#{Time.new.ctime}] %s" % msg)
165 @stdlog.flush
166 end
167 end
168
169 protected :error, :log
170
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
184
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
252
253end
Note: See TracBrowser for help on using the repository browser.