c - 有一种可移植的方法可以从类似套接字的文件描述符中丢弃许多可读字节吗?

标签 c sockets unix io zero-copy

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

  • 记录流到达文件描述符(可以是TCP,SOCK_STREAM类型的UNIX域套接字或可能是管道)。每个记录都以固定大小的标题开头,该标题指定其类型和长度,然后是长度可变的数据。我想先阅读 header ,如果它不是我感兴趣的类型,我只想丢弃下面的数据段,而不将它们转移到用户空间的虚拟缓冲区中。
  • 长度可变且不可预测的记录流到达文件描述符。由于异步特性,当fd变得可读时,记录可能仍然不完整,或者它们可能是完整的,但是当我尝试将固定数量的字节读入缓冲区时,下一条记录的一部分可能已经存在。我想停止在记录之间的确切边界处读取fd,因此不需要管理意外从fd中读取的部分加载的记录。因此,我使用带有recv()标志的MSG_PEEK读入缓冲区,解析记录以确定其完整性和长度,然后再次正确读取(因此实际上是从套接字中删除数据)到确切的长度。这将复制两次数据-我想通过简单地将套接字中缓冲的数据丢弃精确的数量来避免这种情况。

  • 在Linux上,我收集到可以通过使用splice()并将数据重定向到/dev/null而不将其复制到用户空间来实现这一点。但是,splice()仅适用于Linux,更多平台上支持的类似sendfile()不能使用套接字作为输入。我的问题是:
  • 有实现此目的的便携式方法吗?没有splice()的东西也可以在其他UNIX(主要是Solaris)上运行吗?
  • splice()-转换为/dev/null是在Linux上执行此操作的有效方法,还是会浪费精力?

  • 理想情况下,我希望有一个ssize_t discard(int fd, size_t count),它可以简单地从内核中文件描述符fd中删除可读字节的计数(即,不将任何内容复制到用户空间),在可阻塞的fd上进行阻塞,直到请求的字节数被丢弃,或者返回像read()一样,在非阻塞fd上成功丢弃了字节或EAGAIN。并在常规文件中提升搜索位置:)

    最佳答案

    简短的答案是不,没有便携式的方法可以做到这一点。
    sendfile()方法是特定于Linux的,因为在其他大多数实现它的OS上,源必须是文件或共享内存对象。 (我什至没有检查过是否/在哪个Linux内核版本中,支持从套接字描述符到sendfile()/dev/null。老实说,我对这样做的代码非常怀疑。)

    看Linux内核源代码,考虑到ssize_t discard(fd, len)与标准ssize_t read(fd, buf, len)的差异不大,显然可以添加这种支持。甚至可以通过ioctl(例如SIOCISKIP)添加它,以方便进行支持检测。

    但是,问题是您设计了一种效率低下的方法,而不是将方法固定在算法级别上,而是在寻找可以使方法性能更好的拐杖。

    您会看到,很难显示“额外副本”(从内核缓冲区到用户空间缓冲区)是实际性能瓶颈的情况。系统调用的数量(用户空间和内核空间之间的上下文切换)有时是。如果您向上游发送了补丁程序,例如对于TCP和/或Unix域流套接字的ioctl(socketfd, SIOCISKIP, bytes),他们会指出,通过首先不尝试获取不需要的数据,可以更好地实现希望实现的性能提高。 (换句话说,您尝试做事的方式本质上是效率低下的,而不是创建拐杖以使该方法更好地工作,您应该选择性能更好的方法。)

    在第一种情况下,通过固定传输协议(protocol)可以更好地解决接收由类型和长度标识符构成的结构化数据的进程,希望跳过不需要的帧的过程。例如,接收方可以通知发送方它感兴趣的帧(即基本过滤方法)。如果您因为愚蠢的协议(protocol)而陷入困境,而该协议(protocol)由于外部原因而无法替换,那您就该靠自己了。 (FLOSS开发人员社区不是,也不应该仅仅因为有人为之哭泣而负责维持愚蠢的决定。任何人都可以这样做,但是他们需要以不需要其他人额外努力的方式来这样做。也。)

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

    如果您有部分读取的帧,并且您不感兴趣的左上角有N未读字节,则将它们读入缓冲区的未使用部分。总是有足够的空间,因为您可以覆盖当前帧已经使用的部分,并且它的开始始终在缓冲区的前一半之内。

    如果帧较小,例如最大为65536字节,则应使用可调参数来获得最大缓冲区大小。在大多数具有高带宽流套接字的台式机和服务器计算机上,类似2 MiB(2097152字节或更多)的东西更为合理。这并不是浪费太多的内存,但是您很少执行任何内存副本(当您这样做时,它们往往很短)。 (您甚至可以优化内存移动,以便仅复制,对齐全部高速缓存行,因为在缓冲区的开头几乎不留一条高速缓存行的垃圾是微不足道的。)

    我使用大型数据集(包括文本形式的分子数据,其中的记录由换行符分隔,并且使用用于解析十进制整数或浮点值的自定义解析器以提高性能)来执行HPC,并且这种方法在实践中确实行之有效。简而言之,跳过缓冲区中已经存在的数据并不是您需要优化的。与仅避免执行您不需要的事情相比,这是微不足道的开销。

    还有一个问题,您希望通过该操作来优化什么:使用的CPU时间/资源,或整个任务中使用的挂钟。他们是完全不同的东西。

    例如,如果您需要对某个文件中的大量文本行进行排序,则只需将整个数据集读取到内存,构造指向每行的指针数组,对指针进行排序,最后写入,就可以使用最少的CPU时间每行(使用内部缓冲和/或POSIX writev(),因此您无需为每条单独的行执行write()系统调用)。

    但是,如果您希望最小化使用的挂钟时间,则可以使用二进制堆或平衡的二进制树来代替指针数组,并完全读取堆或按顺序插入每一行,以便在最后一行终于读完了,您已经按正确的顺序排列了行。这是因为与使用任何可靠的排序算法对存储I/O进行排序(对于所有病理输入情况而言,除了单个字符行之类的东西)相比,存储I/O花费的时间更长!内联工作(随着数据的输入)的排序算法通常不像脱机工作(在完整的数据集上)的CPU效率那样高,因此最终会花费更多的CPU时间。但是由于CPU工作是在浪费整个数据集加载到内存的时间进行的,因此可以在更短的挂钟时间内完成工作!

    如果有需要和兴趣,我可以提供一个实际的例子来说明这些技术。但是,绝对没有魔术,任何C程序员都应该能够自己实现这些(缓冲方案和排序方案)。 (我确实考虑过使用诸如Linux man pages online和Wikipedia文章之类的资源以及诸如binary heaps上的伪代码来“独立完成”。 (如果有人或某种资源可以帮助您找到好的,健壮的方法来做)。

    关于c - 有一种可移植的方法可以从类似套接字的文件描述符中丢弃许多可读字节吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51218113/

    相关文章:

    c - 将元素添加到包含自定义数据类型的简单链接列表

    java - 为什么 Android 中的这个 ServerSocket(通过 USB/ADB 连接到 PC)只接受一次连接?

    java - 什么可能导致套接字 ConnectException : Connection timed out?

    Java 网络 : evented Socket/InputStream

    linux - 如何退出密码模式?

    ios - 在iOS/OSX上读取带有Windows和Unix行尾的文件

    linux - unix下文件转换为DAT

    c - Linux C socket服务器printf问题

    c - 一个数组的最后一个元素被下一个数组的第一个元素覆盖

    将 2 的补码转换为整数并计算 rms 值