传输控制协议(Transmission Control Protocol 即TCP)应用场景:

  • 应用程序要传输的报文大,就要分段传输,每段封装成一个数据包,在接收端将分段组装成完整的报文。
  • 应用层协议需要交互多次。
  • 功能:可靠传输(丢包自动重传、在接收端排序、流量控制、拥塞避免)。

用户数据包协议(User Datagram Protocol 即UDP)应用场景:

  • 就是应用程序传输的报文小,不需要分段。
  • 应用程序之间不需要多次交互。
  • 屏幕广播软件、不可靠传输。
  • 实时通信。

Tcp功能和首部:

  • Tcp为应用层协议提供可靠的的传输,在应用程序通信之间需要建立Tcp连接,客户端程序和服务端程序使用建立的Tcp连接实现双向通信,在通信过程Tcp实现可靠传输、流量控制、网络拥塞控制自动感知等功能,应用程序通信结束后,再释放Tcp连接。
  • Netstat -n 可以查看建立的的Tcp连接。

Tcp功能:

Tcp为应用程序通信提供可靠传输。在通信过程中实现以下功能:

  • 建立连接:在正式传输数据之前先建立Tcp连接,协商一些参数,比如告诉对方自己的接收缓存多大(单位:字节),一个段最多承载多少自己的数据,是否支持选择性确认(SACK)。
  • 可靠传输:发送端将文件以字节流的形式放入发送端缓存,接收端以字节流的形式从缓存读取。数据包丢失,超时后发送端会自动重传,没按顺序到达,会在接收端缓存排序。
  • 拥塞避免:这整个通信过程网络有可能拥塞也有可能畅通,发送端开始发送数据时先感知网络是否拥堵,调整发送速度。
  • 流量控制:如果发送端发送过块,接收端的应用程序有可能来不及从接收缓存读取数据,造成接收缓存满。接收端接收数据过程中可以告诉发送端发送快一点还是慢一点,是否需要暂停一会儿。
  • 释放连接:发送完毕,还要告诉对方发送完毕,等对方收到确认才释放连接。

Tcp首部:

序号和确认号的作用:

Tcp流工作过程:

  • 最前面三个是建立Tcp连接的数据包,这三个数据包包含参数。Tcp连接建立后,可以实现全双工通信。POP使用该连接接收电子邮件,接收完毕后就要释放Tcp连接。最后四个数据包是释放Tcp连接的数据包。可以看到使用该连接邮件客户端可以向邮件服务器发送请求,邮件服务器也可以向客户端发送响应,不需要建立两个Tcp连接。

Tcp建立连接数据包:

  • 应用程序客户端向服务端程序发起请求,客户端计算机向服务端计算机发送建立Tcp连接的请求。
  • 第3个数据包是客户端向服务器发送的第一个数据包,请求连接的数据包的特征:SYN(同步)标记位为1,ACK(确认)标记位为0,(意味着ACK无效,但WireShark上看到是0,因为这是客户端向服务器发送的第一个数据包,所以序号为0(seq=0))。
  • 该数据包Tcp首部的选项部分,指明客户端支持的最大报文段长度(Maximum Segment Size,MSS)和允许选择确认(Selective Acknowledgment,SACK),连接请求数据包没有数据部分。

Tcp连接确认数据包:

  • 确认连接数据包的特征:SYN(同步)标记位为1,ACK(确认)标记位为1,这是服务器向客户端发送的第一个数据包,所以序号为0(seq=0),服务器收到了客户端的请求(seq=0),确认已经收到,发送的确认号为1,选项部分指明服务器支持的最大报文段长度(MSS)为1460。

Tcp连接确认的确认数据包:

  • 客户端收到服务器的确认后,还需要向服务器发送一个确认,我们称之为确认的确认。这个确认数据包和以后通信的数据包,ACK标记位为1,SYN标记位为0。

建立Tcp连接的过程:

两次握手会出现的问题:

  • 假如客户端以一条路径给服务端发送了一个请求,没等服务端返回连接确认又给服务端以另一条路径发送了第二个请求,服务端发回了第二个请求的确认,但如果此时服务端接收到了请求一,再发送请求一的确认,客户端将会忽略请求一的确认,而服务端则会等待客户端返回请求一的确认,因此服务端和客户端会互相一直等待。

三次握手解决问题:

  • 客户端给服务端会多发送一次确认的确认,例如上述两次握手,如果客户端一直没给服务端发确认的确认,服务端等待过后则会将请求一释放,不会造成一直等待的现象。

可靠传输的实现:

  • Tcp发送的报文段是交给网络层传送的,我们知道,网络层只是尽最大努力将数据包发送到目的地,不考虑网络是否堵塞,数据包是否丢失。这就需要Tcp采取适当的措施才能使发送端和接收端之间的通信变得可靠。

Tcp可靠传输的实现-停止等待协议:

  • 发送一个等待接收一个,超时则重传。
  • 超时重传收到同样的数据包,丢弃发重复的,重新确认。
  • 超时重传收到超时的数据确认包,收下但什么也不做。

连续ARQ协议和滑动窗口协议-改进的停止等待协议:

  • 窗口分组,直接发送一个窗口,收到确认则发送下一个窗口。

  • 连续ARQ:一个窗口里含有多个组,每次一个窗口会将多个组连续发送。

  • 滑动窗口:发送一个窗口等待时,窗口滑动到下一个多组。

以字节为单位的滑动窗口技术详解:

Tcp连接释放:

  • 连接和关闭都是客户端发送请求。
  • Client–>Server:客户端发送关闭连接,客户端变为FIN-WAIT-1状态。
  • Server–>Client:服务端发送连接,变成CLOSE-WAIT状态。
  • Server–>Client:CLOSE-WAIT被动关闭后向客户端发送连接,变成LAST-ACK状态。
  • Client–>Server:客户端发送连接变成TIME-WAIT状态,服务端收到连接变成CLOSED关闭连接状态,客户端等待2MSL(2倍最长报文时间)后变成CLOSED关闭连接状态。

TIME-WAIT的意义:

  • 如果最后Client–>Server发送的连接丢失,Server向Client发送连接,Client必须是TIME-WAIT状态才能收到,如果是CLOSED状态,则无法收到,将会导致服务端一直无法关闭。所以Client会存在TIME-WAIT状态等待2MSL(2倍最长报文时间)来避免出现此错误。

Tcp连接状态:

下面是每一个TCP连接在任意时刻可能处于的状态,在Linux下可以在 netstat命令的最后一列(State列)里看到。

各个状态的含义如下:

  • CLOSED :初始状态,表示TCP连接是“关闭着的”或“未打开的”。
  • LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。
  • SYN_RCVD :表示接收到了SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。
  • SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。
  • ESTABLISHED :表示TCP连接已经成功建立。
  • FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。
  • FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。
  • TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat/proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
  • CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。
  • CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
  • LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

释放Tcp连接的四个数据包:

抓包分析Tcp可靠传输的实现:

快重传:

网速对确认频率的影响:

网速慢,接收端确认频率提高。

小Tips:

在Tcp的协议下,会存在一些Network attack。

  • SYN攻击:攻击者向服务端发送一个不是本机的的源IP地址去请求连接,服务端去向源IP地址的客户端发送确认请求,并等待确认请求的确认,但是源IP地址的客户端并未给服务端发送过请求连接,因此不会发送确认的确认请求连接。但攻击者会模拟大量的伪IP地址给服务端让它去获取IP地址的确认的确认请求连接,此时服务端会变得非常繁忙,占用大量的CPU资源,从而造成宕机的情况。
  • Land攻击:攻击者以服务端的IP为源IP一直向服务端发送请求,导致服务端会向自己一直发送确认,从而造成CPU资源大量被占用,造成宕机的情况。