如何在接收网络数据时运行程序并执行代码?
我写了一个小程序,用Python编写,基本上做了以下几件事:
- 从用户那里获取一个热词(关键字)。如果这个热词和预设的关键字匹配,就继续执行。
- 输入正确的热词后,程序会要求用户输入一个命令。
- 程序读取命令后,会检查一个命令文件,看看是否有与输入的命令匹配的内容。
- 如果找到匹配的命令,就执行那个命令。
我想增加一个功能,让程序可以通过网络执行命令,具体步骤如下(同时我也想学习使用Twisted这个库):
- 客户端 #1 输入一个针对客户端 #2 的命令。
- 这个命令会发送到一个服务器,服务器会把命令转发给客户端 #2。
- 客户端 #2 接收到命令后,如果命令有效,就执行它。
注意:本地输入命令(使用下面的代码)和远程输入命令都应该是可行的。
经过一番思考,我想不出其他实现这个功能的方法,除了:
- 让上面的程序作为进程 #1 运行(就是我一开始写的那个本地程序)。
- 一个Twisted客户端作为进程 #2 运行,接收来自远程客户端的命令。每当接收到命令时,Twisted客户端会启动一个线程,解析命令,检查它的有效性,如果有效就执行它。
由于我对线程的经验不多,对网络编程更是没有经验,所以我想不出其他让我觉得合理的方案。
我上面提到的方案是不是太复杂了? 在我尝试这样实现之前,希望能得到一些建议。
这个Python程序的代码(不包括客户端)是:
主程序(也就是start()方法):
class Controller:
def __init__(self,listener, executor):
self.listener = listener
self.executor = executor
def start(self):
while True:
text = self.listener.listen_for_hotword()
if self.executor.is_hotword(text):
text = self.listener.listen_for_command()
if self.executor.has_matching_command(text):
self.executor.execute_command(text)
else:
tts.say("No command found. Please try again")
监听器(获取用户输入):
class TextListener(Listener):
def listen_for_hotword(self):
text = raw_input("Hotword: ")
text =' '.join(text.split()).lower()
return text
def listen_for_command(self):
text = raw_input("What would you like me to do: ")
text = ' '.join(text.split()).lower()
return text
执行器(执行给定命令的类):
class Executor:
#TODO: Define default path
def __init__(self,parser, audio_path='../Misc/audio.wav'):
self.command_parser = parser
self.audio_path = audio_path
def is_hotword(self,hotword):
return self.command_parser.is_hotword(hotword)
def has_matching_command(self,command):
return self.command_parser.has_matching_command(command)
def execute_command(self,command):
val = self.command_parser.getCommand(command)
print val
val = os.system(val) #So we don't see the return value of the command
命令文件解析器:
class KeyNotFoundException(Exception):
pass
class YAMLParser:
THRESHOLD = 0.6
def __init__(self,path='Configurations/commands.yaml'):
with open(path,'r') as f:
self.parsed_yaml = yaml.load(f)
def getCommand(self,key):
try:
matching_command = self.find_matching_command(key)
return self.parsed_yaml["Commands"][matching_command]
except KeyError:
raise KeyNotFoundException("No key matching {}".format(key))
def has_matching_command(self,key):
try:
for command in self.parsed_yaml["Commands"]:
if jellyfish.jaro_distance(command,key) >=self.THRESHOLD:
return True
except KeyError:
return False
def find_matching_command(self,key):
for command in self.parsed_yaml["Commands"]:
if jellyfish.jaro_distance(command,key) >=0.5:
return command
def is_hotword(self,hotword):
return jellyfish.jaro_distance(self.parsed_yaml["Hotword"],hotword)>=self.THRESHOLD
示例配置文件:
Commands:
echo : echo hello
Hotword: start
1 个回答
我发现很难理解你问题的背景,但我会尽量回答你的问题。
如何在接收到网络数据时运行程序并执行代码?
正如你在问题中提到的,通常写一个“边走边吃糖”的应用程序的方法是采用线程或事件循环的方式来设计代码。
因为你提到了线程和Twisted(这是一种事件循环的风格),我担心你可能在考虑将这两者混合在一起。
我认为它们是两种根本不同的编程风格(各有优缺点),混合使用通常会导致麻烦。
让我给你一些背景知识来解释一下。
背景:线程与事件编程
线程
如何理解这个概念:
我有多个任务需要同时完成,而我希望操作系统来决定如何以及何时运行这些任务。
优点:
让一个程序同时使用多个处理器核心的'唯一'方法
在posix环境中,让一个进程同时在多个CPU核心上运行的唯一方法就是通过线程(通常理想的线程数不超过机器的核心数)
更容易上手
你可以将原本在线运行的代码简单地放入一个线程中,通常不需要重新设计(如果没有GIL,可能需要一些锁,但稍后再说)
处理需要大量CPU的任务时更简单
也就是说,在大多数情况下,使用线程解决数学问题比使用事件/异步框架要简单得多。
缺点:
最佳使用场景:
计算密集型任务。
如果你有大量的数学运算需要并行处理,几乎不可能比操作系统更智能地安排CPU的使用。
(考虑到CPython的GIL问题,线程不应该用于数学运算,而应该使用内部实现了线程的库(比如NumPy))
事件循环/异步编程
如何理解这个概念:
我有多个任务需要同时完成,但我(程序员)希望直接控制如何以及何时运行我的子任务
你应该如何思考你的代码:
把所有子任务看作一个大而复杂的整体,你的脑海中应该始终想着“这段代码运行得够快吗?不会影响我管理的其他子任务吧?”
优点:
缺点:
更难入门。
事件/异步编程需要从第一行代码开始就采用不同的设计风格(不过你会发现这种设计风格在不同语言之间是可以移植的)
一个事件循环,一个核心
虽然事件循环可以让你同时处理大量的IO连接,但它本身不能在多个核心上同时运行。通常的解决方法是以这样的方式编写程序,使得可以同时运行多个程序实例,每个核心一个(这种技术可以绕过GIL问题)
多路复用高CPU任务可能很困难
事件编程需要将高CPU任务切割成小块,使每块占用的CPU时间(理想情况下是可预测的)很小,否则事件系统在运行高CPU任务时就无法进行多任务处理。
最佳使用场景:
基于IO的任务
总结 - 根据你的问题,考虑使用Twisted而不是线程
虽然你的应用程序似乎并不完全基于IO,但也没有明显的CPU密集型任务(看起来你目前是通过system
调用播放音频的,system
每次调用时都会启动一个独立的进程,所以它的工作不会占用你进程的CPU - 不过system
会阻塞,因此在Twisted中不推荐使用 - 你需要使用Twisted中的不同调用)。
你的问题也没有表明你关心如何最大化多个核心的使用。
因此,考虑到你特别提到了Twisted,而事件循环解决方案似乎最适合你的应用,我建议你使用Twisted而不是线程。
鉴于上面列出的“最佳使用场景”,你可能会觉得混合使用Twisted和线程是个好主意,但如果这样做,一旦你做错了任何稍微的事情,你就会失去事件循环(会阻塞)和线程(GIL不允许线程多任务处理)的优势,结果会变得非常复杂,且没有任何好处。
上面提到的方案是否过于复杂?在尝试以这种方式实现之前,我希望能得到一些见解。
你给出的“方案”是:
经过一些思考,我想不出其他实现方式,除了:
- 让上述程序作为进程#1运行(我在开头写的本地运行的程序)。
- 一个Twisted客户端作为进程#2运行,接收来自远程客户端的命令。每当接收到命令时,Twisted客户端将初始化一个线程来解析命令,检查其有效性,并在有效时执行。
由于我对线程没有太多经验,对网络编程更是一无所知,所以我想不出其他合理的方案。
对于“这个方案是否过于复杂”,我几乎可以肯定地说是的,因为你提到了Twisted和线程。(见上面的总结)
根据我对你想要构建的东西的理解(虽然可能不够完整和清晰),我想一个Twisted的解决方案可能会是:
- 仔细研究krondo Twisted入门(这真的需要逐行跟踪示例代码,但如果你愿意花时间去做,这是一个很棒的学习工具,适合Twisted和事件编程)
- 从头开始用你在krondo指南中学到的知识重写你的“热词”功能,开始时只提供你当前拥有的接口(键盘?)
- 将其他通信接口添加到代码中(telnet、web等),以便让你访问为键盘接口编写的处理代码。
如果,正如你在问题中所说的,你真的需要一个服务器,你可以编写第二个Twisted程序来提供这个功能(你会在krondo指南中看到所有这些示例)。不过我猜,当你理解了Twisted的库支持后,你会意识到你不需要构建额外的服务器,只需在基础代码中包含所需的协议即可。