Python实现epoll版http服务器并实现长连接

epoll 是 Linux 平台下特有的一种 I/O 复用模型实现,epoll版http服务器实现原理类似于select版服务器,都是通过某种方式对套接字进行检验其是否能收发数据等。
select和poll中采用的都是轮询的方式进行检测,轮询的方式数据越多效率越低,而epoll中则通过事件通知的方式来进行检测,从主动的去轮询检测到等着需要处理的事件来通知,其效率要更高,同时没有上限,但是epoll版http服务器只能在Linux系统中运行,因此这也是很多公司选择Linux系统做服务器的原因之一。
同时,轮询方式相对与epoll来说,实现起来非常简单,epoll实现起来相对要复杂很多。本文简单介绍了使用epoll编写http服务器的基本实现。
看代码详情:

import socket
import select


def tcp_server(new_tcp_socket, request):

    request_lines = request.splitlines()
    print(request_lines)
    print(">" * 30)
    try:
        file = open("./test/login.html", "rb")
    except:
    	# 构造响应头
        response_header = "HTTP/1.1 404 NOT FOUND\r\n"
        response_header += "\r\n"
        response_header += "----file not found-----"
        new_tcp_socket.send(response_header.encode("utf-8"))
    else:
        html_content = file.read()
        file.close()
        response_body = html_content
        response_header = "HTTP/1.1 200 OK\r\n"
        # 使用Content-Length实现长连接
        response_header += "Content-Length:%d\r\n" % len(response_body)
        response_header += "\r\n"
        response = response_header.encode("utf-8") + response_body
        # 发送响应数据
        new_tcp_socket.send(response)


def main():
    """对大致流程进行控制"""
    # 1.创建tcp套接字
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置当服务器先close()即服务器4次挥手之后资源立即释放
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 2.绑定端口
    tcp_socket.bind(("", 7890))
    # 3.监听套接字
    tcp_socket.listen(128)
    tcp_socket.setblocking(False)
    # 创建一个epoll对象
    epoll = select.epoll()
    # 将监听套接字对应的fd注册到epoll中,并绑定事件   fd:文件描述符
    epoll.register(tcp_socket.fileno(), select.EPOLLIN)
    # 定义保存socket的字典
    fd_event_dict = dict()
    while True:
        # 默认堵塞,直到OS检测到数据到来通过事件通知方式告诉程序,才会解堵塞 返回list
        fd_event_list = epoll.poll()
        # [(fd,event)] (套接字对应的文件描述符,这个文件描述符对应的到底是什么事件,例如可以调用recv接收等)

        # 遍历元组
        for fd, event in fd_event_list:
            if fd == tcp_socket.fileno():
                new_tcp_socket, client_addr = tcp_socket.accept()
                epoll.register(new_tcp_socket.fileno(), select.EPOLLIN)
                # 通过字典保存socket,键为fd,值为socket
                fd_event_dict[new_tcp_socket.fileno()] = new_tcp_socket
            elif event == select.EPOLLIN:
                # 判断已经链接的客户端是否有数据发送过来
                recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
                if recv_data:
                    # 有数据操作
                    # 4.为这个客户端服务
                    tcp_server(fd_event_dict[fd], recv_data)  
                else:
                    # 无数据操作
                    fd_event_dict[fd].close()
                    epoll.unregister(fd)
                    del fd_event_dict[fd]
    # 关闭监听套接字
    tcp_socket.close()


if __name__ == '__main__':
    main()

值得注意的是:我们用字典的方式存储了socket,因为在for循环中我们得到的只是文件描述符而不是socket,鉴于后面的操作需要用到socket,因此采用字典进行存储socket。
另外需要说明的是select的三种模式:
EPOLLIN (可读)
EPOLLOUT (可写)
EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。

ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。

最后,本文中的代码请求的网页是在网上随便找的一个网页保存了下来,然后程序跑起来之后,通过访问localhost:7890即可访问到保存的网页。

参考文章:https://blog.csdn.net/wangbowj123/article/details/77885294

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值