TCP的发送过程由滑动窗口控制,而滑动窗口的大小受限于发送窗口和拥塞窗口,拥塞窗口由拥塞控制算法的代表,而发送窗口是流量控制算法的代表,这篇笔记记录了发送窗口相关的内容,包括发送窗口的初始化、更新、以及它是如何影响数据发送过程的。
1. 概述
TCP的发送窗口可以用下图表示:
如图所示,TCB中有三个成员和发送窗口强相关。
struct tcp_sock {
...
//下一个要发送的序号,即序号等于snd_nxt的数据还没有发送
u32 snd_nxt; /* Next sequence we send */
//已经发送,但是还没有被确认的最小序号,注意序号等于snd_una的数据已经发送,
//最想收到的确认号要大于snd_una。但是有一个特殊情况,如果发送的所有数据都
//已经被确认,那么snd_una将等于下一个要发送的数据,即snd_una代表的数据还
//没有发送,见下面tcp_ack()更新snd_una就可以理解这一点了
u32 snd_una; /* First byte we want an ack for */
//发送窗口大小,以字节为单位,来源于输入段首部的窗口字段,即对端接收缓冲区的剩余大小
u32 snd_wnd; /* The window we expect to receive */
//记录到目前为止对端通告过的窗口的最大值,可以代表对端接收缓冲区的最大值
u32 max_window; /* Maximal window ever seen from peer */
//写系统调用一旦成功返回,说明数据一被TCP协议接收,这时就要为每一个数据分配一个序号,
//write_seq就是下一个要分配的序号,其初始值由secure_tcp_sequence_number()基于
//算法生成。注意等于write_seq的序号还没有被分配
u32 write_seq; /* Tail(+1) of data held in tcp send buffer */
...
};
2. snd_una和snd_wnd的更新
snd_una是发送窗口的左边界,如果该字段更新,即使发送窗口大小snd_wnd没有发生变化,整个发送窗口也会前移,这样从流量控制的角度,就可以发送更多的数据(是否真的可以发送,还要考虑拥塞窗口等其它因素)。
2.1 初始化
可以想的到,snd_una的初始化一定发生在第一个数据段发送过程中,而snd_wnd的初始化应该是发生在第一个输入段处理过程中,所以需要客户端和服务器端分开来看。
2.1.1 客户端初始化
客户端对snd_una的初始化当然是发生在SYN段的发送过程中,相关代码如下:
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
...
//选择初始发送序号
if (!tp->write_seq)
tp->write_seq = secure_tcp_sequence_number(inet->saddr,
inet->daddr,
inet->sport,
usin->sin_port);
...
}
static void tcp_connect_init(struct sock *sk)
{
...
//发送窗口大小要从输入段首部的窗口字段获取,这时还没有任何输入段,先初始化为0