1. XML-RPC 文件复制
1. Server 端
from os.path
import join, isfile, abspath
from SimpleXMLRPCServer
import SimpleXMLRPCServer
from xmlrpclib
import Fault, ServerProxy
from urlparse
import urlparse
import sys
MAX_HISTORY_NODE_LENGTH
=
6
#Node Length
UNHANDLED_CODE
=
100
# Return Code
ACCESS_DENIED_CODE
=
200
#Return Code
SimpleXMLRPCServer.allow_reuse_address
=
1
#Port reuse
class
UnhandledQuery
(
Fault
):
'''
That's show can't handle the query exception
'''
def
__init__
(
self
,
message
=
"
Couldn't handle the query
"
):
super
(UnhandledQuery,
self
).__init__
(UNHANDLED_CODE, message
)
class
AccessDenied
(
Fault
):
'''
When user try to access the forbiden resources raise exception
'''
def
__init__
(
self
,
message
=
"
Access denied
"
):
super
(AccessDenied,
self
).__init__
(ACCESS_DENIED_CODE, message
)
def
inside
(
dir
,
name
):
'''
Check the dir that user defined is contain the filename which the user given
'''
dir
= abspath
(
dir
)
name
= abspath
(name
)
return name.startswith
(join
(
dir,
''
))
def
getPort
(
url
):
'''
Get the port from the url
'''
name
= urlparse
(url
)[
1
]
# '127.0.0.1:9090'
port
=
int
(name.split
(
'
:
'
)[
1
])
return port
class
Node
(
object
):
def
__init__
(
self
,
url
,
dirname
,
secret
):
self.url
= url
self.dirname
= dirname
self.secret
= secret
self.known
=
set
()
def
query
(
self
,
query
,
history
=[]):
try:
return
self._handle_request
(query
)
except UnhandledQuery:
#No file in local node
history
= history
+
[
self.url
]
if
len
(history
)
> MAX_HISTORY_NODE_LENGTH:
raise
return
self._boardcast
(query, history
)
def
hello
(
self
,
other
):
'''
Hello to other nodes
'''
self.known.add
(other
)
return
0
def
fetch
(
self
,
query
,
secret
):
'''
find the file to download
'''
if secret
!=
self.secret:
raise AccessDenied
result
=
self.query
(query
)
f
=
open
(join
(
self.dirname, query
),
'
w
'
)
f.write
(result
)
f.close
return
0
def
_start_rpc_server
(
self
):
s
= SimpleXMLRPCServer
((
'', getPort
(
self.url
))
)
s.register_instance
(
self
)
s.serve_forever
()
def
_handle_request
(
self
,
query
):
dir
=
self.dirname
name
= join
(
dir, query
)
if
not isfile
(name
):
raise UnhandledQuery
if
not inside
(
dir, name
):
raise AccessDenied
return
open
(name
).read
()
def
_boardcast
(
self
,
query
,
history
):
for other
in
self.known.copy
():
if other
in history:
continue
try:
s
= ServerProxy
(other
)
return s.query
(query, history
)
# Other nodes to invoke the RPC SERVER Method
except Fault
as f:
#Fault init function has "faultCode and faultString"
if f.faultCode
== UNHANDLED_CODE:
pass
else:
self.known.remove
(other
)
except:
self.known.remove
(other
)
raise UnhandledQuery
def
main
():
url, directory, secret
= sys.argv
[
1:
]
n
= Node
(url, directory, secret
)
n._start_rpc_server
()
if __name__
==
"
__main__
":
main
()
Server端代码分析:
1. SimpleXMLRPCServer.allow_reuse_address = 1 表示所占用端口可以重用, 即使强制关掉Node Server之后再次重新启动, 也不会出现端口被占用的情况
2. UNHANDLED_CODE = 100 ACCESS_DENIED_CODE = 200 表示处理异常的两个返回码。
3. 过程: 启动远程调用服务器, 调用的接口就是Node类, Node类提供了三个可远程调用的方法, hello,fetch, query。 hello是添加新加入的结点信息到一个集合, fetch是用来获取数据的方法, query是查询遍历结点。
4. fetch方法,首先判断密码是否正确, 然后调用query查找, query的查找, 先是调用_handle_request 进行本地查找, 若没有找到,再通过_broadcast方法向自身维护的集合中结点发送广播消息, 这里会传入一个history, history是用来记录广播时, 不向已经发送广播的结点再次发送, history的长度是用来限制查询结点的长度。
Clinet端:
#/usr/bin/env python2.7
from cmd
import Cmd
from xmlrpclib
import ServerProxy, Fault
from random
import choice
from string
import lowercase
from server
import Node, UNHANDLED_CODE
from threading
import Thread
from time
import sleep
import sys
HEAD_START
=
0.1
SECRET_LENGTH
=
100
def
randomString
(
length
):
chars
=
[]
letters
= lowercase
[:
26
]
# return 'abcdefghijklmnopqrstuvwxyz'
while length
>
0:
chars.append
(choice
(letters
))
length
-=
1
return
''.join
(chars
)
class
Client
(
Cmd
):
prompt
=
'
yzhao_terminal_cmd>
'
def
__init__
(
self
,
url
,
dirname
,
urlfile
):
Cmd.
__init__
(
self
)
self.secret
= randomString
(SECRET_LENGTH
)
n
= Node
(url, dirname,
self.secret
)
t
= Thread
(
target
=n._start_rpc_server
)
t.setDaemon
(
1
)
t.start
()
sleep
(HEAD_START
)
self.server
= ServerProxy
(url
)
for line
in
open
(urlfile
):
line
= line.strip
()
self.server.hello
(line
)
def
do_fetch
(
self
,
arg
):
try:
self.server.fetch
(arg,
self.secret
)
except Fault
as f:
if f.faultCode
!= UNHANDLED_CODE:
raise
print
"
Couldn't find the file
", arg
def
do_exit
(
self
,
arg
):
print ()
sys.exit
()
do_EOF
= do_exit
def
main
():
urlfile, directory, url
= sys.argv
[
1:
]
client
= Client
(url, directory, urlfile
)
client.cmdloop
()
if __name__
==
"
__main__
":
main
()
Client端代码分析:
1. randomString 函数方法用来生成随机密码, Client类通过继承Cmd这个父类, 来实现交互式命令的输入; do_fetch就是调用server端的fetch方法获取资源数据, do_exit则是退出命令行,结束程序
Test
1. 分别创建两个文件夹test1 和 test2
2. test1中创建urltest1.txt, 内容为 http://127.0.0.1:3000
3. test2中创建urltest2.txt, 内容为 http://127.0.0.1:3001, 在test2中创建testfile.txt
4. 第一个控制台: python client.py test1/urltest1.txt test1/ http://127.0.0.1:3001
第二个控制台: python client.py test2/urltest2.txt test2/ http://127.0.0.1:3000
在第一个控制台输入: fetch testfile.txt, 就可以将test2目录中的testfile.txt文件拷贝到test1目录中