如何在Linux上获取异步套接字可读取的字节数?

3

我在Linux上用C++编写了一个简单的TCP/IP服务器。我使用异步套接字和epoll。当我获得EPOLLIN事件时,是否有可能找出有多少字节可供阅读?


为什么?recv()函数会告诉你,并且还会给你数据。 - user207421
有时候,知道应该为读取分配多大的缓冲区大小是非常方便的。例如,在kqueue中,字节数会在数据字段中返回。 - Pavel Davydov
在我看来,你不应该为读取分配缓冲区。那只会产生垃圾、堆碎片等问题。你应该使用本地分配的字节数组。 - user207421
但是如果接收到的数据应该在其他线程中处理怎么办呢?例如,我有一个服务器,其中一个线程接受网络连接、读取请求并将它们传输到另一个线程(甚至线程池),进行一些处理。 - Pavel Davydov
1个回答

8

来自 man 7 tcp

int value;
error = ioctl(sock, FIONREAD, &value);

或者用同义词SIOCINQ代替FIONREAD。
无论如何,我建议在非阻塞模式下使用recv循环,直到它返回EWOULDBLOCK。
更新:
根据您下面的评论,我认为这不是您问题的合适解决方案。
假设您的标头是8个字节,您只接收了4个字节;然后您的轮询/选择将返回EPOLLIN,您将检查FIONREAD,看到标头尚未完成,并等待更多字节。但是这些字节从未到达,因此每次调用轮询/选择时都会得到EPOLLIN,并且您有一个无操作繁忙循环。也就是说,轮询/选择是电平触发的。边缘触发函数也无法解决您的问题。
最好在每个连接上添加一个缓冲区,并将字节排队,直到您拥有足够的字节。这并不像看起来那么困难,而且效果要好得多。例如,类似于以下内容:
struct ConnectionData
{
    int sck;
    std::vector<uint8_t> buffer;
    size_t offset, pending;
};

void OnPollIn(ConnectionData *d)
{
    int res = recv(d->sck, d->buffer.data() + offset, d->pending);
    if (res < 0) 
        handle_error();
    d->offset += res;
    d->pending -= res;

    if (d->pending == 0)
        DoSomethingUseful(d);
}

每当您想获取一定数量的字节:

void PrepareToRecv(ConnectionData *d, size_t size)
{
    d->buffer.resize(size);
    d->offset = 0;
    d->pending = size;
}

谢谢你的回答!是的,我可以像这样使用recv或read,但我有一个二进制协议,在其头部存储数据包大小,所以我认为如果我检查头部是否已经可读,代码就可以变得更简单。 - Pavel Davydov
@PavelDavydov:确实,在这里使用缓冲区和累加可能会有点复杂。但我认为你可能在太过于简化了...请看我的更新。 - rodrigo
是的,无休止地获取EPOLLIN可能会成为一个问题,谢谢。我的服务器的行为方式与您推荐的类似,我只是认为也许可以更轻松、更干净地完成。 - Pavel Davydov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接