我们凡是会碰着这样的需求:通过C++或其他较底层的语言实现了一个巨大的成果模块,需要搭建一个基于Web的Demo,要领查询数据。由于Python语言的强大和简捷,其用来搭建Demo很是符合,Flask框架和jinja2模块成果为python提供了利便的web开拓本领。同时,python可以或许很利便的同其他语言的代码交互。因此我们选择python作为开拓Demo的东西。假设我们需要挪用的模块(提供底层处事)通过尺度输入轮回读入数据,处理惩罚完毕后把功效写出到标出输出,这样的场景在Linux情况下很常见,依赖于Linux强大的重定向本领。然而,很是不幸的是,底层模块有一个很重的初始化进程,因此我们不可以或许每次查询请求都去从头生成挪用底层模块的子历程。办理方案就是只生成一次子历程,然后对每个请求通过管道(pipe)来和子历程交互。
Python的subprocess模块可以很容易地生成子历程,雷同Linux系统挪用fork和exec。subprocess模块的Popen工具大概以非阻塞的方法挪用外部可执行措施,因此我们利用Poen工具来实现需求。假如我们想要把数据写入子历程的尺度输入stdin,那么在建设Popen工具的时候就需要指定参数stdin为subprocess.PIPE;同样,假如我们需要从子历程的尺度输出中读取数据,那么在建设Popen工具的时候就需要指定参数stdout为subprocess.PIPE。先看一个简朴的例子:
from subprocess import Popen, PIPE p = Popen('less', stdin=PIPE, stdout=PIPE) p.communicate('Line number %d.\n' % x)
communicate函数返回一个二元组(stdoutdata, stderrdata),包括了子历程的尺度输出和标堕落误的输出数据。然而,由于Popen工具的communicate函数会阻塞父历程,同时还会封锁管道,因此每个Popen工具只能挪用一次communicate函数,假如有多个请求必需从头生成Popen工具(从头初始化子历程),不能满意我们的需求。
因此,我们只有往Popen工具的stdin和stdout工具里写入和读取数据才气实现我们的需求。然而,不幸的是subprocess模块默认环境下只运行在子历程竣事的时候读取一次尺度输出。Both subprocess and os.popen* only allow input and output one time, and the output to be read only when the process terminates.
进过一番研究之后我发明通过fcntl模块的fcntl函数可以把子历程的尺度输出改为非阻塞的方法,从而到达我们的目标。这样困扰我许久的问题终于获得了完美办理。代码如下:
#!/usr/bin/python # -*- coding: utf-8 -*- # author: weisu.yxd@taobao.com from subprocess import Popen, PIPE import fcntl, os import time class Server(object): def __init__(self, args, server_env = None): if server_env: self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=server_env) else: self.process = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) flags = fcntl.fcntl(self.process.stdout, fcntl.F_GETFL) fcntl.fcntl(self.process.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK) def send(self, data, tail = '\n'): self.process.stdin.write(data + tail) self.process.stdin.flush() def recv(self, t=.1, e=1, tr=5, stderr=0): time.sleep(t) if tr < 1: tr = 1 x = time.time()+t r = '' pr = self.process.stdout if stderr: pr = self.process.stdout while time.time() < x or r: r = pr.read() if r is None: if e: raise Exception(message) else: break elif r: return r.rstrip() else: time.sleep(max((x-time.time())/tr, 0)) return r.rstrip() if __name__ == "__main__": ServerArgs = ['/home/weisu.yxd/QP/trunk/bin/normalizer', '/home/weisu.yxd/QP/trunk/conf/stopfile.txt'] server = Server(ServerArgs) test_data = '在云端', '云梯', '摩萨德', 'Alisa', 'iDB', '阿里大数据' for x in test_data: server.send(x) print x, server.recv()
#p#分页标题#e#
别的,挪用一些外部措施时,大概需要指定相应的情况变量,方法如下:
my_env = os.environ my_env["LD_LIBRARY_PATH"] = "/path/to/lib" server = server.Server(cmd, my_env)