TCP是可靠的传输服务,主要解决可下面两个问题:
1、stream按序,不丢的到达接收端TCP
2、速率控制(接收方和网络原因)
本文讲解了TCP协议的一些原理性东西
一、3次握手
连接建立时需要3次握手,3次握手需要商议请求方和服务方的编号以及其他的字段,如MSS和发送窗口。
To avoid fragmentation in the IP layer, a host must specify the maximum segment size as equal to the largest IP datagram that the host can handle minus the IP header size and TCP header sizes.
The default TCP Maximum Segment Size is 536.[6] Where a host wishes to set the maximum segment size to a value other than the default, the maximum segment size is specified as a TCP option, initially in the TCP SYN packet during the TCP handshake. The value cannot be changed after the connection is established.
默认MSS是536(如果没有协商),但是为了能最大化的利用网络资源,同时避免ip层的分片,最好设置MSS为IP层和MAC层能容纳的数据部分大小,当然MSS的协商不只是由发送方pac和接受方server决定,还有中间的路由器 和其他的网络等。MSS的协商受数据链路层和网络层的影响。如下图说明了MSS的协商:
MSS的协商过程(在3次握手的时候协商,之后不能更改)
MSS是可选的,而且只是在建立连接3次握手的时候协商,建立连接之后MSS就不能再次改变了。
TCP协议建立连接的三次握手过程中的第三次握手允许携带数据,且第三次如果没有发送数据是不会占用编号的,前两次是不能发送数据的,前两次的编号会占用。参见http://www.0xffffff.org/2015/04/15/36-The-TCP-three-way-handshake-with-data/
二、四次握手
tcp的两端都可以先发送FIN,也就是都可以主动结束连接。FIN的编号会被占用。
某段发送了FIN之后就不能再发送数据了,但是还能接收数据。也就是两端都必须主动的去关闭连接。
MSL 是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”
最后关闭的时要启动定时器,定时器默认是2MSL(实际状态不是closed,是time_wait。)
为什么需要time_wait?
因为最后的ack报文可能丢失,而导致另一端收不到ack报文,而不能正常关闭。所以等待2msl,持续的关闭。
三、拥塞控制
1、 拥塞窗口(cwnd)和慢启动阈值(ssthresh)的默认值和修改
The default congestion avoidance algorithm in Linux 2.6.19+, CUBIC, sets the initial ssthresh to 0 (see/sys/module/tcp_cubic/parameters/initial_ssthresh
), so initially congestion avoidance is used, unless an ssthresh metric higher than cwnd is cached from a previous connection
linux 3.0以前,内核默认的initcwnd比较小,MSS为1460时,初始的拥塞控制窗口为3。
linux3.0以后,采取了Google的建议,把初始拥塞控制窗口调到了10。
Google's advice :《An Argument for Increasing TCP's Initial Congestion Window》
The recommended value of initcwnd is 10*MSS.
内核版本:linux-2.6.37
dst_entry
目的入口dst_entry反映了相邻的外部主机在主机内部的一种映像。
A dst_entry corresponds to the destination host bound to the socket.
A dst_entry object stores a lot of data used by the kernel whenever it sends a packet to
the corresponding remote host.
- __u32 tcp_init_cwnd(struct tcp_sock *tp, struct dst_entry *dst)
- {
- /* 取出路由中的initcwnd */
- __u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);
- /* 如果没有路由信息的话 */
- if (! cwnd)
- cwnd = rfc3390_bytes_to_packets(tp->mss_cache);
- /* 不能超过snd_cwnd的最大值:snd_cwnd_clamp */
- return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
- }
根据MSS来决定initcwnd:
(1)MSS <= 1095,initcwnd = 4
(2)1095 < MSS <= 2190,initcwnd = 3
(3)MSS > 2190,initcwnd = 2
- /* Convert RFC3390 larger initial window into an equivalent number of packets.
- * This is based on the numbers specified in RFC 6861, 3.1.
- */
- static inline u32 rfc3390_bytes_to_packets(const u32 smss)
- {
- return smss <= 1095 ? 4 : (smss > 2190 ? 2 : 3);
- }
一般我们的MSS为1460,所以内核默认的TCP初始拥塞控制窗口为3。
内核版本:linux 3.2.12
内核初始的慢启动阈值(ssthresh)
- /* 初始的慢启动阈值为无穷大*/
- #define TCP_INFINITE_SSTHRESH 0x7fffffff
- /* 根据慢启动阈值来判断是否处于初始的慢启动阶段*/
- static inline bool tcp_in_initial_slowstart(const struct tcp_sock *tp)
- {
- return tp->snd_ssthresh >= TCP_INFINITE_SSTHRESH;
- }
内核初始的拥塞窗口(initcwnd)
- #define TCP_INIT_CWND 10
- __u32 tcp_init_cwnd(const struct tcp_sock *tp, const struct dst_entry *dst)
- {
- /* 取出路由中的initcwnd */
- __u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);
- /* 如果没有相关的路由,则把初始值设为10 */
- if (! cwnd)
- cwnd = TCP_INIT_CWND;
- /* 不能超过snd_cwnd的最大值:snd_cwnd_clamp */
- return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
- }
设置初始的ssthresh和cwnd
(1)ip route方法,对通过此路由的TCP连接有效。
设置:ip route change default via <gateway> dev <eth0> initcwnd <value1> ssthresh <value2>
查看:ip route show
注意:In order to make it effective after a reboot, you can place above line in /etc/rc.local.
(2)sysctl方法,对所有的TCP连接有效。
在内核中增加一个控制initcwnd的proc参数,/proc/sys/net/ipv4/tcp_initcwnd。
ip route是通过netlink来修改dst_entry中RTAX_INITCWND对应的值,而sysctl则可以直接在内核中
增加一个变量,它们都需要通过tcp_init_cwnd()来改变initcwnd。
2、拥塞处理过程
1、乘法减小 当网络出现问题(超时或者出现3次重复的ACK)会将慢开始阈值减半
2、慢开始 收到一个ACK,cwnd=cwnd+1
3、拥塞避免 当cwnd>慢开始门限的时候cwnd=cwnd+1/cwnd
4、快重传:接收端收到不是安序的报文时回复的ACK是想要的那个报文的序号,当发送端收到3个重复的ACK时立刻进行重传而不等计时器到时
5、快恢复 当发送端连续收到3个重复的ACK时,进行乘法减小(慢开始门限/=2),同时设置cwnd/=2,执行拥塞避免算法。
6、当出现超时的时候,慢开始门限/=2,cwnd=1,执行慢开始。
四、窗口
1、接收端缓冲区窗口
2、拥塞窗口 (默认为10ms) cwnd
3、慢开始门限(默认为16)
4、真正的发送窗口 下面3个值的最小值:1、发送缓存区的数据 2、拥塞窗口 3、接收端缓冲区窗口
五、TCP的可靠传输机制
速率控制、拥塞控制、可靠
1、编号
2、确认
3、定时
4、握手
5、拥塞控制(慢开始门限,慢开始,拥塞避免,快重传,快恢复,拥塞窗口)
6、窗口
7、服务器响应给客户端的接收窗口为0,这时客户端需要启动定时器去不断的到服务器询问现在的接收窗口是否已经大于0。
定时器参见http://www.cnblogs.com/YDDMAX/p/5402925.html