epoll 静态服务器 python实现

直接上代码

# *-* coding:utf8 *-*

import socket
import re
import select


def handle_client(request, new_socket):
    """为这个客户端服务"""
    # 1. 接收浏览器发送过来的请求 ,即http请求
    request_lines = request.splitlines()
    if not request_lines:
        return
    print(request_lines)

    # GET /index.html HTTP/1.1
    # get post put del
    file_name = ""
    ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"

    # 2. 返回http格式的数据,给浏览器

    try:
        f = open("./html" + file_name, "rb")
    except:
        response = "HTTP/1.1 404 NOT FOUND\r\n"
        response += "\r\n"
        response += "------file not found-----"
        new_socket.send(response.encode("utf-8"))
    else:
        html_content = f.read()
        f.close()

        response_body = html_content

        response_header = "HTTP/1.1 200 OK\r\n"
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"

        response = response_header.encode("utf-8") + response_body

        new_socket.send(response)


def main():
    """主体控制"""
    # 1.创建套接字
    tcp_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.配置服务端主动关闭的时候, 自动释放端口资源
    tcp_socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 3.绑定addr
    tcp_socket_server.bind(('', 7788))
    # 4.主动变被动, 并设置队列的长度
    tcp_socket_server.listen(128)
    # 5.设置监听套接字为非阻塞, 一旦这样设置,那么在原来需要accept的位置如果没有顺利的被建立连接解阻塞,将抛出异常,可以供我们try捕获
    tcp_socket_server.setblocking(False)

    # new_sockets = list()  # 列表做消息队列,队列中装 socket引用 ,而不是数据

    # 创建 epoll 对象
    epl = select.epoll()
    # 注册事件到epoll中
    # epoll.register(fd[, eventmask])
    # 注意,如果fd已经注册过,则会发生异常
    # 将创建的套接字添加到epoll的事件监听中
    epl.register(tcp_socket_server.fileno(), select.EPOLLIN)

    # 创建一个字典来保存 fd 和 socket 之间的映射关系
    fd_event_dict = dict()
    while True:

        # 默认会阻塞, 直到os检测到数据到来, 通过事件通知方式告诉这个程序,此时才会解阻塞 [(fd, event), (fd, event)]
        # 一次会获得一个或多个底层有消息驱动的 fd和event 元祖
        fd_event_list = epl.poll()
        # [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]
        for fd, event in fd_event_list:
            if fd == tcp_socket_server.fileno():
                new_socket, client_addr = tcp_socket_server.accept()
                print('有一个新的客户端连接过来...')
                # 向epoll中注册 新的socket 的可读事件
                epl.register(new_socket.fileno(), select.EPOLLIN)
                # 保存映射关系
                fd_event_dict[new_socket.fileno()] = new_socket
            elif event == select.EPOLLIN:
                # 判断有客户端发送数据过来, 接收数据
                # 已知: 可以从socket对象中通过 fileno 获取其文件描述符, 但是不能直接通过文件描述符获取 socket 对象
                recv_data = fd_event_dict[fd].recv(1024).decode('utf-8', errors='ignore')
                if recv_data:
                    print("接收到客户端发送过来的数据: %s" % recv_data)
                    # 为这个新来的客户端服务
                    handle_client(recv_data, fd_event_dict[fd])
                else:
                    # 对方调用了close导致了没有报出阻塞异常,但是数据为空
                    print("当前客户端关闭连接....")
                    fd_event_dict[fd].close()
                    epl.unregister(fd)  # 注销
                    del fd_event_dict[fd]

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


if __name__ == '__main__':
    main()

"""
笔记:
1.epoll服务器高效率在于主要实现了两个重要功能:
- 1.内存映射技术(mmap): 将socket套接字的文件描述符放在 应用程序和操作系统 共用的存储区域(内存区域)实现通信, 省去了将存储套接字的列表从应用程序空间拷贝到操作系统内存空间的过程;(内核态和用户态)
- 2.使用 事件通知 代替 轮询操作;

"""
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值