> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20220708-ke-yan-xi-tong-yu-freebsd/socket.md).

# 进行中的工作/征求反馈：Socket 缓冲区

* 原文：[WPT/CFT: Socket Buffers](https://freebsdfoundation.org/wp-content/uploads/2022/08/WIP-CFT_sockets.pdf)
* 作者：**Tom Jones**、**Gleb Smirnoff**

历史上，BSD 网络栈有一个通用的套接字缓冲区实现，既用于 TCP、UDP、本地 IPC 套接字（即 UNIX 套接字）等。这些缓冲区当然有一些相似之处——它们用于缓冲数据，但也有一些根本差异。

有些是远程的，有些是本地的。有些支持数据流，有些支持数据报。随着 2015 年非阻塞 **sendfile** 的引入，我们提出了流发送缓冲区中“未就绪数据”的概念。然后，随着 2017 年 **KTLS** 的引入，它们变得更加复杂。与此同时，这些缓冲区仍然支持 POSIX 规定的 UNIX 控制消息。所以，我们得到了一段需要同时支持所有可能特性的通用代码——这变得非常复杂。它对修改变得非常脆弱，因为将一个套接字缓冲区修改为偏向某个协议可能会影响另一个协议的行为。例如，未就绪数据的更改需要一次大范围代码清理，而这些清理与 **sendfile** 完全无关，参见 git 提交 **cfa6009e364** 和 **0f9d0a73a49**。

在最后一个提交中，注意最后一段。**SCTP** 已经并行于 BSD 部分，独立实现了自己的套接字缓冲区（这一实现给了我许多关于当前工作的启示）。与此同时，在 FreeBSD 中，关于复制粘贴多少算坏、多少算好的观念，这些年来也发生了变化。我们有多个设备驱动程序，它们最初是从其他驱动程序复制粘贴而来，但很明显，差异逐渐累积，最终编辑一份副本就比在一份代码里同时支持两个相似实例更合理。接近套接字层的一个例子是两个 TCP 栈，它们也作为两个独立的源文件维护。总结来说，我们不再认为“一份代码适用于所有”是个好主意。最初，套接字代码让人第一眼、第二眼、第三眼都觉得难以下手。如果你查看当前的 **soreceive\_generic()** 和 **sosend\_generic()**，就会明白为什么。然而，做完这些工作后，我提出了一个计划，让我能够“拾起棍子，留下结构不倒”（<https://en.wikipedia.org/wiki/Pick-up_sticks>）。

1. 我们只有两种 **SOCK\_DGRAM** 套接字：**UNIX** 和 **UDP**。只需重新定义 **pru\_sosend** 和 **pru\_soreceive**，我们就有了 **PF\_UNIX/SOCK\_DGRAM** 的私有代码实现。参见 **34649582462** 和 **e3fbbf965e9**。这使得 **PF\_INET/SOCK\_DGRAM**（即 **UDP**）成为 **uipc\_socket.c** 中通用 **sockbuf** 代码支持的唯一数据报类型。
2. **sockbuf** 可以拆分为与事件调度交互的公共部分和执行实际缓冲的私有部分。（参见提交 a4fc41423f7 和 a7444f807ec）。这使得 **PF\_UNIX/SOCK\_DGRAM** 完全独立！这也使得 **PF\_INET/SOCK\_DGRAM**（即 UDP）成为 **struct sockbuf** 中遗留部分需要支持的唯一数据报类型。
3. 现在我们可以开始改进 **PF\_UNIX/SOCK\_DGRAM**，然后再从堆里抽出其他棍子。长期以来，一对多的 **unix/dgram** 套接字存在一个问题：一个写入者可能会使套接字溢出，实际上对其他方发起 DDoS 攻击。这里是我们历史上的尝试：2e89951b6f20 和 240d5a9b1ce76。我们可以让一对多的套接字为每个对等方维护一个独立的子缓冲区。参见 458f475df8e。此外，也可以使用无锁的入队/出队操作来提高 **unix/dgram** 的速度，但这次我不打算这样做。对我来说，**unix/dgram** 的每秒数据包性能并不是最关键的。
4. 回到套接字结构——**PF\_INET/SOCK\_DGRAM**（即 UDP）是唯一一个仍使用通用实现的数据报套接字，因此我们也可以将它私有化。很高兴看到 Robert Watson 已经为 UDP 准备了两个函数：**sosend\_dgram()** 和 **soreceive\_dgram()**。但 **soreceive\_dgram()** 还不能完全替代 **soreceive\_generic()**，借助 **soreceive\_generic()** 处理复杂情况的代码需要修复。
5. 现在我们可以开始研究 UDP 性能，并可能让它使用 **buf\_ring(9)** 替代链式 **mbuf** 列表吗？谁愿意承担这个任务？我们肯定关心 UDP 的每秒数据包处理性能，对吧？
6. 既然 **sosend\_generic()** 和 **soreceive\_generic()** 不再需要支持数据报类型，我们终于可以简化它们了！这可能是历史上第一次，这两个庞然大物将缩小而不是膨胀。
7. 这使得 **UNIX/STREAM** 成为唯一一个由通用代码支持并且具有控制数据的套接字类型。如果它获得私有实现，我们可以从 **sosend\_generic()** 和 **soreceive\_generic()** 中移除对控制数据的支持。此时，它们将进一步缩小！
8. 我们距离让 TCP 和 SCTP 独立出来已经非常接近了。需要注意的是，还有一些特殊的套接字，如 **netgraph** 等。目前，不清楚哪种计划更好：

* 要么选择 **–1**，将 TCP 与通用代码隔离，
* 要么选择 **–2**，将其他所有内容从通用代码中隔离并将通用代码重命名为 TCP。

无论哪种方式，最终目标是将 TCP 和 SCTP 的套接字缓冲区隔离开来，从而让我们可以放开手脚优化性能，而不必担心影响到其他部分。

在 D36002 中，Alexander Chernikov 现在分享了他为 **NETLINK** 套接字类型所做的工作。这个套接字可能会累积互联网路由表的完整视图，对应于需要从内核 **read(2)** 读取的数百兆字节数据。通用的套接字缓冲区实现需要分配大量 **mbuf** 来存储这些数据。这样的全视图获取可能导致 **mbuf** 短缺，而 **mbuf** 是路由器上的关键资源。但是，我们为什么一开始就使用 **mbuf** 呢？我们只需将数据从内核复制到用户态。新的 **NETLINK** 肯定会从协议特定的套接字缓冲区中受益，该缓冲区将数据从其特定的数据结构复制到用户态 I/O，而不需要使用 **mbuf**。

## 如何测试这些工作？

**PF\_UNIX/SOCK\_DGRAM** 的新实现已经成为 FreeBSD 主分支的一部分。非常欢迎各种反馈和测试，尤其是那些有大量 **syslog(3)** 流量，且曾受日志套接字溢出问题影响的人。

进一步的计划仍在进行中。我通常在工作的早期阶段将其分享到 <https://github.com/glebius/FreeBSD>，在更成熟时发布到 <https://reviews.FreeBSD.org>。欢迎在那里或通过电子邮件发表评论。

***

**Tom Jones** 希望基于 FreeBSD 的项目能获得应有的关注。他住在苏格兰东北部，提供 FreeBSD 咨询服务。

**Gleb Smirnoff** 在 17 岁时第一次接触 FreeBSD，并对其一见钟情。他曾在大大小小的公司工作，总是寻找能让他为开源做贡献的工作。现在，他在奈飞 OpenConnect 团队工作，正在用前所未有的强大 FreeBSD 服务器流量填满互联网。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20220708-ke-yan-xi-tong-yu-freebsd/socket.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
