blob: c7644e16818da3e53011ac9ba15bd4a56bfd06e9 [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:
157 ##traceback.print_exc(file=sys.__stderr__)
158 typ, val, tb = info = sys.exc_info()
159 sys.last_type, sys.last_value, sys.last_traceback = info
160 if isinstance(typ, type(Exception)):
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000161 # Class exception
Chui Tey5d2af632002-05-26 13:36:41 +0000162 mod = typ.__module__
163 name = typ.__name__
164 if issubclass(typ, Exception):
165 args = val.args
166 else:
167 args = (str(val),)
168 else:
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000169 # User string exception
Chui Tey5d2af632002-05-26 13:36:41 +0000170 mod = None
171 name = typ
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000172 if val is None: val = ''
173 args = str(val)
Chui Tey5d2af632002-05-26 13:36:41 +0000174 tb = traceback.extract_tb(tb)
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000175 self.debug("localcall:EXCEPTION: ", mod, name, args, tb)
Chui Tey5d2af632002-05-26 13:36:41 +0000176 return ("EXCEPTION", (mod, name, args, tb))
177
178 def remotecall(self, oid, methodname, args, kwargs):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000179 self.debug("calling asynccall via remotecall")
Chui Tey5d2af632002-05-26 13:36:41 +0000180 seq = self.asynccall(oid, methodname, args, kwargs)
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000181 return self.asyncreturn(seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000182
183 def asynccall(self, oid, methodname, args, kwargs):
184 request = ("call", (oid, methodname, args, kwargs))
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000185 seq = self.newseq()
186 self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
187 self.putmessage((seq, request))
Chui Tey5d2af632002-05-26 13:36:41 +0000188 return seq
189
190 def asyncreturn(self, seq):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000191 self.debug("asyncreturn:%d:call getresponse(): " % seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000192 response = self.getresponse(seq)
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000193 self.debug(("asyncreturn:%d:response: " % seq), response)
Chui Tey5d2af632002-05-26 13:36:41 +0000194 return self.decoderesponse(response)
195
196 def decoderesponse(self, response):
197 how, what = response
198 if how == "OK":
199 return what
200 if how == "EXCEPTION":
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000201 self.debug("decoderesponse: EXCEPTION:", what)
Chui Tey5d2af632002-05-26 13:36:41 +0000202 mod, name, args, tb = what
203 self.traceback = tb
Kurt B. Kaisera552e3a2002-08-24 23:57:17 +0000204 if mod: # not string exception
Chui Tey5d2af632002-05-26 13:36:41 +0000205 try:
206 __import__(mod)
207 module = sys.modules[mod]
208 except ImportError:
209 pass
210 else:
211 try:
212 cls = getattr(module, name)
213 except AttributeError:
214 pass
215 else:
Kurt B. Kaisera552e3a2002-08-24 23:57:17 +0000216 # instantiate a built-in exception object and raise it
Chui Tey5d2af632002-05-26 13:36:41 +0000217 raise getattr(__import__(mod), name)(*args)
Kurt B. Kaisera552e3a2002-08-24 23:57:17 +0000218 name = mod + "." + name
219 # do the best we can:
Kurt B. Kaiser0e3a5772002-06-16 03:32:24 +0000220 raise name, args
Chui Tey5d2af632002-05-26 13:36:41 +0000221 if how == "ERROR":
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000222 self.debug("decoderesponse: Internal ERROR:", what)
Chui Tey5d2af632002-05-26 13:36:41 +0000223 raise RuntimeError, what
224 raise SystemError, (how, what)
225
226 def mainloop(self):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000227 """Listen on socket until I/O not ready or EOF
228
229 pollpacket() will loop looking for seq number None, which never
230 comes. The loop will exit when self.ioready() returns 0.
231
232 """
Chui Tey5d2af632002-05-26 13:36:41 +0000233 try:
234 self.getresponse(None)
235 except EOFError:
236 pass
237
238 def getresponse(self, myseq):
239 response = self._getresponse(myseq)
240 if response is not None:
241 how, what = response
242 if how == "OK":
243 response = how, self._proxify(what)
244 return response
245
246 def _proxify(self, obj):
247 if isinstance(obj, RemoteProxy):
248 return RPCProxy(self, obj.oid)
249 if isinstance(obj, types.ListType):
250 return map(self._proxify, obj)
251 # XXX Check for other types -- not currently needed
252 return obj
253
254 def _getresponse(self, myseq):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000255 self.debug("_getresponse:myseq:", myseq)
Chui Tey5d2af632002-05-26 13:36:41 +0000256 if threading.currentThread() is self.mainthread:
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000257 # Main thread: does all reading of requests or responses
258 # Loop here until there is message traffic on the socket
Chui Tey5d2af632002-05-26 13:36:41 +0000259 while 1:
260 response = self.pollresponse(myseq, None)
261 if response is not None:
262 return response
263 else:
264 # Auxiliary thread: wait for notification from main thread
265 cvar = threading.Condition(self.statelock)
266 self.statelock.acquire()
267 self.cvars[myseq] = cvar
268 while not self.responses.has_key(myseq):
269 cvar.wait()
270 response = self.responses[myseq]
271 del self.responses[myseq]
272 del self.cvars[myseq]
273 self.statelock.release()
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000274 return response # might be None
Chui Tey5d2af632002-05-26 13:36:41 +0000275
276 def newseq(self):
277 self.nextseq = seq = self.nextseq + 2
278 return seq
279
280 def putmessage(self, message):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000281 self.debug("putmessage:%d:" % message[0])
Chui Tey5d2af632002-05-26 13:36:41 +0000282 try:
283 s = pickle.dumps(message)
284 except:
285 print >>sys.__stderr__, "Cannot pickle:", `message`
286 raise
287 s = struct.pack("<i", len(s)) + s
288 while len(s) > 0:
289 n = self.sock.send(s)
290 s = s[n:]
291
292 def ioready(self, wait=0.0):
293 r, w, x = select.select([self.sock.fileno()], [], [], wait)
294 return len(r)
295
296 buffer = ""
297 bufneed = 4
298 bufstate = 0 # meaning: 0 => reading count; 1 => reading data
299
300 def pollpacket(self, wait=0.0):
301 self._stage0()
302 if len(self.buffer) < self.bufneed:
303 if not self.ioready(wait):
304 return None
305 try:
306 s = self.sock.recv(BUFSIZE)
307 except socket.error:
308 raise EOFError
309 if len(s) == 0:
310 raise EOFError
311 self.buffer += s
312 self._stage0()
313 return self._stage1()
314
315 def _stage0(self):
316 if self.bufstate == 0 and len(self.buffer) >= 4:
317 s = self.buffer[:4]
318 self.buffer = self.buffer[4:]
319 self.bufneed = struct.unpack("<i", s)[0]
320 self.bufstate = 1
321
322 def _stage1(self):
323 if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
324 packet = self.buffer[:self.bufneed]
325 self.buffer = self.buffer[self.bufneed:]
326 self.bufneed = 4
327 self.bufstate = 0
328 return packet
329
330 def pollmessage(self, wait=0.0):
331 packet = self.pollpacket(wait)
332 if packet is None:
333 return None
334 try:
335 message = pickle.loads(packet)
336 except:
337 print >>sys.__stderr__, "-----------------------"
338 print >>sys.__stderr__, "cannot unpickle packet:", `packet`
339 traceback.print_stack(file=sys.__stderr__)
340 print >>sys.__stderr__, "-----------------------"
341 raise
342 return message
343
344 def pollresponse(self, myseq, wait=0.0):
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000345 """Handle messages received on the socket.
346
347 Some messages received may be asynchronous 'call' commands, and
348 some may be responses intended for other threads.
349
350 Loop until message with myseq sequence number is received. Save others
351 in self.responses and notify the owning thread, except that 'call'
352 commands are handed off to localcall() and the response sent back
353 across the link with the appropriate sequence number.
354
355 """
Chui Tey5d2af632002-05-26 13:36:41 +0000356 while 1:
357 message = self.pollmessage(wait)
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000358 if message is None: # socket not ready
Chui Tey5d2af632002-05-26 13:36:41 +0000359 return None
360 wait = 0.0
361 seq, resq = message
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000362 self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
Chui Tey5d2af632002-05-26 13:36:41 +0000363 if resq[0] == "call":
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000364 self.debug("pollresponse:%d:localcall:call:" % seq)
Chui Tey5d2af632002-05-26 13:36:41 +0000365 response = self.localcall(resq)
Kurt B. Kaiserbc286132003-01-25 21:33:40 +0000366 self.debug("pollresponse:%d:localcall:response:%s"
367 % (seq, response))
Chui Tey5d2af632002-05-26 13:36:41 +0000368 self.putmessage((seq, response))
369 continue
370 elif seq == myseq:
371 return resq
372 else:
373 self.statelock.acquire()
374 self.responses[seq] = resq
375 cv = self.cvars.get(seq)
376 if cv is not None:
377 cv.notify()
378 self.statelock.release()
379 continue
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000380
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000381#----------------- end class SocketIO --------------------
Chui Tey5d2af632002-05-26 13:36:41 +0000382
383class RemoteObject:
384 # Token mix-in class
385 pass
386
387def remoteref(obj):
388 oid = id(obj)
389 objecttable[oid] = obj
390 return RemoteProxy(oid)
391
392class RemoteProxy:
393
394 def __init__(self, oid):
395 self.oid = oid
396
397class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
398
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000399 debugging = False
400 location = "#S" # Server
Chui Tey5d2af632002-05-26 13:36:41 +0000401
402 def __init__(self, sock, addr, svr):
403 svr.current_handler = self ## cgt xxx
404 SocketIO.__init__(self, sock)
405 SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
406
Chui Tey5d2af632002-05-26 13:36:41 +0000407 def handle(self):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000408 "handle() method required by SocketServer"
Chui Tey5d2af632002-05-26 13:36:41 +0000409 self.mainloop()
410
411 def get_remote_proxy(self, oid):
412 return RPCProxy(self, oid)
413
414class RPCClient(SocketIO):
415
Kurt B. Kaiser0930c432002-12-06 21:45:24 +0000416 debugging = False
417 location = "#C" # Client
418
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000419 nextseq = 1 # Requests coming from the client are odd numbered
Chui Tey5d2af632002-05-26 13:36:41 +0000420
421 def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000422 self.listening_sock = socket.socket(family, type)
423 self.listening_sock.setsockopt(socket.SOL_SOCKET,
424 socket.SO_REUSEADDR, 1)
425 self.listening_sock.bind(address)
426 self.listening_sock.listen(1)
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000427
428 def accept(self):
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000429 working_sock, address = self.listening_sock.accept()
Kurt B. Kaiser74d93c82002-12-23 22:51:03 +0000430 if self.debugging:
Kurt B. Kaiser0a0e6c32003-01-25 03:26:35 +0000431 print>>sys.__stderr__, "****** Connection request from ", address
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000432 if address[0] == '127.0.0.1':
Kurt B. Kaiseradc63842002-08-25 14:08:07 +0000433 SocketIO.__init__(self, working_sock)
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000434 else:
Kurt B. Kaiser74d93c82002-12-23 22:51:03 +0000435 print>>sys.__stderr__, "** Invalid host: ", address
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000436 raise socket.error
Chui Tey5d2af632002-05-26 13:36:41 +0000437
438 def get_remote_proxy(self, oid):
439 return RPCProxy(self, oid)
440
441class RPCProxy:
442
443 __methods = None
444 __attributes = None
445
446 def __init__(self, sockio, oid):
447 self.sockio = sockio
448 self.oid = oid
449
450 def __getattr__(self, name):
451 if self.__methods is None:
452 self.__getmethods()
453 if self.__methods.get(name):
454 return MethodProxy(self.sockio, self.oid, name)
455 if self.__attributes is None:
456 self.__getattributes()
457 if not self.__attributes.has_key(name):
458 raise AttributeError, name
459 __getattr__.DebuggerStepThrough=1
460
461 def __getattributes(self):
462 self.__attributes = self.sockio.remotecall(self.oid,
463 "__attributes__", (), {})
464
465 def __getmethods(self):
466 self.__methods = self.sockio.remotecall(self.oid,
467 "__methods__", (), {})
468
469def _getmethods(obj, methods):
470 # Helper to get a list of methods from an object
471 # Adds names to dictionary argument 'methods'
472 for name in dir(obj):
473 attr = getattr(obj, name)
474 if callable(attr):
475 methods[name] = 1
476 if type(obj) == types.InstanceType:
477 _getmethods(obj.__class__, methods)
478 if type(obj) == types.ClassType:
479 for super in obj.__bases__:
480 _getmethods(super, methods)
481
482def _getattributes(obj, attributes):
483 for name in dir(obj):
484 attr = getattr(obj, name)
485 if not callable(attr):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000486 attributes[name] = 1
Chui Tey5d2af632002-05-26 13:36:41 +0000487
488class MethodProxy:
489
490 def __init__(self, sockio, oid, name):
491 self.sockio = sockio
492 self.oid = oid
493 self.name = name
494
495 def __call__(self, *args, **kwargs):
496 value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
497 return value
498
499#
500# Self Test
501#
502
503def testServer(addr):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000504 # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
Chui Tey5d2af632002-05-26 13:36:41 +0000505 class RemotePerson:
506 def __init__(self,name):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000507 self.name = name
Chui Tey5d2af632002-05-26 13:36:41 +0000508 def greet(self, name):
509 print "(someone called greet)"
510 print "Hello %s, I am %s." % (name, self.name)
511 print
512 def getName(self):
513 print "(someone called getName)"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000514 print
Chui Tey5d2af632002-05-26 13:36:41 +0000515 return self.name
516 def greet_this_guy(self, name):
517 print "(someone called greet_this_guy)"
518 print "About to greet %s ..." % name
519 remote_guy = self.server.current_handler.get_remote_proxy(name)
520 remote_guy.greet("Thomas Edison")
521 print "Done."
522 print
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000523
Chui Tey5d2af632002-05-26 13:36:41 +0000524 person = RemotePerson("Thomas Edison")
525 svr = RPCServer(addr)
526 svr.register('thomas', person)
527 person.server = svr # only required if callbacks are used
528
529 # svr.serve_forever()
530 svr.handle_request() # process once only
531
532def testClient(addr):
Kurt B. Kaiserb4179362002-07-26 00:06:42 +0000533 "demonstrates RPC Client"
534 # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
Chui Tey5d2af632002-05-26 13:36:41 +0000535 import time
536 clt=RPCClient(addr)
537 thomas = clt.get_remote_proxy("thomas")
538 print "The remote person's name is ..."
539 print thomas.getName()
540 # print clt.remotecall("thomas", "getName", (), {})
541 print
542 time.sleep(1)
543 print "Getting remote thomas to say hi..."
544 thomas.greet("Alexander Bell")
545 #clt.remotecall("thomas","greet",("Alexander Bell",), {})
546 print "Done."
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000547 print
Chui Tey5d2af632002-05-26 13:36:41 +0000548 time.sleep(2)
Chui Tey5d2af632002-05-26 13:36:41 +0000549 # demonstrates remote server calling local instance
550 class LocalPerson:
551 def __init__(self,name):
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000552 self.name = name
Chui Tey5d2af632002-05-26 13:36:41 +0000553 def greet(self, name):
554 print "You've greeted me!"
555 def getName(self):
556 return self.name
557 person = LocalPerson("Alexander Bell")
558 clt.register("alexander",person)
559 thomas.greet_this_guy("alexander")
560 # clt.remotecall("thomas","greet_this_guy",("alexander",), {})
561
562def test():
563 addr=("localhost",8833)
564 if len(sys.argv) == 2:
565 if sys.argv[1]=='-server':
566 testServer(addr)
567 return
568 testClient(addr)
569
570if __name__ == '__main__':
571 test()