zoukankan      html  css  js  c++  java
  • 在 Python 中使用 Epoll

    在 Python 中使用 epoll - Log4D

    在 Python 中使用 Epoll

    2013/01/12 | Comments

    原文地址: http://scotdoyle.com/python-epoll-howto.html , 我这里取精简内容翻译过来。

    ============ 正文开始 ============

    介绍

    Python 从 2.6 开始支持 epoll。现在我们用 Python3 来写基于这些 API 的 epoll 范例。

    阻塞的 Socket 通信范例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    import socket
    
    EOL1 = b'\n\n'
    EOL2 = b'\n\r\n'
    response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
    response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
    response += b'Hello, world!'
    
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('0.0.0.0', 8080))
    serversocket.listen(1)
    
    try:
       while True:
          connectiontoclient, address = serversocket.accept()
          request = b''
          while EOL1 not in request and EOL2 not in request:
              request += connectiontoclient.recv(1024)
          print('-'*40 + '\n' + request.decode()[:-2])
          connectiontoclient.send(response)
          connectiontoclient.close()
    finally:
       serversocket.close()
    

    这个范例中的代码在 accept()recv()send() 时候会发生阻塞, 导致其他连接无法完成。

    通常情况下,在我们使用阻塞模型时候,会专门建立一个主线程来进行监听, 将建立成功的连接交给其他线程操作,然后继续在主线程上面监听。 这样一来,就不会受单次请求阻塞的限制。

    C10K 问题描述了其他处理高并发方法,比如异步 Socket, 通过监听事件来触发预设的响应。异步 Socket 可以是单线程,也可以是多线程。

    Python 的 API 中包含了 select / poll / epoll,具体的可用性依赖于操作系统。 他们的效率是 epoll > poll > select,从这个 性能测试文章 就可以看出来。

    epoll 异步编程范例

    epoll 的流程是这样的:

    1. 创建 epoll 实例
    2. 告诉 epoll 去监听哪几种类型事件
    3. 向 epoll 查询最近已监听事件的变化
    4. 根据不同的类型做不同的处理
    5. 让 epoll 修改监听列表
    6. 重复 3-5 直到结束
    7. 消灭 epoll 实例

    范例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    
    import socket, select
    
    EOL1 = b'\n\n'
    EOL2 = b'\n\r\n'
    response  = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
    response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
    response += b'Hello, world!'
    
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('0.0.0.0', 8080))
    serversocket.listen(1)
    serversocket.setblocking(0)
    
    epoll = select.epoll()
    epoll.register(serversocket.fileno(), select.EPOLLIN)
    
    try:
       connections = {}; requests = {}; responses = {}
       while True:
          events = epoll.poll(1)
          for fileno, event in events:
             if fileno == serversocket.fileno():
                connection, address = serversocket.accept()
                connection.setblocking(0)
                epoll.register(connection.fileno(), select.EPOLLIN)
                connections[connection.fileno()] = connection
                requests[connection.fileno()] = b''
                responses[connection.fileno()] = response
             elif event & select.EPOLLIN:
                requests[fileno] += connections[fileno].recv(1024)
                if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
                   epoll.modify(fileno, select.EPOLLOUT)
                   print('-'*40 + '\n' + requests[fileno].decode()[:-2])
             elif event & select.EPOLLOUT:
                byteswritten = connections[fileno].send(responses[fileno])
                responses[fileno] = responses[fileno][byteswritten:]
                if len(responses[fileno]) == 0:
                   epoll.modify(fileno, 0)
                   connections[fileno].shutdown(socket.SHUT_RDWR)
             elif event & select.EPOLLHUP:
                epoll.unregister(fileno)
                connections[fileno].close()
                del connections[fileno]
    finally:
       epoll.unregister(serversocket.fileno())
       epoll.close()
       serversocket.close()
    

    最关键的几行如下:

    • 16:注册感兴趣的事件
    • 23:如果发现是监听 socket,则创建连接
    • 30:读事件处理
    • 33:读事件完成后,修改 epoll 对应的状态到写事件
    • 35:写事件
    • 41:释放对应的连接

    Epoll 分边缘触发(edge-triggered)和水平触发(level-triggered)两种, 前者只被内核触发一次通知(除非状态被改变为未就绪),后者在触发后如果不做操作, 以后仍然会收到内核的触发通知。

    更多优化

    连接等待池大小

    我们之前的代码直接使用 listen() 建立连接,可以通过设定一个队列大小, 在队列满了时候,就不再接受新的连接,从而保证已经接受的连接顺利完成。

    TCP 选项

    使用 TCP_CORK 功能,可以将小数据包封装成大包传输,提高效率。

    TCP_NODELAY 则作用相反,将大包分成小包发送出去。比较适合实时应用比如 SSH。

    (译者:Linux下高性能网络编程中的几个TCP/IP选项介绍这几个 HTTP,写的不错。

  • 相关阅读:
    C# 全局热键
    Frida hook 初识
    xposed hook 复杂函数参数问题
    C# http post 中文乱码问题
    Fiddler 抓包https 问题
    C# HttpWebRequest 多线程超时问题
    Android Studio 无 Generate signed apk 菜单选项问题
    c#调用c++ dll const char* String类型转换问题。传值,与接收返回值问题
    C++中GB2312字符串和UTF-8之间的转换
    The underlying connection was closed: An unexpected error occurred on a send
  • 原文地址:https://www.cnblogs.com/lexus/p/2983711.html
Copyright © 2011-2022 走看看