우리는 일반적으로 C++ 또는 기타 하위 수준 언어를 통해 복잡한 기능 모듈을 구현하고, 데이터를 쿼리하기 위해 웹 기반 데모를 구축해야 하는 등의 요구 사항에 직면합니다. Python 언어의 강력함과 단순성으로 인해 데모 구축에 매우 적합합니다. Flask 프레임워크와 jinja2 모듈 기능은 Python에 편리한 웹 개발 기능을 제공합니다. 동시에 Python은 다른 언어의 코드와 쉽게 상호 작용할 수 있습니다. 따라서 우리는 Demo 개발 도구로 Python을 선택했습니다. 우리가 호출해야 하는 모듈(기본 서비스 제공)이 표준 입력을 통해 루프에서 데이터를 읽고 처리 후 표시된 출력에 결과를 쓴다고 가정합니다. 이 시나리오는 Linux 환경에서 매우 일반적이며 Linux의 강력한 리디렉션 기능에 의존합니다. . 그러나 불행하게도 기본 모듈에는 초기화 프로세스가 너무 많기 때문에 모든 쿼리 요청에 대해 기본 모듈을 호출하는 하위 프로세스를 다시 생성할 수 없습니다. 해결책은 자식 프로세스를 한 번만 생성한 다음 각 요청에 대해 파이프를 통해 자식 프로세스와 상호 작용하는 것입니다.
Python의 하위 프로세스 모듈은 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)
통신 함수는 하위 프로세스의 표준 출력과 오류를 표시하는 출력 데이터가 포함된 튜플(stdoutdata, stderrdata)을 반환합니다. . 그러나 Popen 객체의 통신 함수는 상위 프로세스를 차단하고 파이프도 닫으므로 각 Popen 객체는 통신 함수를 한 번만 호출할 수 있습니다. 요청이 여러 개인 경우 Popen 객체를 다시 생성해야 합니다(자식 프로세스 다시 초기화). 우리의 요구를 충족시킬 수 없습니다.
따라서 Popen 객체의 stdin 및 stdout 객체에 데이터를 쓰고 읽어야만 우리의 요구 사항을 충족할 수 있습니다. 그러나 불행히도 하위 프로세스 모듈은 기본적으로 하위 프로세스가 종료될 때 한 번만 표준 출력을 실행하고 읽습니다. 하위 프로세스와 os.popen*은 모두 한 번만 입력과 출력을 허용하고 프로세스가 종료될 때만 출력을 읽을 수 있습니다.
몇몇 조사 끝에 하위 프로세스가 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()
또한 일부 외부 프로그램을 호출할 때 다음과 같이 해당 환경 변수를 지정해야 할 수도 있습니다.
my_env = os.environ my_env["LD_LIBRARY_PATH"] = "/path/to/lib" server = server.Server(cmd, my_env)