WEB静态服务器-epoll版的http服务器

WEB静态服务器

epoll版的http服务器

什么是epoll ?

epoll是什么?按照Linux中 "man"手册的说法:是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法

epoll的原理过程

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

IO 多路复用

就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。

select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。

它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

代码示例

import socket
import re
import select


def service_client(new_socket, request):
    """为这个客户端返回数据"""

    # # 1、接收浏览器发送过来的请求,即http请求
    # # GET /     HTTP/1.1
    # # ......
    # global response
    # request = new_socket.recv(1024).decode("utf-8")
    # # print(">>" * 50)
    # # print(request)

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

    # GET /index.html HTTP/1.1
    # get post put del              正则1:r"[^/]+(/[^ ]*)"      正则2:r".*(/.*?\..*?) HTTP.*"
    file_name = ""
    req_str = re.search(r"[?/html](/.*?\..*?) HTTP.*", request_lines[0])  # 匹配第一个元素的 ‘HTTP’ 之前的文件名
    print(req_str)
    if req_str:
        file_name = req_str.group(1)
        print("*"*50, file_name)
        if file_name == "/":
            file_name = "/html/index.html"

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

    try:
        file = open("./html" + file_name, "rb")
    except IOError:
        # 404表示没有这个页面
        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 = file.read()
        file.close()

        response_body = html_content

        # 2.1、准备发送给浏览器的数据---header
        response_header = "HTTP/1.1 200 OK\r\n"
        response_header += "Content-Length:%d\r\n" % len(html_content)  # 长连接
        response_header += "\r\n"

        response = response_header.encode("utf-8") + response_body  # 二进制

        # 2.2、准备发送给浏览器的数据---body
        # 将response header 发送给浏览器
        new_socket.send(response)

    # 关闭套接字
    # new_socket.close()


def main():
    """作为程序的主控制入口"""
    # 1、创建套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2、绑定
    tcp_server_socket.bind(("", 12708))

    # 3、变为监听套接字
    tcp_server_socket.listen(128)
    tcp_server_socket.setblocking(False)  # 将套接字变为非堵塞

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

    # 将监听套接字对应的fd注册到epoll中进行监听
    epl.register(tcp_server_socket.fileno(), select.EPOLLIN)  # 套接字.fileno()  :  得到套接字的文件描述符

    fd_event_dict = dict()

    while True:

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

        # [(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)
                fd_event_dict[new_socket.fileno()] = new_socket
            elif event == select.EPOLLIN:
                # 判断已经链接的客户端十个缶有数据发送过来
                recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
                if recv_data:
                    service_client(fd_event_dict[fd], recv_data)
                else:
                    fd_event_dict[fd].close()
                    epl.unregister(fd)
                    del fd_event_dict[fd]

    # 关闭套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

小总结

I/O 多路复用的特点:

通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 ‘监视’ 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。

扫描二维码关注公众号,回复: 6089331 查看本文章

当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。

参考资料
如果想了解下epoll在Linux中的实现过程可以参考:http://blog.csdn.net/xiajun07061225/article/details/9250579

关于module ‘select’ has no attribute ‘epoll’,详见下一篇博文。
链接:https://blog.csdn.net/weixin_42250835/article/details/89573354

猜你喜欢

转载自blog.csdn.net/weixin_42250835/article/details/89529732
今日推荐