Python高级——05

5.1. 内容回顾

5.2. [重点]案例-模拟浏览器实现

  • 导入模块

  • 创建套接字

  • 建立连接

    tcp_client_socket.connect((“www.icoderi.com”,80))

    web服务器默认是 80端口

  • 拼接请求报文

    request_line = "GET / HTTP/1.1\r\n"
    # 4.2 请求头
    request_header = "Host:www.icoderi.com\r\n"
    # 4.3 请求空行
    request_blank = "\r\n"
    # 整体拼接
    request_data = request_line + request_header + request_blank
    

    get方式没有请求的主体

  • 发送请求报文

    tcp_client_socket.send(request_data.encode())

    注意:请求报文默认是字符串,必须转二进制

  • 接收服务器返回的数据

  • 处理(截取)

    1. find 进行查找 \r\n\r\n

    2)字符串的切片 截取

    loc = recv_text.find("\r\n\r\n")
    # 7.2 截取字符串
    html_data = recv_text[loc+4:]
    
  • 保存

    with open(“index.html”, “w”) as file:
    ​ file.write(html_data)

  • 关闭连接

5.3. [重点]返回固定数据

  • 导入模块

  • 创建套接字

  • 设置地址可以重用

    #    当前套接字            地址重用         值True
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    
  • 绑定端口和IP

  • 设置套接字为被动

  • 开始接受客户端连接

    while True:
            new_client_socket, ip_port = tcp_server_socket.accept()
            # 调用功能函数处理请求并且响应
            request_handler(new_client_socket, ip_port)
    
  • 接受客户端发送的请求协议

    def request_handler(new_client_socket,ip_port)

    ​ request_data = new_client_socket.recv(1024)

  • 判断客户端是否已经下线

    if not request_data:
            print("%s客户端已经下线!" % str(ip_port))
            new_client_socket.close()
            return
    
  • 拼接响应报文

    • 响应行

    • 响应头

    • 响应空行

    • 响应主体

      # 9.1 响应行
          response_line = "HTTP/1.1 200 OK\r\n"
          # 9.2 响应头
          response_header = "Server:Python20WS/2.1\r\n"
          # 9.3 响应空行
          response_blank = "\r\n"
          # 9.4 响应的主体
          response_body = "HelloWorld!"
      
          response_data = response_line + response_header + response_blank + response_body
      
  • 发送响应报文

    new_client_socket.send(response_data.encode())

    发送之前,需要对响应报文编码

  • 关闭和当前客户端的连接

5.4. [重点]返回固定页面

  • 改进 返回固定内容 的代码

    • 第一步,拷贝 static 到项目目录下
    • 改进代码如下:
    # response_body = "HelloWorld!"
    # ************ 返回固定页面 **********
    # 通过 with open 读取文件
    with open("static/index.html", "rb") as file:
        # 把读取的文件内容返回给客户端
        response_body = file.read()
    
    • 第三步
    response_data = (response_line + response_header + response_blank).encode() + response_body
        # 10、发送响应报文
        new_client_socket.send(response_data)
    

5.5. [重点]返回指定页面

  • 核心思路,获取请求的资源路径

    • 解码请求的协议

      # 1)把请求协议解码,得到请求报文的字符串
          request_text = request_data.decode()
      
    • 得到请求行

      # 2)得到请求行
      #    (1) 查找 第一个\r\n 出现的位置
      loc = request_text.find("\r\n")
      #    (2) 截取字符串,从开头截取到 第一个\r\n 出现的位置
      request_line = request_text[:loc]
      
    • 拆分请求行,得到请求的资源路径

          request_line_list = request_line.split(" ")
          # print(request_line_list)
      
          # 得到请求的资源路径
          file_path = request_line_list[1]
          print("[%s]正在请求:%s" % (str(ip_port), file_path))
      
    • 打开指定的文件

      with open("static"+file_path, "rb") as file:
              # 把读取的文件内容返回给客户端
              response_body = file.read()
      

5.6. [重点]返回指定页面存在的问题

  • 访问的页面不存在

    解决方案:对打开文件的代码,做异常捕获

    try:
            # 通过 with open 读取文件
            with open("static"+file_path, "rb") as file:
                # 把读取的文件内容返回给客户端
                response_body = file.read()
    
        except Exception as e:
            # 1)重新修改响应行 为 404
            response_line = "HTTP/1.1 404 Not Found\r\n"
            # 2)响应的内容为错误
            response_body = "Error! (%s)" % str(e)
            # 3)把内容转换为字节码
            response_body = response_body.encode()
    
  • 默认首页,直接访问 地址192.168.150.31:8080不加路径报错

    解决思路,如果如果直接访问,请求行中可以得到 / 以此作为判断条件

    # 设置默认首页
        if file_path == "/":
            file_path = "/index.html"
    

5.7. [重点]面向对象封装

  • 新建类 WebServer
  • 创建对象方法 __init__() 、start()
  • 修改代码
    • 把套接字初始化的操作,放到 __init__()
    • 把接受客户端连接的代码 放到 start() 方法中
    • 把 request_handler() 函数,变成 对象方法(选中缩进)
    • 在 main() 函数中创建 对象 ws = WebServer() 然后启动 ws.start()

5.8. [重点]服务端基础框架构建-1

  • 创建 application 文件夹
  • 在 application文件夹中 创建 app.py
  • app.py application函数
  • 修改 request_handler() 把核心代码放到 app模块 application函数中
  • app.py 模块中,创建 parse_request() 专门用于解析请求的资源路径

5.9. [重点]服务端基础框架构建-2

  • 在application 文件夹中创建一个utils 模块
  • utils 模块中创建 create_http_response() 函数,专门用来拼接响应报文
  • 修改 app模块的application的返回响应协议的部分

5.10. [重点]给服务器加上命令行参数

  • 导入模块 sys

  • sys.argv 获取系统传递给程序的参数,并且判断个数是否正确

    # 3、判断参数格式是否正确
        if len(sys.argv) != 2:
            print("启动失败,参数格式错误!正确格式:python3 xxx.py 端口号")
            return
    
  • 判断端口号是否是纯数字

    if not sys.argv[1].isdigit():
            print("启动失败,端口号不是一个纯数字!")
            return
    
  • 保存端口号

    port = int(sys.argv[1])

  • 启动服务器的时候指定端口号

    ws = WebServer(port)

    # 初始化方法
        def __init__(self, port):
            # 1、导入模块
            # 2、创建套接字
            tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            # 3、设置地址重用
            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
    

5.11. [重点]案例:网游服务器

  • 拷贝游戏资源到 项目目录中

  • 在 webserver 初始化方法中,配置一个字典

    # 定义类的实例属性, 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"
    
  • 初始化项目的方法

    # 添加一个初始化项目的方法
        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]
    

    注意,该方法在 WebServer的 初始化方法中调用

  • 修改 request_handler() 代码

    # 使用 application文件夹 app 模块的 application() 函数处理
            response_data = app.application(self.current_dir, request_data, ip_port)
    
    
  • 设置浏览器优先的解析方式

    修改 utils模块的 create_http_response() 函数

    response_header += “Content-Type: text/html\r\n”

5.12. 知识点总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值