网络编程 8.Web服务器

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
posted @ 2021-12-15 02:25  HyeJeong  阅读(113)  评论(0)    收藏  举报