2 # Parallel Python Software: http://www.parallelpython.com
3 # Copyright (c) 2005-2009, Vitalii Vanovschi
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are met:
7 # * Redistributions of source code must retain the above copyright notice,
8 # this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above copyright
10 # notice, this list of conditions and the following disclaimer in the
11 # documentation and/or other materials provided with the distribution.
12 # * Neither the name of the author nor the names of its contributors
13 # may be used to endorse or promote products derived from this software
14 # without specific prior written permission.
16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26 # THE POSSIBILITY OF SUCH DAMAGE.
28 Parallel Python Software, Network Server
30 http://www.parallelpython.com - updates, documentation, examples and support
49 copyright
= "Copyright (c) 2005-2009 Vitalii Vanovschi. All rights reserved"
52 # compartibility with Python 2.6
55 sha_new
= hashlib
.sha1
61 class _NetworkServer(Server
):
62 """Network Server Class
65 def __init__(self
, ncpus
="autodetect", interface
="0.0.0.0",
66 broadcast
="255.255.255.255", port
=None, secret
=None,
67 timeout
=None, loglevel
=logging
.WARNING
, restart
=False,
69 Server
.__init
__(self
, ncpus
, secret
=secret
, loglevel
=loglevel
,
70 restart
=restart
, proto
=proto
)
72 self
.bcast
= broadcast
76 self
.port
= self
.default_port
77 self
.timeout
= timeout
79 self
.last_con_time
= time
.time()
80 self
.ncon_lock
= thread
.allocate_lock()
82 logging
.debug("Strarting network server interface=%s port=%i"
83 % (self
.host
, self
.port
))
84 if self
.timeout
is not None:
85 logging
.debug("ppserver will exit in %i seconds if no "\
86 "connections with clients exist" % (self
.timeout
))
87 thread
.start_new_thread(self
.check_timeout
, ())
89 def ncon_add(self
, val
):
90 """Keeps track of the number of connections and time of the last one"""
91 self
.ncon_lock
.acquire()
93 self
.last_con_time
= time
.time()
94 self
.ncon_lock
.release()
96 def check_timeout(self
):
97 """Checks if timeout happened and shutdowns server if it did"""
100 idle_time
= time
.time() - self
.last_con_time
101 if idle_time
< self
.timeout
:
102 time
.sleep(self
.timeout
- idle_time
)
104 logging
.debug("exiting ppserver due to timeout (no client"\
105 " connections in last %i sec)", self
.timeout
)
108 time
.sleep(self
.timeout
)
111 """Initiates listenting to incoming connections"""
113 ssocket
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
114 # following allows ppserver to restart faster on the same port
115 ssocket
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
116 ssocket
.bind((self
.host
, self
.port
))
119 logging
.error("Cannot create socket with port " + str(self
.port
)
120 + " (port is already in use)")
124 #accept connections from outside
125 (csocket
, address
) = ssocket
.accept()
126 #now do something with the clientsocket
127 #in this case, we'll pretend this is a threaded server
128 thread
.start_new_thread(self
.crun
, (csocket
, ))
130 logging
.debug("Closing server socket")
133 def crun(self
, csocket
):
134 """Authenticates client and handles its jobs"""
135 mysocket
= pptransport
.CSocketTransport(csocket
)
137 mysocket
.send(version
)
138 #generate a random string
139 srandom
= "".join([random
.choice(string
.ascii_letters
)
140 for i
in xrange(16)])
141 mysocket
.send(srandom
)
142 answer
= sha_new(srandom
+self
.secret
).hexdigest()
143 cleintanswer
= mysocket
.receive()
144 if answer
!= cleintanswer
:
145 logging
.warning("Authentification failed, client host=%s, port=%i"
146 % csocket
.getpeername())
147 mysocket
.send("FAILED")
153 ctype
= mysocket
.receive()
154 logging
.debug("Control message received: " + ctype
)
158 #reset time at each new connection
159 self
.get_stats()["local"].time
= 0.0
160 mysocket
.send(str(self
.get_ncpus()))
163 mysocket
.send(str(self
.get_stats()["local"].time
))
166 sfunc
= mysocket
.creceive()
167 sargs
= mysocket
.receive()
168 fun
= self
.insert(sfunc
, sargs
)
170 mysocket
.send(sresult
)
172 #print sys.excepthook(*sys.exc_info())
173 logging
.debug("Closing client socket")
178 """Initiaates auto-discovery mechanism"""
179 discover
= ppauto
.Discover(self
)
180 thread
.start_new_thread(discover
.run
,
181 ((self
.host
, self
.port
),
182 (self
.bcast
, self
.port
)),
186 def parse_config(file_loc
):
188 Parses a config file in a very forgiving way.
190 # If we don't have configobj installed then let the user know and exit
192 from configobj
import ConfigObj
193 except ImportError, ie
:
194 print >> sys
.stderr
, "ERROR: You must have configobj installed to use \
195 configuration files. You can still use command line switches."
198 if not os
.access(file_loc
, os
.F_OK
):
199 print >> sys
.stderr
, "ERROR: Can not access %s." % arg
202 # Load the configuration file
203 config
= ConfigObj(file_loc
)
204 # try each config item and use the result if it exists. If it doesn't
205 # then simply pass and move along
207 args
['secret'] = config
['general'].get('secret')
212 autodiscovery
= config
['network'].as_bool('autodiscovery')
217 args
['interface'] = config
['network'].get('interface',
223 args
['broadcast'] = config
['network'].get('broadcast')
228 args
['port'] = config
['network'].as_int('port')
233 args
['loglevel'] = config
['general'].as_bool('debug')
238 args
['ncpus'] = config
['general'].as_int('workers')
243 args
['proto'] = config
['general'].as_int('proto')
248 args
['restart'] = config
['general'].as_bool('restart')
253 args
['timeout'] = config
['network'].as_int('timeout')
256 # Return a tuple of the args dict and autodiscovery variable
257 return args
, autodiscovery
262 print "Parallel Python Network Server (pp-" + version
+ ")"
263 print "Usage: ppserver.py [-hdar] [-n proto] [-c config_path]"\
264 " [-i interface] [-b broadcast] [-p port] [-w nworkers]"\
265 " [-s secret] [-t seconds]"
268 print "-h : this help message"
270 print "-a : enable auto-discovery service"
271 print "-r : restart worker process after each"\
273 print "-n proto : protocol number for pickle module"
274 print "-c path : path to config file"
275 print "-i interface : interface to listen"
276 print "-b broadcast : broadcast address for auto-discovery service"
277 print "-p port : port to listen"
278 print "-w nworkers : number of workers to start"
279 print "-s secret : secret for authentication"
280 print "-t seconds : timeout to exit if no connections with "\
283 print "Due to the security concerns always use a non-trivial secret key."
284 print "Secret key set by -s switch will override secret key assigned by"
285 print "pp_secret variable in .pythonrc.py"
287 print "Please visit http://www.parallelpython.com for extended up-to-date"
288 print "documentation, examples and support forums"
291 if __name__
== "__main__":
293 opts
, args
= getopt
.getopt(sys
.argv
[1:],
294 "hdarn:c:b:i:p:w:s:t:", ["help"])
295 except getopt
.GetoptError
:
300 autodiscovery
= False
302 for opt
, arg
in opts
:
303 if opt
in ("-h", "--help"):
307 args
, autodiscovery
= parse_config(arg
)
309 args
["loglevel"] = logging
.DEBUG
311 args
["interface"] = arg
315 args
["port"] = int(arg
)
317 args
["ncpus"] = int(arg
)
321 args
["restart"] = True
323 args
["broadcast"] = arg
325 args
["proto"] = int(arg
)
327 args
["timeout"] = int(arg
)
329 server
= _NetworkServer(**args
)
333 #have to destroy it here explicitelly otherwise an exception
334 #comes out in Python 2.4
337 # Parallel Python Software: http://www.parallelpython.com