Web服务器
一、基础功能:
1.指定监听端口
2.Web服务器短时间内重启不会引发address already in use异常
3.Web服务器端能接收浏览器请求
4.Web服务器遵守HTTP协议,并返回html给浏览器
5.浏览器关闭后Web服务器能显示断开连接
步骤:
1.创建socket
2.设置地址重用
3.绑定端口
4.开启监听
5.接收客户端连接
6.接收客户端发送的请求报文
7.判断请求报文是否有效
8.解码请求报文,获取请求的URL
9.判断请求的文件是否存在,并读取对应文件
10.拼接响应报文
11.发送响应报文
12.关闭通信
二、面向对象封装
把Web服务封装到类中,通过对象方法启动服务器
步骤:
1.创建WebServer类
2.定义__init__(),初始化socket对象
3.定义start(),启动服务器
4.定义request_handler(),处理request和response
三、服务器端基础框架构建
分模块完成开发
步骤:
1.创建application包存放处理数据的模块
2.创建app、utils等模块为主程序提供方法
3.在主程序导入模块,并调用方法
四、在终端启动Web服务器
通过终端并指定端口来启动服务器
步骤:
1.使用sys.argv获取命令行中的参数
2.判断参数格式和端口号是否合法
3.获取端口号,并在创建Web服务对象时指定端口
五、多项目选择响应
为客户端提供可选择的项目,根据选择结果响应对应的项目
步骤:
1.在类的初始化方法中,使用dict配置可选项目
2.定义init_projects(),初始化项目配置
3.根据客户端的选择更改Web服务器打开的目录
六、多线程
实现同时接收多个客户端发送的多条信息
步骤:
1.每有一个客户端连接就创建一个线程
2.设置线程守护
3.子线程调用处理请求的方法
七、多进程
使用多进程实现Web服务器的多任务
八、协程
使用协程实现Web服务器的多任务
from gevent import monkey
monkey.patch_all()
import socket
import sys
import threading
import multiprocessing
import gevent
from application import app
# WebServer类
class WebServer(object):
# 初始化socket对象
def __init__(self,port_num):
# 创建socket
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置地址重用
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口
addr_server = ('', port_num)
tcp_server_socket.bind(addr_server)
# 开启监听
tcp_server_socket.listen(128)
# 定义实例属性,保存socket对象
self.tcp_server_socket = tcp_server_socket
# 定义实例属性,保存可选项目
self.projects_dict = dict()
# 定义实例属性,保存项目路径
self.project_path = ''
self.projects_dict['wow'] = 'proj1'
self.projects_dict['dota2'] = 'proj2'
self.projects_dict['csgo'] = 'proj3'
self.projects_dict['yys'] = 'proj4'
self.projects_dict['gta5'] = 'proj5'
self.projects_dict['static'] = 'static'
# 调用初始化项目配置的方法
self.init_projects()
# 初始化项目配置
def init_projects(self):
# 显示可选项目菜单
key_list = list(self.projects_dict.keys())
for i,name in enumerate(key_list):
options = f'{i+1}.{name}'
print(options)
# 接收客户端的选择
client_option = int(input('选择要启动的项目序号:'))
index = client_option - 1
print(f'已选择:{key_list[index]}')
# 根据选择获取对应项目的目录,发布指定的项目
self.project_path = self.projects_dict[key_list[index]]
# 启动服务器
def start(self):
print('服务器启动成功,等待客户端连接...')
while True:
# 接收客户端连接
client_socket, client_addr = self.tcp_server_socket.accept()
print(f'客户端 {client_addr} 已连接')
# # 调用处理请求报文和响应报文的函数
# self.request_handler(client_socket, client_addr)
# # 创建线程
# thread_client = threading.Thread(target=self.request_handler,args=(client_socket,client_addr))
# # 设置线程守护
# thread_client.setDaemon(True)
# # 启动子线程
# thread_client.start()
# # 创建进程
# process_client = multiprocessing.Process(target=self.request_handler,args=(client_socket,client_addr))
# # 启动子进程
# process_client.start()
# # 子进程运行时会copy主进程的资源,导致socket对象在进程内部没有被释放
# client_socket.close()
# 创建协程
# 需要添加补丁,识别程序中的耗时操作
# 此处主进程继续下一次循环,等待客户端连接,而不是结束,所以无需join()
gevent_client = gevent.spawn(self.request_handler,client_socket,client_addr)
# 处理request和response
def request_handler(self,client_socket,client_addr):
# 接收客户端发送的请求报文
recv_data = client_socket.recv(8192)
print(recv_data)
# 判断客户端是否在线,在线则将请求报文解码
if not recv_data:
print(f'客户端:{client_addr}已下线\n\n')
client_socket.close()
return
# 调用处理请求报文和响应报文的方法
response_text = app.application(self.project_path,recv_data)
# 发送响应报文
client_socket.send(response_text)
print('已完成响应')
# 关闭通信
client_socket.close()
print(f'客户端 {client_addr} 已断开连接\n\n')
def main():
# 获取系统传递到程序的参数
params_list = sys.argv
# 判断参数格式是否正确
if len(params_list) != 2:
return print('启动失败,参数数量需为1')
# 判断端口号是否合法
port_num = params_list[1]
if not port_num.isdigit() or int(port_num) < 1024 or int(port_num) > 65535:
return print('启动失败,端口号需满足:1.为纯数字,2.范围为 1024 ~ 65535')
# 获取端口号并在启动Web服务时使用指定端口
port_num = int(port_num)
ws = WebServer(port_num)
ws.start()
if __name__ == '__main__':
main()
from .utils import parse_request,create_http_response
# 接收请求数据,返回响应数据
def application(current_dir,recv_data):
# 调用解析请求报文的方法
request_URL = parse_request(recv_data)
# 拼接请求文件的路径
file_path = current_dir + request_URL
# 响应主体
# 判断URL是否存在
try:
# 返回请求的html页面
with open(file_path, 'rb') as f:
response_body = f.read()
status = '200 ok'
# 调用构建响应报文的方法
response_text = create_http_response(status,response_body)
except Exception as e:
# 修改响应状态码,并返回404页面
with open('static/404.html', 'rb') as f:
# response_body = (str(e)+'\n\n').encode()
response_body = f.read()
status = '404 Not found'
response_text = create_http_response(status,response_body)
return response_text
import datetime
# 解析请求报文
def parse_request(recv_data):
# 解码请求报文
recv_request = recv_data.decode()
print(recv_request)
# 从请求报文中提取要访问的资源路径
request_line_index = recv_request.find('\r\n')
request_line = recv_request[:request_line_index]
request_line_list = request_line.split()
request_URL = request_line_list[1]
if request_URL == '/':
request_URL = '/index.html'
print(f'正在请求:{request_URL}')
return request_URL
# 构建响应报文
def create_http_response(status,response_body):
# 响应行
response_line = f'HTTP/1.1 {status}\r\n'
# 响应头
time_current = datetime.datetime.now().strftime('%F %T')
response_header = 'Host:localhost\r\n'
response_header += 'Server:python.HyeJeong\r\n'
response_header += 'Content-Type:text/html\r\n'
response_header += f'date:{time_current}\r\n'
# 空行
response_blank = '\r\n'
# 拼接响应报文
response_text = (response_line + response_header + response_blank).encode() + response_body
return response_text