上节我们完成了一个简单的多线程服务器,可以并发处理多个客户端连接。但是 Python
由于全局解释器锁 GIL
的存在,致使多个线程只能占满一个 CPU
核心,多线程并不能充分利用多核的优势。所以多数 Python
服务器推荐使用多进程模型。
服务端代码
import json
import struct
import socket
import multiprocessing
def handle_conn(conn, ip, handlers):
print("{} connect ...".format(ip))
# 循环读写
while True:
length_prefix = conn.recv(4) # 请求长度前缀
if not length_prefix: # 连接关闭了
conn.close()
print("{} close ...".format(ip))
break # 退出循环,处理下一个连接
length, = struct.unpack("I", length_prefix)
body = conn.recv(length) # 请求消息体
request = json.loads(body)
client_method = request['client']
client_parameter = request['params']
print("client request method is {}, params is {}".format(client_method, client_parameter))
handler = handlers[client_method] # 查找请求处理器
handler(conn, client_parameter) # 处理请求
def process_request(sock, handlers):
while True:
conn, host_ip = sock.accept() # 接收连接
handle_conn(conn, host_ip, handlers) # 处理连接
def func1(conn, params):
res = json.dumps({"response": "OK", "result": params}) # 响应消息体
length_prefix = struct.pack("I", len(res)) # 响应长度前缀
conn.sendall(length_prefix)
# conn.sendall(response) # python2
conn.sendall(str.encode(res)) # python3
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个 TCP 套接字
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 打开 reuse addr 选项
s.bind(("localhost", 8080)) # 绑定端口
s.listen(1) # 监听客户端连接
handlers = {
"func1": func1 # 注册请求处理器
}
# process_request(s, handlers) # 进入服务循环
process = []
process_nums = 3
for i in range(process_nums):
p = multiprocessing.Process(target=process_request, args=(s, handlers))
process.append(p)
for i in range(process_nums):
process[i].start()
for i in range(process_nums):
process[i].join()
if __name__ == '__main__':
main()
示例代码中开启了 3 个进程,如果客户端请求数量大于 3 那么多余的请求就会阻塞,直到之前的客户端请求关闭后,新的请求才会处理。