kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

拥塞窗口cwnd的理解

http://blog.csdn.net/linweixuan/article/details/4353015

开始的时候拥塞窗口是1,发一个数据包等ACK回来 cwnd++即2,这个时候可以发送两个包,发送间隔几乎没有, 对方回的ACK到达发送方几乎是同时到达的.一个RTT来回,cwnd就翻倍,cwnd++,cwnd++即4了.如此下去,cwnd是指数增加.

snd_cwnd_clamp这个变量我们可以不管,假定是一个大值.窗口到了我们设置的门限,snd_cwnd不在增加 而通过snd_cwnd_cnt变量来计数增加,一直增加到大过cwnd值,cwnd才加1,然后snd_cwnd_cnt重新计数, 通过snd_cwnd_cnt延缓cwnd计数,由于TCP是固定大小报文,每一个snd_cwnd代表了一个报文段的增加,snd_cwnd_cnt则看成byte的增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void tcp_cong_avoid(struct send_queue* sq)
{
	/* In saft area, increase*/
	if (sq->snd_cwnd <= sq->snd_ssthresh){
		if (sq->snd_cwnd < sq->snd_cwnd_clamp)
			sq->snd_cwnd++;
	}
	else{ 
		/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd */
		if (sq->snd_cwnd_cnt >= sq->snd_cwnd) {
			if (sq->snd_cwnd < sq->snd_cwnd_clamp)
				sq->snd_cwnd++;
			sq->snd_cwnd_cnt = 0;
		} else
			sq->snd_cwnd_cnt++;
	} 
}

snd_cwnd 还没到达门限不断增加snd_cwnd++
snd_cwnd++ | <–snd_ssthresh ^

到达了snd_ssthresh转入拥塞避免,这个阶段由变量snd_cwnd_cnt来控制

转入拥塞,由于snd_cwnd_cnt从0开始小于snd_ssthresh,即从snd_ssthresh那个点开始计数, 一旦计数达到snd_cwnd拥塞窗口的值,但是还小过牵制snd_cwnd_clamp值

1
2
3
4
5
6
7
8
9
10
11
12
13
                          snd_cwnd_clamp
                                 ^
    snd_cwnd++                   |            | <--snd_ssthresh
                                              ^
                                    snd_cwnd++        
                                                          snd_cwnd_clamp
                                                                 ^
                                snd_cwnd_cnt++                   |            | <--snd_ssthresh
                                                                              ^
                                               0      --->       snd_cwnd_cnt++
 
 
               <------                       时间                      ------->

TCP接收窗口的调整算法

TCP接收窗口的调整算法(上)
TCP接收窗口的调整算法(中)
TCP接收窗口的调整算法(下)


TCP接收窗口的调整算法(上)

我们知道TCP首部中有一个16位的接收窗口字段,它可以告诉对端:我现在能接收多少数据。TCP的流控制主要就是通过调整接收窗口的大小来进行的。

本文内容:分析TCP接收窗口的调整算法,包括一些相关知识和初始接收窗口的取值。

内核版本:3.2.12

数据结构

以下是涉及到的数据结构。

1
2
3
4
5
6
7
8
9
10
struct tcp_sock {  
	...  
	/* 最早接收但未确认的段的序号,即当前接收窗口的左端*/  
	u32 rcv_wup; /* rcv_nxt on last window update sent */  
	u16 advmss; /* Advertised MSS. 本端能接收的MSS上限,建立连接时用来通告对端*/  
	u32 rcv_ssthresh; /* Current window clamp. 当前接收窗口大小的阈值*/  
	u32 rcv_wnd; /* Current receiver window,当前的接收窗口大小*/  
	u32 window_clamp; /* 接收窗口的最大值,这个值也会动态调整*/  
	...  
}
1
2
3
4
5
6
7
struct tcp_options_received {  
	...  
		snd_wscale : 4, /* Window scaling received from sender, 对端接收窗口扩大因子 */  
		rcv_wscale : 4; /* Window scaling to send to receiver, 本端接收窗口扩大因子 */  
	u16 user_mss; /* mss requested by user in ioctl */  
	u16 mss_clamp; /* Maximal mss, negotiated at connection setup,对端的最大mss */  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/** 
 * struct sock - network layer representation of sockets 
 * @sk_rcvbuf: size of receive buffer in bytes 
 * @sk_receive_queue: incoming packets 
 * @sk_write_queue: packet sending queue 
 * @sk_sndbuf: size of send buffer in bytes 
 */  
struct sock {  
	...  
	struct sk_buff_head sk_receive_queue;  
	/* 表示接收队列sk_receive_queue中所有段的数据总长度*/  
#define sk_rmem_alloc sk_backlog.rmem_alloc  
  
	int sk_rcvbuf; /* 接收缓冲区长度的上限*/  
	int sk_sndbuf; /* 发送缓冲区长度的上限*/  
  
	struct sk_buff_head sk_write_queue;  
	...  
}  
  
struct sk_buff_head {  
	/* These two members must be first. */  
	struct sk_buff *next;  
	struct sk_buff *prev;  
	__u32 qlen;  
	spinlock_t lock;  
};

TCP的核心系列 — SACK和DSACK的实现

TCP的核心系列 — SACK和DSACK的实现(一)
TCP的核心系列 — SACK和DSACK的实现(二)
TCP的核心系列 — SACK和DSACK的实现(三)
TCP的核心系列 — SACK和DSACK的实现(四)
TCP的核心系列 — SACK和DSACK的实现(五)
TCP的核心系列 — SACK和DSACK的实现(六)
TCP的核心系列 — SACK和DSACK的实现(七)


TCP的核心系列 — SACK和DSACK的实现(一)

TCP的实现中,SACK和DSACK是比较重要的一部分。

SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护。

tcp_ack()处理接收到的带有ACK标志的数据段时,如果此ACK处于慢速路径,且此ACK的记分牌不为空,则调用
tcp_sacktag_write_queue()来根据SACK选项标记发送队列中skb的记分牌状态。

笔者主要分析18和37这两个版本的实现。
相对而言,18版本的逻辑清晰,但效率较低;37版本的逻辑复杂,但效率较高。

本文主要内容:18版tcp_sacktag_write_queue()的实现,也即18版SACK和DSACK的实现。

18版数据结构

1
2
3
4
5
/* 这就是一个SACK块 */
struct tcp_sack_block {
	u32 start_seq;  /* 起始序号 */
	u32 end_seq;    /* 结束序号 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
struct tcp_sock {
	...
	/* Options received (usually on last packet, some only on SYN packets). */
	struct tcp_options_received rx_opt;
	...
	struct tcp_sack_block recv_sack_cache[4]; /* 保存收到的SACK块,用于提高效率*/
	...
	/* 快速路径中使用,上次第一个SACK块的结束处,现在直接从这里开始处理 */
	struct sk_buff *fastpath_skb_hint;
	int fastpath_cnt_hint;  /* 快速路径中使用,上次记录的fack_count,现在继续累加 */
	...

};
1
2
3
4
5
6
7
8
9
10
struct tcp_options_received {
	...
	u16 saw_tstamp : 1,    /* Saw TIMESTAMP on last packet */
		tstamp_ok : 1,     /* TIMESTAMP seen on SYN packet */
		dsack : 1,         /* D-SACK is scheduled, 下一个发送段是否存在D-SACK */
		sack_ok : 4,       /* SACK seen on SYN packet, 接收方是否支持SACK */
		...
	u8 num_sacks;          /* Number of SACK blocks, 下一个发送段中SACK块数 */
	...
};