blob: b50643ada8c8509b315a21bc5b62487181939745 [file] [log] [blame]
Kurt B. Kaiserb4179362002-07-26 00:06:42 +00001"""RPC Implemention, originally written for the Python Idle IDE
2
3For security reasons, GvR requested that Idle's Python execution server process
4connect to the Idle process, which listens for the connection. Since Idle has
5has only one client per server, this was not a limitation.
6
7 +---------------------------------+ +-------------+
8 | SocketServer.BaseRequestHandler | | SocketIO |
9 +---------------------------------+ +-------------+
10 ^ | register() |
11 | | unregister()|
12 | +-------------+
13 | ^ ^
14 | | |
15 | + -------------------+ |
16 | | |
17 +-------------------------+ +-----------------+
18 | RPCHandler | | RPCClient |
19 | [attribute of RPCServer]| | |
20 +-------------------------+ +-----------------+
21
22The RPCServer handler class is expected to provide register/unregister methods.
23RPCHandler inherits the mix-in class SocketIO, which provides these methods.
24
25See the Idle run.main() docstring for further information on how this was
26accomplished in Idle.
27
28"""
29
30import sys
Chui Tey5d2af632002-05-26 13:36:41 +000031import socket
32import select
33import SocketServer
34import struct
35import cPickle as pickle
36import threading
37import traceback
38import copy_reg
39import types
40import marshal
41
42def unpickle_code(ms):
43 co = marshal.loads(ms)
44 assert isinstance(co, types.CodeType)
45 return co
46
47def pickle_code(co):
48 assert isinstance(co, types.CodeType)
49 ms = marshal.dumps(co)
50 return unpickle_code, (ms,)
51
Kurt B. Kaiseradc63842002-08-25 14:08:07 +000052# XXX KBK 24Aug02 function pickling capability not used in Idle
53# def unpickle_function(ms):
54# return ms
Chui Tey5d2af632002-05-26 13:36:41 +000055
Kurt B. Kaiseradc63842002-08-25 14:08:07 +000056# def pickle_function(fn):
57# assert isinstance(fn, type.FunctionType)
58# return `fn`
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000059
Chui Tey5d2af632002-05-26 13:36:41 +000060copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
Kurt B. Kaiseradc63842002-08-25 14:08:07 +000061# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
Chui Tey5d2af632002-05-26 13:36:41 +000062
63BUFSIZE = 8*1024
64
65class RPCServer(SocketServer.TCPServer):
66
67 def __init__(self, addr, handlerclass=None):
68 if handlerclass is None:
69 handlerclass = RPCHandler
Chui Tey5d2af632002-05-26 13:36:41 +000070 SocketServer.TCPServer.__init__(self, addr, handlerclass)
71
Kurt B. Kaiserb4179362002-07-26 00:06:42 +000072 def server_bind(self):
73 "Override TCPServer method, no bind() phase for connecting entity"
74 pass
Chui Tey5d2af632002-05-26 13:36:41 +000075
Kurt B. Kaiserb4179362002-07-26 00:06:42 +000076 def server_activate(self):
77 """Override TCPServer method, connect() instead of listen()
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000078
Kurt B. Kaiserb4179362002-07-26 00:06:42 +000079 Due to the reversed connection, self.server_address is actually the
80 address of the Idle Client to which we are connecting.
Chui Tey5d2af632002-05-26 13:36:41 +000081
Kurt B. Kaiserb4179362002-07-26 00:06:42 +000082 """
83 self.socket.connect(self.server_address)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +000084
Kurt B. Kaiserb4179362002-07-26 00:06:42 +000085 def get_request(self):
86 "Override TCPServer method, return already connected socket"
87 return self.socket, self.server_address
Chui Tey5d2af632002-05-26 13:36:41 +000088
89objecttable = {}
90
91class SocketIO:
92
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +000093 nextseq = 0
94
Chui Tey5d2af632002-05-26 13:36:41 +000095 def __init__(self, sock, objtable=None, debugging=None):
96 self.mainthread = threading.currentThread()
97 if debugging is not None:
98 self.debugging = debugging
99 self.sock = sock
100 if objtable is None:
101 objtable = objecttable
102 self.objtable = objtable
103 self.statelock = threading.Lock()
104 self.responses = {}
105 self.cvars = {}
106
107 def close(self):
108 sock = self.sock
109 self.sock = None
110 if sock is not None:
111 sock.close()
112
113 def debug(self, *args):
114 if not self.debugging:
115 return
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000116 s = self.location + " " + str(threading.currentThread().getName())
Chui Tey5d2af632002-05-26 13:36:41 +0000117 for a in args:
118 s = s + " " + str(a)
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000119 print>>sys.__stderr__, s
Chui Tey5d2af632002-05-26 13:36:41 +0000120
121 def register(self, oid, object):
122 self.objtable[oid] = object
123
124 def unregister(self, oid):
125 try:
126 del self.objtable[oid]
127 except KeyError:
128 pass
129
130 def localcall(self, request):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000131 self.debug("localcall:", request)
Chui Tey5d2af632002-05-26 13:36:41 +0000132 try:
133 how, (oid, methodname, args, kwargs) = request
134 except TypeError:
135 return ("ERROR", "Bad request format")
136 assert how == "call"
137 if not self.objtable.has_key(oid):
138 return ("ERROR", "Unknown object id: %s" % `oid`)
139 obj = self.objtable[oid]
140 if methodname == "__methods__":
141 methods = {}
142 _getmethods(obj, methods)
143 return ("OK", methods)
144 if methodname == "__attributes__":
145 attributes = {}
146 _getattributes(obj, attributes)
147 return ("OK", attributes)
148 if not hasattr(obj, methodname):
149 return ("ERROR", "Unsupported method name: %s" % `methodname`)
150 method = getattr(obj, methodname)
151 try:
152 ret = method(*args, **kwargs)
153 if isinstance(ret, RemoteObject):
154 ret = remoteref(ret)
155 return ("OK", ret)
156 except:
Kurt B. Kaiser8cd0def2003-01-31 05:06:43 +0000157 self.debug("localcall:EXCEPTION")
158 efile = sys.stderr
Chui Tey5d2af632002-05-26 13:36:41 +0000159 typ, val, tb = info = sys.exc_info()
160 sys.last_type, sys.last_value, sys.last_traceback = info
Kurt B. Kaiser8cd0def2003-01-31 05:06:43 +0000161 tbe = traceback.extract_tb(tb)
162 print >>efile, 'Traceback (most recent call last):'
163 exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
164 self.cleanup_traceback(tbe, exclude)
165 traceback.print_list(tbe, file=efile)
166 lines = traceback.format_exception_only(typ, val)
167 for line in lines:
168 print>>efile, line,
169 return ("EXCEPTION", None)
170
171 def cleanup_traceback(self, tb, exclude):
172 "Remove excluded traces from beginning/end of tb; get cached lines"
173 orig_tb = tb[:]
174 while tb:
175 for rpcfile in exclude:
176 if tb[0][0].count(rpcfile):
177 break # found an exclude, break for: and delete tb[0]
Chui Tey5d2af632002-05-26 13:36:41 +0000178 else:
Kurt B. Kaiser8cd0def2003-01-31 05:06:43 +0000179 break # no excludes, have left RPC code, break while:
180 del tb[0]
181 while tb:
182 for rpcfile in exclude:
183 if tb[-1][0].count(rpcfile):
184 break
185 else:
186 break
187 del tb[-1]
188 if len(tb) == 0:
189 # error was in RPC internals, don't prune!
190 tb[:] = orig_tb[:]
191 print>>sys.stderr, "** RPC Internal Error: ", tb
192 for i in range(len(tb)):
193 fn, ln, nm, line = tb[i]
194 if nm == '?':
195 nm = "-toplevel-"
196 if not line and fn.startswith("<pyshell#"):
197 line = self.remotecall('linecache', 'getline',
198 (fn, ln), {})
199 tb[i] = fn, ln, nm, line
Chui Tey5d2af632002-05-26 13:36:41 +0000200
201 def remotecall(self, oid, methodname, args, kwargs):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000202 self.debug("calling asynccall via remotecall")
Chui Tey5d2af632002-05-26 13:36:41 +0000203 seq = self.asynccall(oid, methodname, args, kwargs)
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000204 return self.asyncreturn(seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000205
206 def asynccall(self, oid, methodname, args, kwargs):
207 request = ("call", (oid, methodname, args, kwargs))
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000208 seq = self.newseq()
209 self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
210 self.putmessage((seq, request))
Chui Tey5d2af632002-05-26 13:36:41 +0000211 return seq
212
213 def asyncreturn(self, seq):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000214 self.debug("asyncreturn:%d:call getresponse(): " % seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000215 response = self.getresponse(seq)
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000216 self.debug(("asyncreturn:%d:response: " % seq), response)
Chui Tey5d2af632002-05-26 13:36:41 +0000217 return self.decoderesponse(response)
218
219 def decoderesponse(self, response):
220 how, what = response
221 if how == "OK":
222 return what
223 if how == "EXCEPTION":
Kurt B. Kaiser8cd0def2003-01-31 05:06:43 +0000224 raise Exception, "RPC SocketIO.decoderesponse exception"
Chui Tey5d2af632002-05-26 13:36:41 +0000225 if how == "ERROR":
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000226 self.debug("decoderesponse: Internal ERROR:", what)
Chui Tey5d2af632002-05-26 13:36:41 +0000227 raise RuntimeError, what
228 raise SystemError, (how, what)
229
230 def mainloop(self):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000231 """Listen on socket until I/O not ready or EOF
232
233 pollpacket() will loop looking for seq number None, which never
234 comes. The loop will exit when self.ioready() returns 0.
235
236 """
Chui Tey5d2af632002-05-26 13:36:41 +0000237 try:
238 self.getresponse(None)
239 except EOFError:
240 pass
241
242 def getresponse(self, myseq):
243 response = self._getresponse(myseq)
244 if response is not None:
245 how, what = response
246 if how == "OK":
247 response = how, self._proxify(what)
248 return response
249
250 def _proxify(self, obj):
251 if isinstance(obj, RemoteProxy):
252 return RPCProxy(self, obj.oid)
253 if isinstance(obj, types.ListType):
254 return map(self._proxify, obj)
255 # XXX Check for other types -- not currently needed
256 return obj
257
258 def _getresponse(self, myseq):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000259 self.debug("_getresponse:myseq:", myseq)
Chui Tey5d2af632002-05-26 13:36:41 +0000260 if threading.currentThread() is self.mainthread:
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000261 # Main thread: does all reading of requests or responses
262 # Loop here until there is message traffic on the socket
Chui Tey5d2af632002-05-26 13:36:41 +0000263 while 1:
264 response = self.pollresponse(myseq, None)
265 if response is not None:
266 return response
267 else:
268 # Auxiliary thread: wait for notification from main thread
269 cvar = threading.Condition(self.statelock)
270 self.statelock.acquire()
271 self.cvars[myseq] = cvar
272 while not self.responses.has_key(myseq):
273 cvar.wait()
274 response = self.responses[myseq]
275 del self.responses[myseq]
276 del self.cvars[myseq]
277 self.statelock.release()
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000278 return response # might be None
Chui Tey5d2af632002-05-26 13:36:41 +0000279
280 def newseq(self):
281 self.nextseq = seq = self.nextseq + 2
282 return seq
283
284 def putmessage(self, message):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000285 self.debug("putmessage:%d:" % message[0])
Chui Tey5d2af632002-05-26 13:36:41 +0000286 try:
287 s = pickle.dumps(message)
288 except:
289 print >>sys.__stderr__, "Cannot pickle:", `message`
290 raise
291 s = struct.pack("<i", len(s)) + s
292 while len(s) > 0:
293 n = self.sock.send(s)
294 s = s[n:]
295
296 def ioready(self, wait=0.0):
297 r, w, x = select.select([self.sock.fileno()], [], [], wait)
298 return len(r)
299
300 buffer = ""
301 bufneed = 4
302 bufstate = 0 # meaning: 0 => reading count; 1 => reading data
303
304 def pollpacket(self, wait=0.0):
305 self._stage0()
306 if len(self.buffer) < self.bufneed:
307 if not self.ioready(wait):
308 return None
309 try:
310 s = self.sock.recv(BUFSIZE)
311 except socket.error:
312 raise EOFError
313 if len(s) == 0:
314 raise EOFError
315 self.buffer += s
316 self._stage0()
317 return self._stage1()
318
319 def _stage0(self):
320 if self.bufstate == 0 and len(self.buffer) >= 4:
321 s = self.buffer[:4]
322 self.buffer = self.buffer[4:]
323 self.bufneed = struct.unpack("<i", s)[0]
324 self.bufstate = 1
325
326 def _stage1(self):
327 if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
328 packet = self.buffer[:self.bufneed]
329 self.buffer = self.buffer[self.bufneed:]
330 self.bufneed = 4
331 self.bufstate = 0
332 return packet
333
334 def pollmessage(self, wait=0.0):
335 packet = self.pollpacket(wait)
336 if packet is None:
337 return None
338 try:
339 message = pickle.loads(packet)
340 except:
341 print >>sys.__stderr__, "-----------------------"
342 print >>sys.__stderr__, "cannot unpickle packet:", `packet`
343 traceback.print_stack(file=sys.__stderr__)
344 print >>sys.__stderr__, "-----------------------"
345 raise
346 return message
347
348 def pollresponse(self, myseq, wait=0.0):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000349 """Handle messages received on the socket.
350
351 Some messages received may be asynchronous 'call' commands, and
352 some may be responses intended for other threads.
353
354 Loop until message with myseq sequence number is received. Save others
355 in self.responses and notify the owning thread, except that 'call'
356 commands are handed off to localcall() and the response sent back
357 across the link with the appropriate sequence number.
358
359 """
Chui Tey5d2af632002-05-26 13:36:41 +0000360 while 1:
361 message = self.pollmessage(wait)
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000362 if message is None: # socket not ready
Chui Tey5d2af632002-05-26 13:36:41 +0000363 return None
364 wait = 0.0
365 seq, resq = message
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000366 self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
Chui Tey5d2af632002-05-26 13:36:41 +0000367 if resq[0] == "call":
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000368 self.debug("pollresponse:%d:localcall:call:" % seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000369 response = self.localcall(resq)
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000370 self.debug("pollresponse:%d:localcall:response:%s"
371 % (seq, response))
Chui Tey5d2af632002-05-26 13:36:41 +0000372 self.putmessage((seq, response))
373 continue
374 elif seq == myseq:
375 return resq
376 else:
377 self.statelock.acquire()
378 self.responses[seq] = resq
379 cv = self.cvars.get(seq)
380 if cv is not None:
381 cv.notify()
382 self.statelock.release()
383 continue
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000384
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000385#----------------- end class SocketIO --------------------
Chui Tey5d2af632002-05-26 13:36:41 +0000386
387class RemoteObject:
388 # Token mix-in class
389 pass
390
391def remoteref(obj):
392 oid = id(obj)
393 objecttable[oid] = obj
394 return RemoteProxy(oid)
395
396class RemoteProxy:
397
398 def __init__(self, oid):
399 self.oid = oid
400
401class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
402
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000403 debugging = False
404 location = "#S" # Server
Chui Tey5d2af632002-05-26 13:36:41 +0000405
406 def __init__(self, sock, addr, svr):
407 svr.current_handler = self ## cgt xxx
408 SocketIO.__init__(self, sock)
409 SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
410
Chui Tey5d2af632002-05-26 13:36:41 +0000411 def handle(self):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000412 "handle() method required by SocketServer"
Chui Tey5d2af632002-05-26 13:36:41 +0000413 self.mainloop()
414
415 def get_remote_proxy(self, oid):
416 return RPCProxy(self, oid)
417
418class RPCClient(SocketIO):
419
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000420 debugging = False
421 location = "#C" # Client
422
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000423 nextseq = 1 # Requests coming from the client are odd numbered
Chui Tey5d2af632002-05-26 13:36:41 +0000424
425 def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000426 self.listening_sock = socket.socket(family, type)
427 self.listening_sock.setsockopt(socket.SOL_SOCKET,
428 socket.SO_REUSEADDR, 1)
429 self.listening_sock.bind(address)
430 self.listening_sock.listen(1)
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000431
432 def accept(self):
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000433 working_sock, address = self.listening_sock.accept()
Kurt B. Kaiser74d93c82002-12-23 22:51:03 +0000434 if self.debugging:
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000435 print>>sys.__stderr__, "****** Connection request from ", address
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000436 if address[0] == '127.0.0.1':
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000437 SocketIO.__init__(self, working_sock)
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000438 else:
Kurt B. Kaiser74d93c82002-12-23 22:51:03 +0000439 print>>sys.__stderr__, "** Invalid host: ", address
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000440 raise socket.error
Chui Tey5d2af632002-05-26 13:36:41 +0000441
442 def get_remote_proxy(self, oid):
443 return RPCProxy(self, oid)
444
445class RPCProxy:
446
447 __methods = None
448 __attributes = None
449
450 def __init__(self, sockio, oid):
451 self.sockio = sockio
452 self.oid = oid
453
454 def __getattr__(self, name):
455 if self.__methods is None:
456 self.__getmethods()
457 if self.__methods.get(name):
458 return MethodProxy(self.sockio, self.oid, name)
459 if self.__attributes is None:
460 self.__getattributes()
461 if not self.__attributes.has_key(name):
462 raise AttributeError, name
463 __getattr__.DebuggerStepThrough=1
464
465 def __getattributes(self):
466 self.__attributes = self.sockio.remotecall(self.oid,
467 "__attributes__", (), {})
468
469 def __getmethods(self):
470 self.__methods = self.sockio.remotecall(self.oid,
471 "__methods__", (), {})
472
473def _getmethods(obj, methods):
474 # Helper to get a list of methods from an object
475 # Adds names to dictionary argument 'methods'
476 for name in dir(obj):
477 attr = getattr(obj, name)
478 if callable(attr):
479 methods[name] = 1
480 if type(obj) == types.InstanceType:
481 _getmethods(obj.__class__, methods)
482 if type(obj) == types.ClassType:
483 for super in obj.__bases__:
484 _getmethods(super, methods)
485
486def _getattributes(obj, attributes):
487 for name in dir(obj):
488 attr = getattr(obj, name)
489 if not callable(attr):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000490 attributes[name] = 1
Chui Tey5d2af632002-05-26 13:36:41 +0000491
492class MethodProxy:
493
494 def __init__(self, sockio, oid, name):
495 self.sockio = sockio
496 self.oid = oid
497 self.name = name
498
499 def __call__(self, *args, **kwargs):
500 value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
501 return value
502
503#
504# Self Test
505#
506
507def testServer(addr):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000508 # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
Chui Tey5d2af632002-05-26 13:36:41 +0000509 class RemotePerson:
510 def __init__(self,name):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000511 self.name = name
Chui Tey5d2af632002-05-26 13:36:41 +0000512 def greet(self, name):
513 print "(someone called greet)"
514 print "Hello %s, I am %s." % (name, self.name)
515 print
516 def getName(self):
517 print "(someone called getName)"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000518 print
Chui Tey5d2af632002-05-26 13:36:41 +0000519 return self.name
520 def greet_this_guy(self, name):
521 print "(someone called greet_this_guy)"
522 print "About to greet %s ..." % name
523 remote_guy = self.server.current_handler.get_remote_proxy(name)
524 remote_guy.greet("Thomas Edison")
525 print "Done."
526 print
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000527
Chui Tey5d2af632002-05-26 13:36:41 +0000528 person = RemotePerson("Thomas Edison")
529 svr = RPCServer(addr)
530 svr.register('thomas', person)
531 person.server = svr # only required if callbacks are used
532
533 # svr.serve_forever()
534 svr.handle_request() # process once only
535
536def testClient(addr):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000537 "demonstrates RPC Client"
538 # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
Chui Tey5d2af632002-05-26 13:36:41 +0000539 import time
540 clt=RPCClient(addr)
541 thomas = clt.get_remote_proxy("thomas")
542 print "The remote person's name is ..."
543 print thomas.getName()
544 # print clt.remotecall("thomas", "getName", (), {})
545 print
546 time.sleep(1)
547 print "Getting remote thomas to say hi..."
548 thomas.greet("Alexander Bell")
549 #clt.remotecall("thomas","greet",("Alexander Bell",), {})
550 print "Done."
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000551 print
Chui Tey5d2af632002-05-26 13:36:41 +0000552 time.sleep(2)
Chui Tey5d2af632002-05-26 13:36:41 +0000553 # demonstrates remote server calling local instance
554 class LocalPerson:
555 def __init__(self,name):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000556 self.name = name
Chui Tey5d2af632002-05-26 13:36:41 +0000557 def greet(self, name):
558 print "You've greeted me!"
559 def getName(self):
560 return self.name
561 person = LocalPerson("Alexander Bell")
562 clt.register("alexander",person)
563 thomas.greet_this_guy("alexander")
564 # clt.remotecall("thomas","greet_this_guy",("alexander",), {})
565
566def test():
567 addr=("localhost",8833)
568 if len(sys.argv) == 2:
569 if sys.argv[1]=='-server':
570 testServer(addr)
571 return
572 testClient(addr)
573
574if __name__ == '__main__':
575 test()