socket 事件中怎么才算是可读可写呢?

最近在研究 IO 多路复用中的 epoll API

看到下面这段代码:

import socket
import select  #: epoll包含在select模块中
from datetime import datetime, timedelta, timezone


def get_utc_now_timestamp() -> datetime:
    return datetime.utcnow().replace(tzinfo=timezone.utc)


def timestamp_translate_gmt(timestamp: datetime) -> str:
    return timestamp.strftime('%a, %d %b %Y %H:%M:%S GMT')

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'

http_response_start_line: bytes = b'HTTP/1.0 200 OK\r\n'  # 状态行

http_response_body: bytes = b'Hello, world!'  # 响应正文

http_response_headers: bytes = b'\r\n'.join([
    f'Date: {timestamp_translate_gmt(get_utc_now_timestamp())}'.encode(
    ),
    b'Content-Type: text/plain',
    f'Content-Length: {len(http_response_body)}'.encode()
])+b'\r\n'

http_empty_line = b'\r\n'

http_response: bytes = (
    http_response_start_line+http_response_headers+http_empty_line+http_response_body
)

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)  #: 默认情况下,socket处于阻塞模式

epoll = select.epoll()       #: 创建一个epoll对象
# 在serversocket上注册读事件,读事件发生在serversocket每次接受socket连接时
epoll.register(serversocket.fileno(), select.EPOLLIN)

try:
    # 文件描述符(整数)与其对应的网络连接对象的映射
    connections = {}
    requests = {}
    responses = {}
    while True:
        # 查询epoll对象,看是否有感兴趣的事件发生
        # 参数`1`表明我们最多会等待1秒钟
        # 如果在这次查询之前有我们感兴趣的事件发生,这次查询将会立即返回这些事件的列表
        events = epoll.poll()
        # 事件是一个`(fileno, 事件code)`的元组
        for fileno, event in events:
            # 如果serversocket上发生了读事件,那么意味着有一个有新的连接
            if fileno == serversocket.fileno():
                connection, address = serversocket.accept()
                # 将新的socket设为非阻塞
                connection.setblocking(0)
                # 给新创建的socket注册读事件(EPOLLIN),表明将有数据请求
                epoll.register(connection.fileno(), select.EPOLLIN)
                connections[connection.fileno()] = connection
                # 收集各客户端来的请求
                requests[connection.fileno()] = b''
                responses[connection.fileno()] = http_response
            elif event & select.EPOLLIN:
                # 如果发生了读事件,从客户端读取数据
                requests[fileno] += connections[fileno].recv(1024)
                if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
                    responses[fileno]=http_response
                    epoll.modify(fileno, select.EPOLLOUT)
            elif event & select.EPOLLOUT:
                # 如果发生了写事件,向客户端发送响应
                # 每次向客户端发送一定长度的响应内容,每次都更新余下待发送的响应内容
                byteswritten = connections[fileno].send(responses[fileno])
                responses[fileno] = responses[fileno][byteswritten:]
                # 响应已经发送完毕,一次请求/响应周期完成,不再监听该socket的事件了
                if len(responses[fileno]) == 0:
                    epoll.modify(fileno, 0)
                # 告诉客户端,关闭连接
                connections[fileno].shutdown(socket.SHUT_RDWR)
            # `HUP`(挂起)事件表明客户端断开了连接
            elif event & select.EPOLLHUP:
                # 注销对断开客户端socket的事件监听
                epoll.unregister(fileno)
                # 关闭连接,服务端关闭
                connections[fileno].close()
                del connections[fileno]
finally:
    epoll.unregister(serversocket.fileno())
    epoll.close()
    serversocket.close()

所以怎么操作系统才算是一个事件是可读的呢?

判断该 socket 对应的 read 内核缓存区内的数据长度不为 0 吗?

  • 长度大于 0 为发生可读事件?
  • 长度等于 0 为没有可读事件发生?

那怎么判断 可写事件 是否发生呢?

操作系统总不能是通过判断该 socket 对应的内核 write 缓冲区内的长度情况吧!

看上面那段代码中,发生可读时间是要专门写代码,显性的调用 epoll.modify(fileno, select.EPOLLOUT) 等于可写事件是程序员手动设置的是吗?

参考文章:
边缘触发和水平触发的轮询 (epoll) 对象
How To Use Linux epoll with Python

阅读 1.3k
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题