TCP、UDP 和 SCTP 输出介绍

    每个 TCP 套接字都有一个发送缓冲区,可以使用 SO_SNDBUF 套接字选项来更改该缓冲区的大小。下图展示了某个应用进程写数据到一个 TCP 套接字时发生的步骤。

    当某个应用进程调用 write 时,内核就从其缓冲区中复制所有数据到所写套接字的发送缓冲区。若该套接字的发送缓冲区容不下该应用进程的所有数据(比如应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区中已有其他数据),该应用进程就会被投入睡眠(这里是说套接字阻塞的情况)。内核将不从 write 系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个 TCP 套接字的 write 调用成功返回仅仅表示可以重新使用原来的应用进程缓冲区,并不表明对端的 TCP 或应用进程已接收到数据。
    这一端的 TCP 提取套接字发送缓冲区中的数据并把它们发送给对端 TCP,其过程基于 TCP 数据传送的所有规则。对端 TCP 必须确认收到的数据,本端 TCP 只有在收到对端发来的 ACK 确认后才能从套接字发送缓冲区中丢弃已确认的数据。TCP 必须为已发送的数据保留一个副本,直到它被对端确认为止。
    本端 TCP 以 MSS 大小的或更小的块把数据传递给 IP,同时给每个数据块安上一个 TCP 首部以构成 TCP 分节,其中 MSS 或是由对端通告的值,或是 536(若对端未发送一个 MSS 选项,536 是 IPv4 最小重组缓冲区字节数 576 减去 IPv4 首部字节数 20 和 TCP 首部字节数 20 的结果)。IP 给每个 TCP 分节安上一个 IP 首部以构成 IP 数据报,并按照其目的 IP 地址查找路由表以确定外出接口,然后把数据报传递给相应的数据链路。IP 可能在把数据报传递给数据链路之前将其分片,不过 MSS 选项的目的之一就是试图避免分片,较新的实现还使用了路径 MTU 发现功能。每个数据链路都有一个输出队列,如果该队列已满,则新到的分组将被丢弃,并沿协议栈向上返回一个错误。TCP 将注意到该错误,并在以后某个时刻重传相应的分节。应用进程并不知道这种暂时的情况。

    接下来再来讨论 UDP 的输出情况。下图展示了某个应用进程写数据到一个 UDP 套接字时发生的步骤。

    这里我们用虚线框展示套接字发送缓冲区,因为它实际上并不存在。任何 UDP 套接字都有发送缓冲区大小(可使用 SO_SNDBUF 套接字选项更改),不过它仅仅是可写到该套接字的 UDP 数据报的大小上限。如果一个应用进程写一个大于套接字发送缓冲区大小的数据报,内核将返回该进程一个 EMSGSIZE 错误。既然 UDP 是不可靠的,它不必保存应用进程数据的一个副本,因此无需一个真正的发送缓冲区(应用进程的数据在沿协议栈向下传递时,通常被复制到某种格式的一个内核缓冲区中,然而当该数据被发送之后,这个副本就被数据链路层丢弃了)。
    这一端的 UDP 简单地给来自用户的数据报安上它的 8 字节的首部以构成 UDP 数据报,然后传递给 IP。IPv4 或 IPv6 给 UDP 数据报安上相应的 IP 首部以构成 IP 数据报,执行路由操作确定外出接口,然后或者直接把数据报加入数据链路层输出队列(如果适合于 MTU),或者分片后再把每个片段加入数据链路层的输出队列。如果某个 UDP 应用进程发送大数据报,那么它们相比 TCP 应用数据更有可能被分片,因为 TCP 会把应用数据划分成 MSS 大小的块,而 UDP 却没有对等的手段。
    从写一个 UDP 套接字的 write 调用成功返回表示所写的数据报或其所有片段已被加入数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或它的某个片段,内核通常会返回一个 ENOBUFS 错误给它的应用进程(不过有些 UDP 的实现并不返回这种错误,这样甚至数据报未经发送就被丢弃的情况应用进程也不知道)。

    下面再来看某个应用进程写数据到一个 SCTP 套接字中时发生的步骤。

    因为 SCTP 是与 TCP 类似的可靠协议,所以从写一个 SCPT 套接字的 write 调用成功返回也仅仅是表示可以重新使用原来的应用进程缓冲区,并不表明对端的 SCTP 或应用进程已接收到数据。这一端的 SCTP 提取套接字发送缓冲区的数据并把它发送给对端 SCTP,其过程基于 SCTP 数据传送的所有规则。本端 SCTP 必须等待 SACK,在累积确认点超过已发送的数据后,才可以从套接字缓冲区中删除该数据。

猜你喜欢

转载自aisxyz.iteye.com/blog/2387133