python修炼——多版本的多任务的http服务器!

昨日回顾

http 协议

  • 超文本传输协议
  • request 请求

四次挥手:客户端 close ,服务器close,释放资源,断开链接

今日内容

localhost = 127.0.0.1 表示本地地址

url:Uniform Resource Locators 统一资源定位符

多任务版本的http服务器

多进程版本:

​ 多进程最耗费资源

​ 子进程会复制主进程的一份资源

​ fd:文件描述符,就是一个数字,对应一个特殊的文件,例如网络接口

​ 所有的Linux里的东西都对应一个文件

import socket
import re
import multiprocessing


def server_client(client_socket):
    """实现接收消息和回送消息"""
    # 接收消息
    # request 请求
    request = client_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # 使用正则取出用户请求的数据名
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    file_name = ""
    if ret:
        file_name = ret.group(1)
        print("请求的名字是:%s" % file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 返回 http 格式的数据给浏览器
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "-----没有要找的页面----"
        client_socket.send(response.encode("utf-8"))

    else:
        html_content = f.read()
        f.close()
        # 回送消息, 返回 http 格式的数据给浏览器
        # 回应给浏览器的数据   header     reponse  回应
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"  # 浏览器中的回车时 \r  空行是 \n

        # 发送数据  header
        client_socket.send(response.encode("utf-8"))
        # 发送数据  body
        client_socket.send(html_content)

    # 关闭套接字
    client_socket.close()


def main():
    # 创建服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 可以让端口重复使用

    # 绑定本地信息
    tcp_server_socket.bind(("", 8888))

    # 监听消息
    tcp_server_socket.listen()

    while True:
        # 等待客户的连接
        new_socket, client_addr = tcp_server_socket.accept()

        # 服务客户端
        # server_client(new_socket)
        # 使用多进程来实现
        p = multiprocessing.Process(target=server_client, args=(new_socket,))  # 创建多进程
        p.start()  # 启动子进程

        # 在这里要关闭套接字,子进程有自己的资源,创建子进程会给这个新套接字上来个新的硬链接,由子进程最后来关闭这个套接字
        # 主进程中不关闭的话服务器会认为一直在请求数据,
        new_socket.close()

    # 关闭监听
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

多线程版本:

​ 多线程创建和多进程创建差不多,区别在于:在多进程中,每个进程有自己的资源,所以需要在主进程中关闭客户端套接字,当子进程执行完毕,由进程来最后关闭自己的套接字;而多线程共享资源,所以不需要在主线程中关闭套接字。

import socket
import re
import threading


def server_client(client_socket):
    """实现接收消息和回送消息"""
    # 接收消息
    # request 请求
    request = client_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # 使用正则取出用户请求的数据名
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    file_name = ""
    if ret:
        file_name = ret.group(1)
        print("请求的名字是:%s" % file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 返回 http 格式的数据给浏览器
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "-----没有要找的页面----"
        client_socket.send(response.encode("utf-8"))

    else:
        html_content = f.read()
        f.close()
        # 回送消息, 返回 http 格式的数据给浏览器
        # 回应给浏览器的数据   header     reponse  回应
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"  # 浏览器中的回车时 \r  空行是 \n

        # 发送数据  header
        client_socket.send(response.encode("utf-8"))
        # 发送数据  body
        client_socket.send(html_content)

    # 关闭套接字
    client_socket.close()


def main():
    # 创建服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 可以让端口重复使用

    # 绑定本地信息
    tcp_server_socket.bind(("", 8888))

    # 监听消息
    tcp_server_socket.listen()

    while True:
        # 等待客户的连接
        new_socket, client_addr = tcp_server_socket.accept()

        # 服务客户端
        # server_client(new_socket)
        # 使用多进程来实现
        t = threading.Thread(target=server_client, args=(new_socket,))  # 创建多进程
        t.start()  # 启动子进程

        # 线程中共享资源,所以不要提前关闭套接字
        # new_socket.close()

    # 关闭监听
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

协程版本:

​ 协程多任务消耗资源最少

import socket
import re
import gevent


def server_client(client_socket):
    """实现接收消息和回送消息"""
    # 接收消息
    # request 请求
    request = client_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # 使用正则取出用户请求的数据名
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    file_name = ""
    if ret:
        file_name = ret.group(1)
        print("请求的名字是:%s" % file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 返回 http 格式的数据给浏览器
    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "-----没有要找的页面----"
        client_socket.send(response.encode("utf-8"))

    else:
        html_content = f.read()
        f.close()
        # 回送消息, 返回 http 格式的数据给浏览器
        # 回应给浏览器的数据   header     reponse  回应
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"  # 浏览器中的回车时 \r  空行是 \n

        # 发送数据  header
        client_socket.send(response.encode("utf-8"))
        # 发送数据  body
        client_socket.send(html_content)

    # 关闭套接字
    client_socket.close()


def main():
    # 创建服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 可以让端口重复使用

    # 绑定本地信息
    tcp_server_socket.bind(("", 8888))

    # 监听消息
    tcp_server_socket.listen()

    while True:
        # 等待客户的连接
        new_socket, client_addr = tcp_server_socket.accept()

        # 服务客户端
        # server_client(new_socket)
        # 创建协程
        g = gevent.spawn(server_client, new_socket)
        g.join()

    # 关闭监听
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

单进程、单线程、长链接、非阻塞版本的http服务器

长连接:建立好连接后,多次收发数据,不传递数据了断开即可

短连接:收发一次数据后,就建立连接和断开连接

import socket
import re


def server_client(client_socket, request):
    """实现接收消息和回送消息"""
    # 接收消息
    # request 请求
    # request = client_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()  # 将客户端传递过来的请求转换为列表
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # 使用正则取出用户请求的数据名
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    file_name = ""
    if ret:  # 进行判断,如果得到请求的数据名就进行赋值
        file_name = ret.group(1)
        print("请求的名字是:%s" % file_name)
        if file_name == "/":  # 当用户只输入ip和端口的时候,请求的只是 / ,此时给用户返回默认页面
            file_name = "/index.html"

    # 返回 http 格式的数据给浏览器
    try:  # 进行异常判断
        f = open("./html" + file_name, "rb")  # 打开用户请求的文件名
    except:  # r如果没有用户请求的内容,返回 404
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response = "<h1>FILE NOT FOUND</h1>"
        client_socket.send(response.encode("utf-8"))
    else:  # 有用户请求的内容
        html_content = f.read()  # 读取文件内容
        f.close()  # 关闭文件
        response_body = html_content  # 将读取出来的文件赋值给 响应的内容
        # print(len(response_body))
        # 回送消息, 返回 http 格式的数据给浏览器
        # 回应给浏览器的数据   header     reponse  回应
        response_header = "HTTP/1.1 200 OK\r\n"  # 1.1 是长链接 1.0 是短链接
        # 在响应头中告诉用户浏览器本次发送的内容大小,当本次数据接收完毕会结束,
        # 并进行下一次请求,这就是长链接
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"  # 浏览器中的回车时 \r  空行是 \n
        # 将响应头和响应内容合并成响应 response 发送过去,响应头需要编码
        response = response_header.encode("utf-8") + response_body
        # 发送数据
        client_socket.send(response)


def main():
    # 创建服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 让端口可以重复使用
    tcp_server_socket.bind(("", 8888))  # 绑定本地信息
    tcp_server_socket.listen(128)  # 监听客户端,最大连接数为128
    tcp_server_socket.setblocking(False)  # 将套接字设置为非阻塞
    client_socket_list = list()  # 创建一个客户端套接字列表

    while True:
        try:  # 因为是非阻塞,所以如果没有客户端链接的情况下一定会报错,需要进行异常处理
            new_socket, client_addr = tcp_server_socket.accept()  # 等待客户端链接
        except Exception as ret:  # 出现错误就忽略
            pass
        else:  # 有客户端链接
            new_socket.setblocking(False)  # 先将新套接字设置为非阻塞
            client_socket_list.append(new_socket)  # 将新套接字的引用放到客户端套接字列表中

        for client_socket in client_socket_list:  # 遍历得到每个客户端套接字
            try:  # 因客户端套接字已经设置为非阻塞,所有在没有消息发送过来的时候会报错,进行异常判断
                recv_data = client_socket.recv(1024).decode("utf-8")  # 需要进行传参,所以直接进行转码
            except:  # 忽略没有消息的情况
                pass
            else:  # 有消息过来
                if recv_data:  # 进行判断,有数据就调用 服务客户端 的函数,将客户端套接字和接收到的数据传参过去
                    server_client(client_socket, recv_data)
                else:  # 发送过来的数据为空,说明客户端已经关闭
                    client_socket.close()  # 此时将该客户端对应的套接字关闭
                    client_socket_list.remove(client_socket)  # 并且将该客户端的引用从客户端套接字列表中中删除,防止资源占用

    # 关闭监听
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

epoll 版本的 http 服务器

作用:提高单进程、单线程传输数据的效率

如何实现:开辟一个单独的空间出来

  1. 操作系统搞一块公共内存,减少了复制的次数
  2. 用了事件通知机制,比轮循快多了
import socket
import re
import select


def server_client(client_socket, request):
    """实现接收消息和回送消息"""
    # 接收消息
    # request 请求
    # request = client_socket.recv(1024).decode("utf-8")
    # print(request)

    request_lines = request.splitlines()  # 将客户端传递过来的请求转换为列表
    print("")
    print(">" * 20)
    print(request_lines)

    # GET /index.html HTTP/1.1
    # 使用正则取出用户请求的数据名
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    file_name = ""
    if ret:  # 进行判断,如果得到请求的数据名就进行赋值
        file_name = ret.group(1)
        print("请求的名字是:%s" % file_name)
        if file_name == "/":  # 当用户只输入ip和端口的时候,请求的只是 / ,此时给用户返回默认页面
            file_name = "/index.html"

    # 返回 http 格式的数据给浏览器
    try:  # 进行异常判断
        f = open("./html" + file_name, "rb")  # 打开用户请求的文件名
    except:  # r如果没有用户请求的内容,返回 404
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response = "<h1>FILE NOT FOUND</h1>"
        client_socket.send(response.encode("utf-8"))
    else:  # 有用户请求的内容
        html_content = f.read()  # 读取文件内容
        f.close()  # 关闭文件
        response_body = html_content  # 将读取出来的文件赋值给 响应的内容
        # 回送消息, 返回 http 格式的数据给浏览器
        # 回应给浏览器的数据   header     reponse  回应
        response_header = "HTTP/1.1 200 OK\r\n"  # 1.1 是长链接 1.0 是短链接
        # 在响应头中告诉用户浏览器本次发送的内容大小,当本次数据接收完毕会结束,
        # 并进行下一次请求,这就是长链接
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"  # 浏览器中的回车时 \r  空行是 \n
        # 将响应头和响应内容合并成响应 response 发送过去,响应头需要编码
        response = response_header.encode("utf-8") + response_body
        # 发送数据
        client_socket.send(response)


def main():
    # 创建服务器套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # socket.SO_REUSEADDR 在同一端口上可以运行多个应用程序,但是只有第一次绑定这个端口的进程才能使用
    # 如果多个进程要使用同一端口,必须设置 socket.SO_REUSEADDR ,起到排队的作用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  # 让端口可以重复使用
    tcp_server_socket.bind(("", 8888))  # 绑定本地信息
    tcp_server_socket.listen(128)  # 监听客户端,最大连接数为128
    tcp_server_socket.setblocking(False)  # 将套接字设置为非阻塞

    client_socket_list = list()  # 创建一个客户端套接字列表

    # 创建一个epoll 对象
    epl = select.epoll()

    # 将监听套接字对应的fd注册到epoll中
    epl.register(tcp_server_socket.fileno(), select.EPOLLIN)

    fd_event_dict = dict()

    while True:

        # 默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞
        fd_event_list = epl.poll()

        # [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]
        for fd, event in fd_event_list:
            # 等待新客户端的链接
            if fd == tcp_server_socket.fileno():
                new_socket, client_addr = tcp_server_socket.accept()
                epl.register(new_socket.fileno(), select.EPOLLIN)  # 将新套接字也注册到epoll中
                fd_event_dict[new_socket.fileno()] = new_socket  # 将新套接字存入字典,key键是新套接字的文件描述符,value值是新套接字的引用
            elif event == select.EPOLLIN:
                # 判断已经链接的客户端是否有数据发送过来
                recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
                if recv_data:
                    server_client(fd_event_dict[fd], recv_data)  # 有数据就调用服务客户端的函数
                else:
                    # 没有数据过来说明客户端已经关闭
                    fd_event_dict[fd].close()   # 关掉这个套接字
                    epl.unregister(fd)  # 从epoll中删除
                    del fd_event_dict[fd]  # 从字典中删除



    # 关闭监听
    tcp_server_socket.close()


if __name__ == '__main__':
    main()

猜你喜欢

转载自blog.csdn.net/qyf__123/article/details/81950634
今日推荐