每个TCP报文段由固定的20Byte头部组成,TCP报文头部 选项可以跟在固定标头之后。 带有标头,使其最多可以标记 65535 个数据字节。
TCP 连接需要完成两项工作一是做好发送数据前的准备工作(即双方都知道对方准备好了) 二是完成序列号(sequence number )的同步,这个序列号在握手的过程中被发送和确认
根据上述如果进行两次连接 Server 在接收到Client的SYN请求时直接进入到Establishment状态,并且给Client发送 SYN和ACK 包。但是这个SYN+ACK 包在传输的过程中可能丢失,因此Client无法准确的知道Server是否建立起连接。因此Client会忽略Server发送过来数据包,只等连接确认包。而Server在发送数据包分组超时后会重复发送超时的数据包,这样就造成了死锁。
上述回答是通过一个博客看到了,但是我从RFC793 [page 32] 文档看到了如下的说明:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion. To deal with this, a special control message, reset, has been devised. If the receiving TCP is in a non-synchronized state (i.e., SYN-SENT,SYN-RECEIVED), it returns to LISTEN on receiving an acceptable reset. If the TCP is in one of the synchronized states (ESTABLISHED,FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), it aborts the connection and informs its user. We discuss this latter case under "half-open" connections below.
大概意思就是说三次握手是为了防止旧的重复初始化连接造成混乱
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
字节
而不是报文
在TCP报文头部序列号(Sequence Number)所占32bit 也就是最多能表达的字节也就是2^32 个也就是4GB,当如果一次TCP连接传输的数据超过4G时,在1-4G传输过程中,如果数据报传输有延迟晚到底,可能会让接收端分不清那个是先用这个序列号的,那个报文是后用这个序号的
这个也就是PAWS问题(Protect Against Wrapped Sequence numbers )防止序列号回绕
下图是RTT的图示,简单的来说就是一个报文从发送到接收到响应的时间,但是下面是理想的情况,现实中的计算比这复杂多了
RTO是超时重传时间,RTO设置应该比RTT略大,如果设置太小,可能数据报已经被ACK了,但是由于超时重发时间设置的太短,就又重发了,重发了本应该不用重发的数据报。如果设置的太大就会导致传输的效率太低,如果ACK数据报丢失,需要很长时间来进行重发。
cwnd: 拥塞窗口大小
rwnd: 接收方窗口大小
慢启动的思想就是为发送方增加了一个拥塞窗口记为cwnd,拥塞窗口指的是接收到ACK后,发送端还能发送最大的MSS数,发送方的窗口大小 = Min {cwnd,rwnd}。
慢启动算法就是,每收到一个ACK (eg.也就是经过一个RTT) cwnd的大小*2 ,比如最开始cwnd的大小是 initcwnd,当接收到该报文的ACK ,cwnd的大小就变为initcwnd * 2 。下次发送报文段的数量就是 initcwnd *2 ,再接收到 initcwnd * 2 报文段的ACK后,那么接着发送窗口的大小就是 initcwnd * 4 ,这是一种指数关系
\[CWND = initcwnd * 2^n \]
n 指的是RTT的次数
慢启动阈值:ssthresh
当cwnd<ssthresh时,拥塞窗口使用慢启动算法,按指数级增长。 当cwnd="">ssthresh时,拥塞窗口使用拥塞避免算法,按线性增长。
拥塞避免算法每经过一个RTT,拥塞窗口增加initcwnd
。
当到达设置的阈值时发送窗口的大小就每收到一个ACK,cwnd+=initcwnd ,当出现丢包的情况是就立马将发送窗口的大小设置为 initcwnd.
当发生拥塞的时候(超时或者收到重复ack),RFC5681认为此时ssthresh需要置为没有被确认包的一半,但是不小于两个MSS。此外,如果是超时引起的拥塞,则cwnd被置为initcwnd。
超时重传对传输性能有严重影响。原因之一是在RTO阶段不能传数据,相当于浪费了一段时间;原因之二是拥塞窗口的急剧减小,相当于接下来传得慢多了。
有时候拥塞比较轻微,只有少量包丢失,后续的包能够正常到达。当后续的包到达接收方时,接收方会发现其Seq号比期望的大,所以它每收到一个包就Ack一次期望的Seq号,以此提醒发送方重传。当发送方收到3个或以上重复确认(Dup Ack)时,就意识到相应的包已经丢了,从而立即重传它。这个过程称为快速重传。
为什么要规定凑满3个呢?这是因为网络包有时会乱序,乱序的包一样会触发重复的Ack,但是为了乱序而重传没有必要。由于一般乱序的距离不会相差太大,比如2号包也许会跑到4号包后面,但不太可能跑到6号包后面,所以限定成3个或以上可以在很大程度上避免因乱序而触发快速重传。
还有一个问题,如下图:
如果2号和3号包都丢失了,但是后面4,5,6,7号都正常收到了,并触发了三次ack = 2。在重传了2号包之后该传哪个包那,是全部需要重传还是只传2号包?
为了解决这种问题,TCP在发送重复的Ack包的时候,会告诉接收方收到的已经收到包的序号,如下图:
这样发送方就知道该重传哪个包了,这种方式被称为选择性确认(Selective Acknowledgement)。
如果在拥塞阶段发生了快速重传就没有必要像超时重传那样处理拥塞窗口了,因为此时的拥塞并不是很严重。RFC5681建议此时的慢启动阈值ssthreh设置为没有被确认包的1/2,但是不小于2个MSS。拥塞窗口设置为慢启动阈值加3个MSS。这个过程被称为快速恢复。
TCP的四次挥手如下图所示
当处于Listen状态的服务器接收到一个SYN报文后,可以将ACK(应答作用)报文和SYN (请求建立连接) 报文放到一个报文中进行发送。
但是在关闭TCP连接时,对方给你发送一个FIN报文,这仅仅表示对方没有数据给你发送了,但是并不代表你没有数据给对方发送了。所以先给对方发送一个ACK报文,当发送完数据过后在发送一个FIN报文表示告诉对方你同意关闭连接了。所以在通常情况下ACK报文和FIN报文是分开发送的。
</ssthresh时,拥塞窗口使用慢启动算法,按指数级增长。>