UDP基本包括了传输层所必须的端口字段。它相信“网之初,性本善,不丢包,不乱序”。
后来呢,我们都慢慢长大,了解了社会的残酷,变得复杂而成熟,就像TCP协议一样。它之所以这么复杂,那是因为它秉承的是“性恶论”。它天然认为网络环境是恶劣的,丢包、乱序、重传,拥塞都是常有的事情,一言不合就可能送达不了,因而要从算法层面来保证可靠性。
TCP包头格式
- TCP头
源端口号和目标端口号和UDP是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。
包的序号。为什么要给包编号呢?
为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该后到呢。编号是为了解决乱序问题。
确认序号。
发出去的包应该有确认。若没有收到就应该重发,直到送达,以达到不丢包的目的。
TCP是靠谱的协议,但不代表它所处的网络环境很好。
IP层来看,如果网络状况的确差,无任何可靠性保证,即使是IP的上一层TCP也无能为力,能做的只是更努力,不断重传,通过各种算法尽量保证。
即对于TCP,IP层你丢不丢包,我管不着,但在我TCP层,会尽力保证可靠性。
一些状态位,例如:
- SYN
发起一个连接 - ACK
回复 - RST
重新连接 - FIN
结束连接
TCP是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
窗口大小。
TCP要做流量控制,通信双方各声明一个窗口,标识自己当前能够的处理能力,别发送太快或太慢。
拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。
TCP的三次握手
首先要先建立一个连接,TCP的连接建立,常称为三次握手。
- A:您好,我是A
- B:您好A,我是B
- A:您好B
也常称为“请求->应答->应答之应答”的三个回合。
为什么要三次,两次不够吗?
我们生活里两个人打招呼,一来一回就够了呀,那既然为了可靠,为啥不是四次?
假设这个通路是非常不可靠的,A要发起一个连接,当发了第一个请求,无响应,会有很多的可能性,比如:
- 第一个请求包丢了
- 没有丢,但是绕了弯路,超时了
- B没有响应,不想和我连接
A不能确认结果,于是再发发发。终于有个请求包到了B,但请求包到了B这件事,目前A还是不知道,所以A可能再发。
B收到请求包,就知道了A的存在,并且知道A要和它建立连接。
若B
- 不情愿建立连接,则A会重试一阵后放弃,连接建立失败,没有问题
- 乐意建立连接,则会发送应答包给A
对于B,这个应答包也是不知道能不能到达A。这时B自然也不能认为连接是建立好了,因为应答包:
- 会丢
- 会绕弯路
- A挂了
都可能。而且这时B还能碰到一个诡异现象:A和B原来建立了连接,简单通信后,结束了连接。还记得吧,A建立连接时,请求包可能重复发几次,有的请求包绕了一大圈又回来了,B会认为这也是一个正常的的请求的话,因此建立了连接,可以想象,这个连接不会进行下去,也没有个终结的时候,纯属单相思。
所以两次握手不够。
B发送的应答可能会发送多次,但只要一次到达A,A就认为连接已建立,因为对于A,他的消息有去有回。A会给B发送应答之应答,而B也在等这个消息,才能确认连接的建立,只有等到了这个消息,对于B来讲,才算它的消息有去有回。
当然A发给B的应答之应答也可能:
- 丢了
- 绕路
- B挂了
看起来应该还有个应答的应答的应答,但这样下去就没底了。所以四次握手是可以的,四十次都可以,关键四百次你也不能保证就真的可靠了。
所以只要保证:双方的消息都有去有回即可。
实际上大部分情况下,A和B建立连接后,A会马上发送数据,一旦A发送数据,则很多问题都得到解决。
例如A发给B的应答丢了,当A后续发送的数据到达时,B可认为该连接已建立,或B就是挂了,A发送的数据,会报错,说明B不可达,A就知道B有异常。
当然你可以说A比较坏,就是不发数据,建立连接后一直空着。可以开启keepalive机制,即使没有真实数据包,也有探活包。
作为服务端B的开发人员,对于A这种长时间不发包的客户端,可主动关闭,从而空出资源响应其它客户端。
三次握手还为了解决
TCP包的序号问题
A要告诉B,我这面发起的包的序号起始是从哪个号开始的,B同样也要告诉A,B发起的包的序号起始是从哪个号开始的。
为什么序号不能都从1开始?
这样往往会出现冲突。
例如,A连上B后,发了1、2、3三包。
发送3时,中间丢了或绕路了,于是重发。
后来A掉线了,重连上B后,序号又从1开始,然后发送2,但没想过发送3,但上次绕路的那个3又回来了,发给了B,B自然认为,这就是下一个包,于是发生错误!
所以每个连接都要有不同序号。
序号的起始序号随着时间变化,可看成一个32位的计数器,每4ms加一。
稍稍计算一下,若重复,需4h+,那个绕路的包也早就没了,因为IP包头里有个TTL,生存时间。
最后双方终于成功建立了连接。为维护该连接,双方都要维护一个状态机,在连接建立的过程中,双方的状态变化时序图就像:
- 起初,客户端、服务端处CLOSED状态
- 先是服务端主动监听某个端口,处于LISTEN状态。
- 然后客户端主动发起连接SYN,之后处于SYN-SENT状态
- 服务端收到发起的连接,返回SYN,并且ACK客户端的SYN,之后处于SYN-RCVD状态
- 客户端收到服务端发送的SYN和ACK之后,发送ACK的ACK,之后处于ESTABLISHED状态,因为它一发一收成功了
- 服务端收到ACK的ACK之后,处于ESTABLISHED状态,因为它也一发一收