HTTP 1.0是短链接, 1.1转化为长链接

短链接:为了得到一个数据,三次握手服务器,服务器给了我,四次挥手结束,为了得到另外一个数据又进行此过程,为了获取一个新的数据重新建立链接

长链接:通过同一个套接字获取多个数据

上面多进程 多线程,协程都是使用短链接的形式

短链接是收少显示多少,而长链接有一个问题就客户端会不知道你给他发多少数据有没有发完,所以就会一直在等待你发送,使用Content-Lenght来规定发送过去的长度达到发送即显示的目的

单进程单线程非堵塞长链接版本:

import socket
import re


def service_client(new_socket, request):
    """为这个客户端返回数据"""
    # 1.接搜浏览器发来的请求,即http请求
    # GET /HTTP/1.1
    # request = new_socket.recv(1024).decode("gbk")
    # 将接收到的数据转化成列表
    request_lines = request.splitlines()

    file_name = ""
    # 使用正则匹配
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        print(file_name)
        if file_name == "/":
            file_name = "/index.html"

    try:
        # 使用open读取数据
        f = open("." + file_name, 'rb')
    except:
        request_body = "---------->页面出错啦"
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        # 实现长链接,不需要调用close只需要设置HTTP/1.1 和Content-Length 获取你发送过去的长度
        response += "Content-Length:%d\r\n" %len(request_body)
        response += "\r\n"
        response += request_body
        response += "--------->页面出错了!!!"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()

        response_body = html_content
        # 2.返回http格式的数据给浏览器
        # 2.1准备发送给浏览器的数据---header
        response_header = "HTTP/1.1 200 OK\r\n"
        body = len(response_body)
        response_header += "Content-Length:%d\r\n" % body
        response_header += "\r\n"

        res = response_header.encode("gbk") + response_body
        # new_socket.send(response_header)
        new_socket.send(res)
    # 关闭套解字
    # new_socket.close()


def main():
    """用来完成整体的控制"""

    # 1.创建套套接子
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 四次挥手谁先调用close就需要等到,要是服务器先调用就会造成端口被占用使用此语句就能解决
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2.绑定端口bind
    tcp_socket.bind(("", 8899))

    # 3.变为监听套解字listen(128) 数字表示同一时间能有128个同时连接
    tcp_socket.listen(128)

    tcp_socket.setblocking(False)  # 将套接字变为非堵塞
    client_socket_list = list()

    while True:
        try:
            # 4.等待新客户端的链接:accept()
            new_socket, client_adder = tcp_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_da = client_socket.recv(1024).decode("gbk")
            except Exception as ret:
                pass
            else:
                if recv_da:
                    service_client(client_socket, recv_da)
                else:
                    print("2")
                    client_socket.close()
                    client_socket_list.remove(client_socket)
    # 关闭监听套接字
    tcp_socket.close()


if __name__ == "__main__":
    main()

Web静态服务器-epoll

IO多路复用
就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所有socket,当某个socket有数据到达了,就通知用户进程。

之前写的那个单进程、单线程、非堵塞效率不够高的原因在于,当列表太长,每次都需要copy到操作系统进行判断,而epoll在此诞生了,有一个特殊的内存,应用程序和操作系统共用的,所以当每进来一个套接字就不用复制到操作系统里面去,减少了复制的过程,开发过程中,从第一个遍历到最后一个叫做轮询,效率较低,效率较高的是事件通知,事件通知是,不是去遍历列表而是,列表里面的套接字,哪个消息到来,就去处理它

import socket
import re
import select

def service_client(new_socket, request):
    """为这个客户端返回数据"""
    # 1.接搜浏览器发来的请求,即http请求
    # GET /HTTP/1.1
    # request = new_socket.recv(1024).decode("gbk")
    # 将接收到的数据转化成列表
    request_lines = request.splitlines()

    file_name = ""
    # 使用正则匹配
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        print(file_name)
        if file_name == "/":
            file_name = "/index.html"

    try:
        # 使用open读取数据
        f = open("." + file_name, 'rb')
    except:
        request_body = "---------->页面出错啦"
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "Content-Length:%d\r\n" %len(request_body)
        response += "\r\n"
        response += request_body
        response += "--------->页面出错了!!!"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()

        response_body = html_content
        # 2.返回http格式的数据给浏览器
        # 2.1准备发送给浏览器的数据---header
        response_header = "HTTP/1.1 200 OK\r\n"
        body = len(response_body)
        response_header += "Content-Length:%d\r\n" % body
        response_header += "\r\n"

        res = response_header.encode("gbk") + response_body
        # new_socket.send(response_header)
        new_socket.send(res)
    # 关闭套解字
    # new_socket.close()


def main():
    """用来完成整体的控制"""

    # 1.创建套套接子
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 四次挥手谁先调用close就需要等到,要是服务器先调用就会造成端口被占用使用此语句就能解决
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 2.绑定端口bind
    tcp_socket.bind(("", 8899))

    # 3.变为监听套解字listen(128) 数字表示同一时间能有128个同时连接
    tcp_socket.listen(128)

    tcp_socket.setblocking(False)  # 将套接字变为非堵塞
    client_socket_list = list()
    # 创建一个epoll对象(共享内存)
    epl = select.epoll()

    # 将监听套接字对应的fd注册到epoll中,一个套接字点fileneo()文件操作符
    # select.EPOLLIN监听前面套接字是否有输入
    epl.register(tcp_socket.fileno(), select.EPOLLIN)

    fd_event_dict = dict()  # 创建字典用来存储fd和对应套接字 因为下面拿到的是文件描述符,而文件描述符不能转套接字,

    while True:
        fd_event_list = epl.poll()  # 默认堵塞,直到os监测到数据到来,通过事件通知方式,告诉这个程序,此时才会解堵塞,返回值是一个列表

        # [(fd, event), (套接字对应的文件的文件描述符,这个文件描述符到底是什么事件例如可以调用recv接收等)]
        for fd, event in fd_event_list:
            # 4.等待新客户端的链接:accept()
            if fd == tcp_socket.fileno():
                new_socket, client_adder = tcp_socket.accept()
                epl.register(new_socket.fileno(), select.EPOLLIN)
                fd_event_dict[new_socket.fileno()] = new_socket
            elif event == select.EPOLLIN:
                # 判断已经链接的客户端是否有数据发送过来
                recv_da = fd_event_dict[fd].recv(1024).decode("gbk")
                if recv_da:
                    service_client(fd_event_dict[fd], recv_da)
                else:
                    print("2")
                    fd_event_dict[fd].close()
                    epl.unregister(fd)  # 从epl里面注销
                    del  fd_event_dict[fd]

    # 关闭监听套接字
    tcp_socket.close()


if __name__ == "__main__":
    main()