13-案例:协程实现多任务Web服务器
目标¶
- 知道使用协程改进 Web服务器,以支持多个客户端同时访问
1. 功能分析¶
通过协程实现服务器的多任务处理
2. 实现思路¶
1、导入gevent 模块
2、使用gevent 调用 request_handler函数,实现多任务
3、引入monkey ,对程序打补丁
3. 代码实现¶
创建 13-简单Web服务器_协程版.py文件
内容从代码10-简单Web服务器_游戏服务器.py
中复制
并做功能改进,修改后代码如下:
"""
TCP服务端
1、导入模块
2、创建套接字
3、设置地址重用
4、绑定端口
5、设置监听,让套接字由主动变为被动接收
6、接受客户端连接 定义函数 request_handler()
7、接收客户端浏览器发送的请求协议
8、判断协议是否为空
9、拼接响应的报文
10、发送响应报文
11、关闭操作
"""
import socket
# 导入模块
from application import app
import sys
import gevent
# 破解
from gevent import monkey
monkey.patch_all()
"""
1、在类的初始化方法中配置当前当前的项目
{"2048":"./2048", "植物大战僵尸v1":"./zwdzjs-v1", ...}
2、给类增加一个初始化项目配置的方法 init_projects()
2.1 显示所有可以发布的游戏 菜单
2.2 接收用户的选择
2.3 根据用户的选择发布指定的项目 (保存用户选择的游戏对应的本地目录)
3、更改Web服务器打开的文件目录
"""
class WebServer(object):
# 初始化方法
def __init__(self, port):
# 1、导入模块
# 2、创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 3、设置地址重用
# 当前套接字 地址重用 值True
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 4、绑定端口
tcp_server_socket.bind(("", port))
# 5、设置监听,让套接字由主动变为被动接收
tcp_server_socket.listen(128)
# 定义实例属性,保存套接字对象
self.tcp_server_socket = tcp_server_socket
# 定义类的实例属性, projects_dict 初始化为空
self.projects_dict = dict()
# 定义实例属性,保存要发布的项目的路径
self.current_dir = ""
# key value
self.projects_dict['植物大战僵尸-普通版'] = "zwdzjs-v1"
self.projects_dict['植物大战僵尸-外挂版'] = "zwdzjs-v2"
self.projects_dict['保卫萝卜'] = "tafang"
self.projects_dict['2048'] = "2048"
self.projects_dict['读心术'] = "dxs"
# 调用初始化游戏项目的方法
self.init_porjects()
# 添加一个初始化项目的方法
def init_porjects(self):
# 2.1 显示所有可以发布的游戏菜单
# list(self.projects_dict.keys()) 取出字典的key 并且转换为列表
keys_list = list(self.projects_dict.keys())
# 遍历显示所有的key
# enumerate(keys_list)
# [(0,'植物大战僵尸v1'), (1, '植物大战僵尸v2') ...]
for index, game_name in enumerate(keys_list):
print("%d.%s" % (index,game_name))
# 2.2 接收用户的选择
sel_no = int(input("请选择要发布的游戏序号:\n"))
# 2.3 根据用户的选择发布指定的项目 (保存用户选择的游戏对应的本地目录)
# 根据用户的选择,得到游戏的名称(字典的key)
key = keys_list[sel_no]
# 根据字典的key 得到项目的具体路径
self.current_dir = self.projects_dict[key]
def start(self):
"""启动Web服务器"""
print("Web服务器已经启动...等待客户端连接中....")
# 6、接受客户端连接 定义函数 request_handler()
while True:
new_client_socket, ip_port = self.tcp_server_socket.accept()
print("新客户来了:", ip_port)
# 调用功能函数处理请求并且响应
# self.request_handler(new_client_socket, ip_port)
gevent.spawn(self.request_handler, new_client_socket, ip_port)
def request_handler(self, new_client_socket, ip_port):
"""接收信息,并且做出响应"""
# 7、接收客户端浏览器发送的请求协议
request_data = new_client_socket.recv(1024)
# print(request_data)
# 8、判断协议是否为空
if not request_data:
print("%s客户端已经下线!" % str(ip_port))
new_client_socket.close()
return
# 使用 application文件夹 app 模块的 application() 函数处理
response_data = app.application(self.current_dir, request_data, ip_port)
# 10、发送响应报文
new_client_socket.send(response_data)
# 11、关闭当前连接
new_client_socket.close()
def main():
"""主函数"""
"""
1、导入sys 模块
2、获取系统传递到程序的参数
3、判断参数格式是否正确
4、判断端口号是否是一个数字
5、获取端口号
6、在启动Web服务器的时候,使用指定的端口
"""
# 2、获取系统传递到程序的参数['09-简单Web服务器_命令行启动.py', '8888', 'aaa', 'bbb', 'xxxx', '123']
# ['09-简单Web服务器_命令行启动.py', '8888', 'aaa', 'bbb', 'xxxx', '123']
params_list = sys.argv
# print(params_list)
# 3、判断参数格式是否正确
if len(sys.argv) != 2:
print("启动失败,参数格式错误!正确格式:python3 xxx.py 端口号")
return
# 4、判断端口号是否是一个数字
# 0 1
# ['09-简单Web服务器_命令行启动.py', '8888']
if not sys.argv[1].isdigit():
print("启动失败,端口号不是一个纯数字!")
return
# 5、获取端口号
port = int(sys.argv[1])
# 6、在启动Web服务器的时候,使用指定的端口
# 创建WebServer类的对象
ws = WebServer(port)
# 对象.start() 启动Web服务器
ws.start()
if __name__ == '__main__':
main()