用Python实现C系统与Java/PHP/Ruby等Web系统的对接
一、问题的提出因为web和原有系统互通的需要,而原系统已经有基于tcp的长连接API。由于web客户端是并发性的访问,而老系统的API是C2C的对等长连接通信,不允许多个客户端同时连接一个服务器。因此要在原有C系统与web间搭建起互通的适配层,将web的通信协议与内部系统间对接起。
二、解决方案
早先曾考虑用JNI的方式把api包装下,但JNI在处理字符串的时候,经常会有字符集转换的问题。再则,JNI搞定了java,那php、RoR等又得一个个的单独实现,比较费时,因此改用语言无关的方案。
基于这个需求,想来python最合适干这个,原因有三:
1、python标准库里的协议集最全面,随手可得不说,还有标准的doc和一些立即可以run的example,这对于为web提供服务来说,最容易不过;
2、python对c的集成也是非常容易的
3、python代码的简洁性是有目共睹的,包括简洁的语法、大量唾手可得的标志模块,如日志系统、线程等等
选定python,就着手开始下一步,选定与web的协议。考虑到web client不能也定死到python,应该允许使用php、java等任意web语言使用本模块的可能性。因此可选的方案必须要符合语言无关的特点(当然也是平台无关的),可选的方案有:webservice、xmlrpc、REST等。因为ws过于复杂而REST还需要自己定义参数格式,最后选择了xmlrpc这种既简洁又具有参数类型的协议(xmlrpc在参数的前面添加类别定义信息,一般的xmlrpc协议栈都会根据该定义将数据转换到与语言对应的类型上来)
至于原有CAPI的包装,已有非常多的文章,可以google下,笔者常用的是Boost.Python,非常简单,差不多是公式性代码,这里不再藉述。
三、具体实现
接下来就开始实现这个xmlrpcserver,python里就有一个SimpleXMLRPCServer的,在python的帮助里还有示例代码:
服务器侧:
from SimpleXMLRPCServer import SimpleXMLRPCServer
# Create server
server = SimpleXMLRPCServer(("localhost", 8000))
server.register_introspection_functions()
# Register pow() function; this will use the value of
# pow.__name__ as the name, which is just 'pow'.
server.register_function(pow)
# Register a function under a different name
def adder_function(x,y):
return x + y
server.register_function(adder_function, 'add')
# Register an instance; all the methods of the instance are
# published as XML-RPC methods (in this case, just 'div').
class MyFuncs:
def div(self, x, y):
return x // y
server.register_instance(MyFuncs())
# Run the server's main loop
server.serve_forever()
客户端侧:import xmlrpclib
s = xmlrpclib.Server('http://localhost:8000')
print s.pow(2,3)# Returns 2**3 = 8
print s.add(2,3)# Returns 5
print s.div(5,2)# Returns 5//2 = 2
# Print list of available methods
print s.system.listMethods() windows下开两个pyshell,先运行服务器侧的代码,然后输入客户端的代码,马上就能看到结果。一切进展顺利,然后新问题出现了。三、性能优化与改进
这个适配层很通用,意味着可能被用来做为多个web与多个后台系统间的统一中间层,而这个SimpleServer里没有看到任何并发的概念,因此立即修改了server的一个函数,增加了time.sleep。果然,如我所料,全部调用都是串行的。这在web与后台系统的集成中是会经常发生的。因此需要解决两个问题:1、如何把两个请求独立开来处理,不能因为某个调用阻塞了其他所有的请求2、web有超时的概念,这个xmlrpc该如何处理先来看第一个问题,google了资料。有人说要改python库的代码,有人说用twisted。虽然鄙人早先使用VC的时候,曾有过修改MFC代码的时候,但深知这种修改所带来的麻烦。在VC的工程里如果修改了MFC代码,几个人合作的项目要让别人也编译过是多么的麻烦不说,就算是自己过段时间重装了windows后再来修改几个月前的项目,也会遇到不少麻烦,因此不可取。twisted到其网站上查找xmlrpc,得到的答案是这个东西对xmlrpc的支持很勉强。因此还决定采用C++里的常用模式,copy出pyhton里的SimpleXMLRPCServer来,自己修改后重新命名成自己的。其实因为python强大的标准库,修改起来很简单。只需要:from SimpleXMLRPCServer import SimpleXMLRPCDispatcher, SimpleXMLRPCRequestHandler
from SocketServer import ThreadingTCPServer
try:
import fcntl
except ImportError:
fcntl = None
class MyXMLRPCServer(ThreadingTCPServer,
SimpleXMLRPCDispatcher):
"""Simple XML-RPC server.
Simple XML-RPC server that allows functions and a single instance
to be installed to handle requests. The default implementation
attempts to dispatch XML-RPC calls to the functions or instance
installed in the server. Override the _dispatch method inhereted
from SimpleXMLRPCDispatcher to change this behavior.
"""
allow_reuse_address = True
def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None):
self.logRequests = logRequests
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
ThreadingTCPServer.__init__(self, addr, requestHandler)
# If possible, set close-on-exec flag; if a
# method spawns a subprocess, the subprocess shouldn't have
# the listening socket open.
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)然后把原有代码里的SimpleXMLRPCServer换成MyXMLRPCServer就行了。查看ThreadingTCPServer的代码,可以看到其核心实际上是基类的ThreadingMixIn里的process_request方法,在每个request到来时,都是很简单的启动一个python里的threading:
def process_request(self, request, client_address):
"""Start a new thread to process the request."""
import threading
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
if self.daemon_threads:
t.setDaemon (1)
t.start()再明显不过,因此多线程问题得到解决。如果实际系统在运行的时候,发现这种“不受控”的开启线程过于“并发”,当然可以自己实现一个ThreadingMixIn,仿照Python库里的方式足组合一把。遗留问题:1、API调用的超时问题该如何解决2、 Java客户端代码
页:
[1]