如何写入非阻塞套接字使用epoll时

问题描述

在非阻塞套接字上使用epoll执行异步套接字IO时,读取似乎很简单:只需epoll_wait直到套接字准备好读取,然后读取直到获得EAGAIN / EWOULDBLOCK

但是您如何发送?大概执行一个大的send操作会阻塞,因此您将始终获得EAGAIN / EWOULDBLOCK。你打算做什么?

解决方法

大概内核将无法立即发送所有数据,这就是send成功返回已发送字节数的原因。重要的是,返回的数字可以(并且很可能会)小于调用中指定的总长度。如果根本没有字节可以发送,那么您只会得到EAGAIN/EWOULDBLOCK作为答案。因此,在发送大量数据时,您只需要正确地处理(很可能)并非一次发送所有数据的可能性。

注意:对于数据报(UDP)套接字,这是不同的,因为无法发送部分数据报。有关更多信息,请参见 POSIX: Return value from write() on UDP socket

以下示例假设一个SOCK_STREAM(TCP)套接字:

size_t len;
size_t sent;
char *buf;

/* ... prepare data ... */

while (sent < len) {
    ssize_t n;

    if ((n = send(sockfd,buf + sent,len - sent,flags)) == -1) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            continue;
        } else {
            perror("send failed");
            exit(1);
        }
    }
    
    sent += n;
}
,

PrintfType将发送尽可能多的字节,并返回其实际能够发送给内核的多少字节。

如果使用的是TCP套接字,请循环调用send/to(),直到所有字节发送完毕或报告send() / EAGAIN。在后一种情况下,请停止循环并将其余字节缓存在某个位置。

如果您使用的是UDP套接字,EWOULDBLOCK只能发送整个数据报,因此根本不要使用循环,并且如果报告了send/to() / EAGAIN,则缓存整个数据报。

只要EWOULDBLOCK指示套接字可写,就根据需要发送该套接字的所有已缓存字节/数据报,仅从缓存中删除成功的字节/数据报,直到清除缓存或epoll /报告了EAGAIN。在缓存中保留未发送的字节/数据报。

每当您需要发送新的TCP字节或新的UDP数据报时,如果套接字的缓存不为空,则将字节/数据报附加到缓存的末尾并继续操作,否则尝试立即发送字节/数据报,如上所述,缓存是否报告EWOULDBLOCK / EAGAIN