协议层次
因特网协议栈中的5个层次有应用层、运输层、网络层、链路层、物理层;
①应用层是网络应用程序及它们的应用层协议存留的地方。
②运输层在应用程序端点之间传送应用层报文。
③网络层负责将成为数据报的网络层分组从一台主机移动到另一台主机。
④链路层沿着路径将数据报传递给下一个结点。
⑤物理层的任务是将该帧中的一个一个比特从一个结点移动到下一个结点。
TCP
TCP报文段结构
TCP报文段:首部 + 数据,其中,首部包含以下字段:
字段 | 长度 | 说明 |
---|---|---|
源端口号、目的端口号 | 均为16bit | 用于多路复用/分解来自或送到上层应用的数据 |
校验和 | 16bit | 接收方用来检查该报文段中是否出现了差错 |
序号、确认号 | 均为32bit | 被TCP发送方和接收方用来实现可靠数据传输服务 |
接收窗口 | 16bit | 用于流量控制,指示接收方愿意接收的字节数量 |
首部长度 | 4bit | 指示TCP首部长度 通常该字段为空,TCP首部的典型长度就是20字节 |
标志 | 6bit | ACK用于指示确认字段中的值是有效的,即该报文段包括一个对已被成功接收报文段的确认; RST、SYN、FIN用于建立和拆除连接; 其中, SYN=1表示要建立连接 FIN=1表示要拆除连接 其他的两个PSH和URG在实践中并未使用 |
其他可选与变长的字段 | 略 | 略 |
数据则是来自上层应用层的报文,该数据的最大长度受MSS(Maximum Segment Size,最大分段长度)限制。
确认号和序列号的理解
确认号和序列号是TCP报文段首部中最重要的两个字段,这两个字段是TCP可靠传输的关键。
序列号
假如主机A上的一个进程想通过一条TCP连接向主机B上的一个进程发送一个数据流,则主机A的TCP会隐式地为该数据流的每个字节进行编号。假定数据流由一个包含500 000 字节的文件组成,其MSS(最大报文段长度,不包括协议首部,只包含应用数据)为1000字节,数据流的首字节编号是0。该TCP将为该数据流构建500个报文段。给第一个报文段分配序号0,第二个报文段分配序号1000,第三个报文段分配序号2000,以此类推。每一个序号被填入到相应的TCP报文段首部的序号字段中。
确认号
主机A向主机B发送报文(序号为38,字节数为4),假如B成功收到该报文段,并向A发送确认报文段,这个确认报文段中的确认号才是42(告诉A,我现在收到这个你发的报文段了,编号为38、39、40、41字节我都收到了,你可以发从42开始发送了),而不是最初从A发向B的那个报文段的确认号是42。即主机A填充进报文段的确认号是主机A期望从主机B收到的下一字节的序号。
TCP三次握手
所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 接收窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。
第一次握手(SYN=1, 初始序列号=x,不携带应用程序数据):
客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号字段里。
发送完毕后,客户端进入 SYN_SEND 状态。第二次握手(SYN=1, ACK=1, 初始序列号=y, 确认号=x+1,不携带应用程序数据):
服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己初始序列号y,放到序列号字段中,同时将确认序号设置为客户的初始序列号加1,即x+1。 发送完毕后,服务器端进入SYN_RCVD 状态。第三次握手(ACK=1,确认号=y+1,可以携带应用程序数据)
客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来的初始序号字段+1,即y+1,放在确定号字段中发送给对方
发送完毕后,客户端进入ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。
SYN 攻击
在三次握手过程中,服务器发送SYN-ACK 之后,收到客户端的 ACK 之前(即两次握手后)的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.
SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。
SYN 攻击是一种典型的 DoS/DDoS 攻击。
TCP四次挥手
TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
第一次挥手(FIN=1,seq=x)
假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。
发送完毕后,客户端进入 FIN_WAIT_1 状态。第二次挥手(ACK=1,确认号=x+1)
服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。第三次挥手(FIN=1,seq=y)
服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。第四次挥手(ACK=1,确认号=y+1)
客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。
TCP流量控制
为什么要进行流量控制
一条TCP连接的两端主机都为该连接设置了接收缓存。当该TCP连接接收到正确、按序的字节后,它就将数据放入接收缓存。相关联的应用程序会从该缓存中读取数据,但不是数据一到达就立即读取,它可能在忙于其他任务。如果某应用程序读取数据的速度相对缓慢,而发送方发送的太快、太多,发送的数据就会很容易是该连接的接收缓存溢出。所以,TCP为应用程序提供流量控制服务,以消除发送方使接收方缓存溢出的可能性。(注意:TCP发送方也可能因为IP网络的拥塞而被遏制,这种形式的发送发的控制被称为拥塞控制,与这里的流量控制概念不在同一范畴。)-
如何控制
TCP通过让发送方维护一个称为接收窗口的变量,该接收窗口用于给发送方一个提示—TCP连接另一端的接收方还有多少可用的缓存空间。因为TCP是全双工通信,在连接两端的发送方都各自维护一个接收窗口。
现在,只关注某一个接收方,我们定义以下变量:
RcvBuffer
:接收缓存的大小,该值是固定不变的;
rwnd
:接收窗口的大小,该值是随时变动的;
LastByteRead
:应用程序从缓存读出的数据流的最后一个字节的编号;
LastByteRcvd
:从网络中达到的并且已经放入接收缓存的数据流的最后一字节的编号;
要想缓存不溢出,则要满足下列关系:LastByteRcvd - LastByteRead <= RcvBuffer
上式中左边的差值,表示已经达到但还没被应用程序读取的数据流的字节大小,则接收缓存的大小减去这个差值之后的值,就是接收窗口的值,即:
rwnd
=RcvBuffer - ( LastByteRcvd - LastByteRead )
温故以下前面的知识,TCP报文段的首部中,有一个字段叫做接收窗口,就是用来做流量控制的。现在假设主机A向主机B通过TCP连接发送一个文件,主机B通过把自己的当前接收窗口的值放入该字段中,通知主机A:我还有这么多的可用空间。在连接开始时,主机B设定接收窗口的大小等于接收缓存的大小。
当接收窗口为0时的小问题
假如在某一时刻,主机B的接收窗口值为0了,主机A得到这个通知后,就停止继续向B发送报文段,假设主机B没有任何数据要发给主机A,此后随着时间流逝,主机B上的应用进程将接收缓存清空,TCP并不向主机A发送带有接收窗口新值的新报文段(因为主机B仅当在它有数据或有确认要发送时才会发报文给A),这样,主机A不能知道主机B的接受缓存已经有新的空间了,即主机A被阻塞而不能在发送数据。
为了解决这个问题,TCP规范要求:当主机B的接收窗口为0时,主机A要继续发送只有一个字节数据的报文段。这些报文段被主机B接收后,主机B会发送确认报文段,在这个确认报文段中,会设置上新的接收窗口的值。
参考
①《计算机网络--自顶向下方法》
② 计算机网络