上节我们编写了一个最简单的 RPC 服务器模型,简单到同时只能处理单个连接。本节我们为服务器增加多线程并发处理能力,同时可以处理多个客户端连接。后来的客户端连接再也不用排队了。这也是古典 RPC 服务最常见的处理模型。
既然要使用多线程,自然离不开 Python 内置的多线程编程库。我们在上节引出的 socket、struct 和 json 三个库的基础上再增加第四个内置库 thread,本节程序的多线程功能将由它来打开。
thread 是 Python 内置的线程库,用户可以使用 thread 库创建原生的线程。
在python中可以尝试以下方式创建一个线程:
def play(params):
pass
t = threading.Thread(target=play, args=(1,2...))
t.start()
这样每个线程接收一个连接后进行业务处理,最后关闭连接。
服务器可以并行处理多个客户端,每来一个新连接,则开启一个新的线程单独进行处理。每个线程都是同步读写客户端连接。
另外新增一个服务,用来计算斐波那契数列。
直接上代码:
#server
import json
import struct
import socket
import threading
def handle_conn(conn, addr, handlers):
print('client: {0} connect'.format(addr))
#循环读写
while True:
length_prefix = conn.recv(4) #接收请求头长度
if not length_prefix: #连接关闭
print('client: {0} close'.format(addr))
conn.close()
break
length, = struct.unpack('I', length_prefix)
body = conn.recv(length)
request = json.loads(body)
print('recv :', request)
in_ = request['in']
params = request['params']
handler = handlers[in_] #查找请求对应的处理函数
handler(conn, params) #处理请求
def fib(conn, params):
result = []
a = 0
b = 1
max = int(params)
while b < max:
result.append(b)
a, b = b, a + b
send_result(conn, 'result', result)
def loop(sock, handlers):
while True:
conn , addr = sock.accept() #接收连接
t = threading.Thread(target=handle_conn, args=(conn, addr, handlers))
t.start()
def ping(conn, params):
send_result(conn, 'pong', params)
def send_result(conn, out, result):
response = json.dumps({'out' : out, 'result':result}) # 响应消息体
lenght_prefix = struct.pack('I', len(response))
conn.sendall(lenght_prefix)
conn.sendall(str.encode(response))
if __name__ == '__main__':
# 创建一个 TCP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 打开 reuse addr 选项
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("localhost", 8080)) # 绑定端口
sock.listen(1) # 监听客户端连接
print('listen')
#注册请求处理器
handlers = {
'ping' : ping , #心跳应答
'fib' : fib , #求斐波那契数列
}
loop(sock, handlers)
开启客户端结果如下:
import json
import time
import struct
import socket
#```
# //.1
# // 输入
# {
# in: "ping",
# params: "ireader 0"
# }
#
# // 输出
# {
# out: "pong",
# result: "ireader 0"
# }
#
# //.2
# // 输入
# {
# in: "fib",
# params: "5"
# }
#
# // 输出
# {
# out: "result",
# result: [1,2,3]
# }
# ```
def rpc(sock, in_, params):
request = json.dumps({"in": in_, "params": params})
length_prefix = struct.pack('I', len(request)) #发送4字节长度信息
sock.sendall(length_prefix)
sock.sendall(str.encode(request)) #发送完毕
print('send data:', str.encode(request))
#等待响应包
length_prefix = sock.recv(4) #响应包长度
length, = struct.unpack('I', length_prefix) #unpack返回一个元组
body = sock.recv(length) # 响应消息体
print('recv:', body)
response = json.loads(body)
return response['out'], response['result'] # 返回响应类型和结果
if __name__ == '__main__':
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 8080))
out, result = rpc(s, 'fib', '1000')
print(out, result)
s.close()