C 是否有一种可移植的方法可以从类似套接字的文件描述符中丢弃大量可读字节?

C 是否有一种可移植的方法可以从类似套接字的文件描述符中丢弃大量可读字节?,c,sockets,unix,io,zero-copy,C,Sockets,Unix,Io,Zero Copy,是否有一种可移植的方法可以丢弃套接字中的大量传入字节,而不将它们复制到用户空间?在常规文件中,我可以使用lseek(),但在套接字中,这是不可能的。我有两个可能需要它的场景: 记录流到达文件描述符(可以是TCP、SOCK_流类型的UNIX域套接字或潜在的管道)。每个记录前面都有一个固定大小的标题,指定其类型和长度,后面是可变长度的数据。我想首先读取头,如果它不是我感兴趣的类型,我只想丢弃下面的数据段,而不将它们传输到用户空间中的虚拟缓冲区中 长度可变且不可预测的记录流到达文件描述符。由于异步性质

是否有一种可移植的方法可以丢弃套接字中的大量传入字节,而不将它们复制到用户空间?在常规文件中,我可以使用
lseek()
,但在套接字中,这是不可能的。我有两个可能需要它的场景:

  • 记录流到达文件描述符(可以是TCP、SOCK_流类型的UNIX域套接字或潜在的管道)。每个记录前面都有一个固定大小的标题,指定其类型和长度,后面是可变长度的数据。我想首先读取头,如果它不是我感兴趣的类型,我只想丢弃下面的数据段,而不将它们传输到用户空间中的虚拟缓冲区中

  • 长度可变且不可预测的记录流到达文件描述符。由于异步性质,当fd变得可读时,记录可能仍然不完整,或者它们可能是完整的,但当我尝试将固定数量的字节读入缓冲区时,下一条记录的一部分可能已经存在。我想在记录之间的确切边界处停止读取fd,这样我就不需要管理意外从fd读取的部分加载的记录。因此,我使用带有
    MSG_PEEK
    标志的
    recv()
    来读取缓冲区,解析记录以确定其完整性和长度,然后再次正确读取(从而实际从套接字中删除数据)到确切长度。这将复制两次数据-我希望通过简单地丢弃套接字中缓冲的数据来避免这种情况

  • 在Linux上,我认为可以通过使用
    splice()
    并将数据重定向到
    /dev/null
    来实现这一点,而无需将它们复制到用户空间。但是,
    splice()
    仅适用于Linux,并且在更多平台上支持的类似的
    sendfile()
    不能使用套接字作为输入。我的问题是:

  • 有没有一种可移植的方法来实现这一点?在其他Unix(主要是Solaris)上也可以使用的东西,这些Unix没有
    splice()

  • 在Linux上,将
    splice()
    -ing转换为
    /dev/null
    是一种有效的方法,还是一种浪费精力的方法

  • 理想情况下,我希望有一个
    ssize\u t discard(int fd,size\u t count)
    ,它只从内核中的文件描述符fd中删除可读字节的计数(即,不将任何内容复制到用户空间),在可阻塞fd上阻塞,直到丢弃请求的字节数,或返回非阻塞fd上成功丢弃的字节数或EAGAIN,就像
    read()
    一样。当然,在一个常规文件上前进搜索位置:)

    简短的回答是不,没有可移植的方法来实现这一点

    sendfile()
    方法是特定于Linux的,因为在实现它的大多数其他操作系统上,源必须是文件或共享内存对象。(我甚至还没有检查是否/在哪个Linux内核版本中,从套接字描述符到
    /dev/null
    sendfile()
    是受支持的。老实说,我非常怀疑这样做的代码。)

    查看例如Linux内核源代码,并考虑到
    ssize\u t discard(fd,len)
    与标准
    ssize\u t read(fd,buf,len)
    的差别有多小,显然可以添加这样的支持。人们甚至可以通过ioctl(比如,
    SIOCISKIP
    )添加它,以便于支持检测

    然而,问题是您设计了一种低效的方法,而不是在算法级别修复该方法,而是在寻找能够使您的方法性能更好的拐杖

    您知道,很难说明“额外副本”(从内核缓冲区到用户空间缓冲区)是实际性能瓶颈的情况。系统调用(用户空间和内核空间之间的上下文切换)的数量有时是有限的。如果您向上游发送了一个补丁,为TCP和/或Unix域流套接字实现了例如
    ioctl(socketfd,SIOCISKIP,bytes)
    ,他们会指出,通过不尝试获取您一开始不需要的数据,可以更好地提高性能。(换句话说,你试图做事情的方式本质上是低效的,你应该选择一种表现更好的方法,而不是制造拐杖来让这种方法更好地发挥作用。)

    在第一种情况下,接收由类型和长度标识符构成的结构化数据的进程希望跳过不需要的帧,最好通过修改传输协议来修复。例如,接收侧可以通知发送侧它感兴趣的帧(即,基本滤波方法)。如果你被一个愚蠢的协议所困扰,而这个协议由于外部原因而无法替代,那你只能靠自己了。(FLOSS开发者社区没有,也不应该仅仅因为有人抱怨而对愚蠢的决定负责。任何人都可以这样做,但他们需要以一种不需要其他人额外工作的方式来做。)

    在第二种情况下,您已经读取了数据。不要那样做。相反,使用足够大的用户空间缓冲区来容纳两个完整大小的帧。每当需要更多数据,但帧的开头已经超过缓冲区的中间位置时,
    memmove()
    首先从缓冲区开头开始的帧

    当您有一个部分读取的帧,并且您对剩余的
    N
    未读字节不感兴趣时,将它们读入缓冲区的未使用部分。始终有足够的空间,因为您可以覆盖当前帧已经使用的部分,并且其开始部分始终在缓冲区的前半部分内

    如果帧很小,比如说最大65536字节,那么应该使用可调的最大缓冲区大小。在大多数台式机和服务器上