目录
在日常工作中,免不了两个进程之间进行通信,例如:a程序需要拉取b程序的配置数据,b程序更新配置数据时,a程序也需要及时更新。
需要完成该需求,我们则需要进行两个进程之间的通信。网络上比较常用的方式有两种:
方式一:使用multiprocessing进行子进程,创建,再进行进程通信;
方式二:利用redis等其他中间件进行通信。
redis | multiprocessing | |
进程解耦 | 两个或多个进程可以独立运行并通信 | 必须启动一个主进程,再运行子进程 |
搭建方式 | 需要独立安装redis | 无需搭建第三方,直接调用 |
性能 | 内存级别,需要连接开销 | 内存级别,无需连接开销 |
以上两种方式都有优缺点,如果使用redis,必须系统中集成redis;但使用multiprocessing,就必须存在一个主进程,这样进程直接就被主进程绑定在了一起,如果主进程死掉,就都死了。
结合以上两点,我的需求是无需搭建第三方服务并可在独立进程之间进行通信。
想要实现独立进程间通信,无外乎需要第三方的一个程序去管理消息,我第一时间想到的就是socket,利用socket搭建一个消息订阅平台,且全部使用python,自动运行自动处理。
一、系统架构
简单的架构如上所示,当subscribe和publish任何一方,在需要订阅和发布时,去检查Broker的启动情况,如果Broker未启动,则去启动并等待连接;如果Broker启动,则直接连接。
通过Broker的转发,将消息发送给相应的订阅方,订阅方和发布方不做任何联系,双方对对方无感。
本案例不做任何消息缓存,即发布方发布时,如果没有任何订阅方进行订阅,则该消息就会丢失。
二、Broker启动方式
Broker将以socketserver的方式启动,使用后台方式进行运行。
import socketserver
2.1 linux后台启动:
os.system("sudo xxx.python3 xxx.py&")
2.2 windows后台启动:
1、利用bat运行python程序;
2、python调用bat文件。
为了使用bat进行后台运行,我需要在启动broker之前创建一份bat文件,代码如下:
def create_run_bat(py_path, broker_path):
"""
创建bat
:param py_path: 当前python地址
:param broker_path: 当前broker的python文件地址
:return: bat的地址
"""
global RUN_BAT_PATH # 固定的bat文件地址
if not os.path.exists(RUN_BAT_PATH):
with open(RUN_BAT_PATH, "w") as f:
f.write("@echo off\n")
f.write(
f'mshta vbscript:createobject("wscript.shell").run("{py_path} {broker_path}",0)(window.close)&&exit')
return RUN_BAT_PATH
当bat还未存在时,将会去创建一个固定的bat文件地址,但该地址中的python可执行文件地址和咱们Broker文件地址是动态传入的。
2.3 后台运行
def run_broker():
"""
根据当前系统后台运行Broker
:return:
"""
global CURRENT_FOLDER_PATH # 当前目录地址
py_path = os.sys.executable # 获取当前python可执行文件地址
broker_path = f"{CURRENT_FOLDER_PATH}/Broker.py" # Broker的python文件地址
if platform.system() == 'Windows':
py_path = py_path.replace("python.exe", "pythonw.exe") # windows中使用pythonw.exe执行程序
bat_path = create_run_bat(py_path, broker_path) # 获取或创建bat文件
os.system(f"{bat_path}")
else:
os.system(f"sudo {py_path} {broker_path}&")
我们的将创建一个Broker.py来承载我们的Broker程序,使用上面代码就可以实现自动后台运行咱们的Broker。
三、判断Broker运行状态
为了能够实现查看Broker是否正在运行,使用socket回包的形式进行判断,因此不管是端口占用还是程序未启动,我都能因此来判断Broker的运行情况:
def check_broker_active():
"""
判断Broker是否活跃
:return:
"""
global PORT # 定义自己的端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(("127.0.0.1", PORT))
except:
return False
s.sendall(b"PyMQ test") # 给Broker发送特定语句
time.sleep(0.01)
try:
res = s.recv(1024)
if res != b"Yes": # 当Broker未回复特定语句Yes,则开端口的程序非我们的Broker
return False
except:
return False
s.close()
return True
四、Broker启动方法
def run_server():
"""
启动Broker
:return:
"""
if not check_broker_active(): # 查看Broker的运行情况
try:
radio_func_dic = {} # 广播字典
server = MQThreadingTCPServer(
('127.0.0.1', PORT), Handle, radio_func_dic=radio_func_dic
)
radio_thread = threading.Thread(target=global_radio_handle, args=(radio_func_dic,)) # 广播订阅消息线程
radio_thread.start()
server.serve_forever()
except Exception as e:
return False
return True
if __name__ == '__main__':
run_server()
关于radio_func_dic和radio_thread用途后面介绍。
未完待续