我试图了解操作系统通常如何实现网络请求的重组。根据我的最佳理解,以下内容是正确的:
HTTP 请求是在应用层使用某个 HTTP 库发出的。该 HTTP 库实际上是操作系统实现的某个套接字的包装器。
使用 HTTP 请求的目标和源来实例化“传输”套接字。
使用设备的 IP 地址和随机(空闲)端口来实例化“接收”套接字。
HTTP 消息是使用“传输”套接字的文件描述符来“发送”的。
套接字的“发送”方法将消息发送到操作系统的 TCP 实现。
TCP 实现对 HTTP 消息进行分段,并在前面添加目标端口(在实例化套接字时提供)和源端口。(我认为这是根据实现以某种方式传递的。)一旦 HTTP 消息被分段并且 TCP 标头附加到它,TCP 段就会传递给操作系统的 IP 实现。
TCP 段前面附加有 IP 标头。实例化套接字时提供了 IP 目标地址。(我再次假设源 IP 地址是根据实现情况传递的。)
然后用以太网头包装 IP 数据包,发送到路由器,发送到服务器,服务器处理请求,并发回响应。
这就是我对重组过程中究竟发生了什么事情的理解失败的地方。
一旦 IP 数据包返回到设备,响应如何返回到“接收”套接字的接收缓冲区?
显然,标头会丢失,但要采取什么步骤才能将其返回到“接收”套接字的接收缓冲区,然后从套接字返回到应用程序?
PS 我希望有更多技术实现细节,而不仅仅是“IP 重新组装”或“TCP 重新组装”并将其传递到下一层。我希望了解这究竟是如何发生的,而不仅仅是理论上的(尽管我确实知道这是特定于操作系统的)。
编辑:
为了更清楚地说明该主题,我要指出,我对套接字和任何套接字方法的引用均指的是 Linux 操作系统。
答案1
Mike Penningtion 的发现很不错(在评论) 查找有关请求沿着网络堆栈向下和向上的路径的详细技术描述(特定于 Linux OS 2005)。
请注意,虽然列出的步骤不如上面提到的文档那么详细,但大致步骤如下:
操作系统有一个专用于以太网 rx 端口的文件描述符,
The rx ring is a ring in the kernel memory where the network card transfers the incoming packet through DMA. The raw data which is stored in the rx ring structure is either copied into a sk buff structure.
然后,它会触发 ISR,将数据包移至网络层。请注意,这会选择是处理数据包还是转发数据包(这很有趣,因为我猜想这也许是启用转发的工作原理,例如对于 VPN)
如果有效,则转到 IP 层。检查其协议(来自 IP 报头),如果协议是 TCP,则调用函数tcp v4 rcv
,从而转到 TCP 层。
这部分至关重要:
The next step for this function is to find an open socket for this incoming packet,
这是通过调用来完成的tcp v4 lookup
,如以下代码段所示:
sk = __tcp_v4_lookup(skb->nh.iph->saddr, th->source,
skb->nh.iph->daddr, ntohs(th->dest),
tcp_v4_iif(skb));
本质上,我收集到有一个 LUT 将 TCP 套接字映射到套接字连接的源和目标地址/端口,如该函数调用所示。
如果存在有效套接字,则数据将被放入tcp_data_queue
堆栈并继续向上传输,以供应用程序使用。
答案2
(来自评论)
我得到了封装。假设操作系统有一个专用的文件描述符,它在专用的以太网 Rx 端口上打开。它从帧中剥离以太网头并将其传递给某个 IP 解析函数,以剥离目标 IP。它将其放入某个结构中,然后将其和结构发送到 TCP 层。在这里,它将其从目标端口剥离并将其放入同一个结构中。在这里,必须根据端口和 IP 映射到套接字文件描述符,以将数据放入分配给文件描述符的缓冲区中。这是我最好的尝试。但我想知道实际发生了什么。
您正在寻找的相当旧的版本在这里:Linux 网络堆栈。我试图找到比 2005 年描述更新的内容,因为自那时以来,Linux 的一些核心发生了变化。大多数套接字缓冲信息都保存在一个名为的结构中sk_buff
,这是一个 Linux 结构,其中保存指向 TCP 套接字句柄的指针,以及所有套接字读/写缓冲区。
当 Linux NIC 驱动程序从给定套接字上的 NIC 接收信息时,它会查找sk_buff
实例(名为skb
)的缓冲区空间,并将数据转储到指向的缓冲区中skb
。