基于Python在Linux下使用epoll实现简单的高并发服务器

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/rusi__/article/details/97186830

前言


需要知道的以下几点:

  • 重中之重:该项目注释相当详细,小白完全可以看懂。
  • 项目需求:浏览器作为客户端发送请求,服务器接收并处理返回对应的数据–也就是网页(要熟悉正则表达。式)。
  • 部分重复代码并未定义函数,直接复制粘贴。
  • epoll中重要的是“事件通知”和“内存映射”以及“fd(file descriptor)”的灵活使用。
  • 此文中还有长连接的运用及介绍。

代码部分

import socket, re, gevent
import select
from gevent import monkey

# monkey.patch_all()


def service_client(client_socket,client_request):
    # client_request = client_socket.recv(1024).decode("utf-8")
    # print(">>"*50)  # 体现浏览器多少个请求
    # print(client_request)  # GET / HTTP/1.1

    client_request_lines = client_request.splitlines()  # splitlines() 切割一个字符串(\n\r),返回一个列表,缺省为False
    print(">>" * 20)
    print(client_request_lines)

    # GET /index.html HTTP/1.1
    ret = re.match(r"[^/]+(/[^ ]*)", client_request_lines[0])
    file_name = ""  # 解决引用未定义的变量
    if ret:
        file_name = ret.group(1)
        # print("*"*50, file_name)
        if file_name == "/":
            file_name = "/index.html"
    # else:
    #     file_name = "/index.html"

    try:
        f = open("./html" + file_name, "rb")
        html_content = f.read()
        f.close()

        response_body = html_content
        response_header = "HTTP/1.1 200 OK\r\n"  # win中只识别 \r\n的换行
        # 长连接的关键
        response_header += "Content-Length:%d\r\n" % len(response_body)

        response_header += "\r\n"
        # 字符串编码为二进制相加
        response = response_header.encode("gbk")+response_body

        client_socket.send(response)

    except Exception as a:
        f = open("./html/" + "404.html", "rb")
        html_content = f.read()
        f.close()

        response_body = html_content
        response_header = "HTTP/1.1 200 OK\r\n"  # win中只识别 \r\n的换行
        # 长连接的关键
        response_header += "Content-Length:%d\r\n" % len(response_body)

        response_header += "\r\n"
        # 字符串编码为二进制相加
        response = response_header.encode("gbk") + response_body

        client_socket.send(response)

        # todo 经过测试发现 如果想要利用长连接的话,那么不能直接返回字符串,而需要返回一个网页。(另外,可能因为我的网页问题,我的浏览器在访问我的服务器时,有时会出现类似堵塞的状况)
        # todo 得到解决了,那是因为我没有设置监听套接字为非堵塞(见main())
        # print(a)
        # # response = "HTTP/1.1 404 NOT FOUND\r\n"
        # response = "HTTP/1.1 200 OK\r\n"  # 兼容 谷歌外的浏览器
        # response += "\r\n"
        # # response += "Content-Length:%d\r\n" % len("<h1>---没有你要的地址---</h1>")
        # response += "<h1>---没有你要的地址---</h1>"
        # client_socket.send(response.encode("gbk"))
        #
        # client_socket.close()  # 调用了close() 还是短连接
    pass


def main():
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置服务器先close 即服务器端四次挥手之后资源能够立即释放,这样就保证了下次运行服务器程序的时候,可以立即启动。
    tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    tcp_socket.bind(("192.168.233.1", 7890))
    tcp_socket.listen(128)
    tcp_socket.setblocking(False)

    # 创建一个epoll对象
    epl = select.epoll()
    # 将监听套接字对应的fd注册到epoll中 .fileno(): 获取套接字对应的fd,.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:  # 列表中有几个,便循环几次(虽说很简单的基础知识,但是对于理解这一部分是很重要的)。
            # 返回的fd和监听套接字的fd一样,便可有一个新的套接字。
            if fd == tcp_socket.fileno(): # 这是监听套接字的 if。
                client_socket, client_addr = tcp_socket.accept()
                epl.register(client_socket.fileno(), select.EPOLLIN)  # 到这就至少有两个套接字了,循环之后因为有了监听套接字的缘故,便可有多个“新的套接字”了。
                fd_event_dict[client_socket.fileno()] = client_socket
            # 判断链接的客户端是否有数据过来
            elif event == select.EPOLLIN:  # 剩下的套接字的 elif

                recv_data = fd_event_dict[fd].recv(1024).decode("gbk")
                if recv_data:
                    service_client(fd_event_dict[fd], recv_data)
                else:
                    # 关闭套接字
                    fd_event_dict[fd].close()
                    # 注销fd
                    epl.unregister(fd)
                    # 删除字典中的内容
                    del fd_event_dict[fd]

    tcp_socket.close()
    pass


if __name__ == '__main__':
    main()

# todo 仅参考。
# 长连接的意义:减少使用服务器的资源。一次性要所有的数据:(比如一次性将网页中的图片全部要来(可能比起短连接的要慢一点【短链接中服务器解析浏览器的数据请求中包含多个图片文件,会建立多个短链接发送数据】--但是现在网速都相对较快了,所以在资源合理化利用的角度上来说是要使用长连接的))

猜你喜欢

转载自blog.csdn.net/rusi__/article/details/97186830