目的:
想要把一个打开的文件描述符从一个进程传递到另外一个进程去处理。例如,服务器负责接收客户端请求,但是相关处理事务有另外一条所谓的工作者(worker)进程来处理。
实现:
这里的内容可以对于仅仅是看博客学习的同学是有困难的,小白piao这里也为各位准备了一些廉价的课程(直播或者录屏),有兴趣的同学可以关注小白piao(微信搜索wxi_xbp_python3或者公众号learnPythonByPiao即可)。不是为了可以卖弄什么,只是分享技术,如果有一定编程基础的同学,这里也可以作为一个技术总结即可。
import socket
import multiprocessing
from multiprocessing.reduction import recv_handle, send_handle#用于传递文件描述符的两个函数;且只能用于多进程连接中。
def worker(in_p, out_p):#工作者进程的事务处理函数
out_p.close()#关闭命名管道Pipe的出口,只能读取,不能写入
while True:#时间循环
fd = recv_handle(in_p)# 获取管道内的文件描述符
print('got a fileno:',fd)# 打印该文件描述符
with socket.socket(socket.AF_INET,socket.SOCK_STREAM,fileno=fd) as s:
while True:
msg = s.recv(1024)#接受信息
if not msg:
break
s.send(msg)#发给客户端
def server(address, in_p, out_p, work_pid):
in_p.close()# 关闭读取,仅仅写入
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#创建服务器套接字
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)#设置套接字属性
s.bind(address)#绑定IP地址
s.listen(1)#监听客户端
while True:
client, addr = s.accept()# 获取客户端套接字和地址
print('SERVER GOT :', addr)# 打印客户端地址
send_handle(out_p, client.fileno(), work_pid)# 将写入管道、客户端文件描述符,工作者进程pid通过管道发送给工作者进程
client.close()# 关闭客户端
if __name__ == '__main__':
c1, c2 = multiprocessing.Pipe()# c1是读取,c2是写入
worker_p = multiprocessing.Process(target=worker, args=(c1, c2))#创建工作者进程,并且传递命名管道的C1和C2;C1用来读取,C2用来传输,为了保证数据完整性,写入时读取关闭,读取时写入关闭
worker_p.start()#工作者进程启动
server_p = multiprocessing.Process(target=server,args=(('',20000), c1, c2, worker_p.pid))#创建服务器进程,传入服务器事务处理函数、基本地址,管道文件,工作者进程pid
server_p.start()# 服务器启动
c1.close() # 关闭管道c1
c2.close() #关闭管道c2
可以将工作者进程和服务器进程分开来完成,不用放在一起也可以:
#serverrmp.py
from multiprocessing.connection import Listener
from multiprocessing.reduction import send_handle
import socket
def server(work_addr, port)
# 等待工作者进程接入
work_serv = Listener(work_addr, authkey=b'peekaboo')
worker = work_serv.accept()# 接收工作者进程
work_pid = worker.recv()# 接收工作者进程pid
# 等待TCP/IP客户端接入,并且将客户端实例发送给工作者进程
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
s.bind(('',port))
s.listen(1)
while True:
client, addr = s.accept()
print('SERVER GOT CLIENT:', addr)
send_handle(worker,client.fileno(),work_pid)
client.close()
if __name__ == '__main__':
import sys
if len(sys.argv) != 3:
print('SysArgv count error! must be 3 argvs!')
raise SystemExit(1)
server(sys.argv[1], int(sys.argv[2]))
#workrmp.py
from multiprocessing.connection import Client
from multiprocessing.reduction import recv_handle
import socket
import os
def worker(server_address):
serv = Client(server_address, authkey=b'peekaboo')#向服务器进程注册,指明参加的是哪一个服务器进程
serv.send(os.getpid())# 向服务器进程发送自己的进程id
while True:
fd = recv_handle()#接收服务器进程发来的用户套接字的文件描述符
print('got a client fileno:', fd)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
while True:
msg = s.recv(1024)
if not msg:
break
print('worker recived a message:{0!r}'.format(msg))
s.send(msg)
if __name__ == '__main__':
import sys
if len(sys.argv) != 2:#命令行参数个数不为2
print('sysargv must be 2 args!')
raise SystemExit(1)
worker(sys.argv[1])# 获取命令行第二个参数的值(字符串)
分文件的写法很好的规避了管道的建立,通过服务器的Listener来监听连入的工作者进程,而工作者进程通过Client()向服务器进程进行注册,注意:这里谈到的都是关于多进程之间如何通信的方法,但是千万不要和TCP\IP混淆了!这里不论监听还是注册,都不是TCP\IP中的概念,而是分布式进程中的概念。这样进行代码的书写,很好的体现了分布式架构的思想(两个进程独占两个文件去处理事务)。