传输层

TCP(Transmission Control Protocol)为应用程序之间提供面向连接的可靠的字节流服务。TCP为全双工协议,提供流控制机制,即允许接收方控制发送方的发送速度,此外还提供拥塞控制功能。

因特网的网络层只提供无连接、不可靠的尽力服务。它可以将分组从一个主机通过因特网传送到另一台主机,可能出现比特错、丢失、重复和错序到达的情形。

传输层建立在网络层之上,为进程之间的数据传输提供服务。传输层可以通过不可靠的因特网在两个进程之间建立一条可靠的逻辑链路,提供字节流传输服务。

因特网的传输层有两个协议UDP和TCP:

  • UDP(User Datagram Protocol)只提供无连接的不可靠的服务,应用进程通过<远端IP地址,远端端口号>向远端进程发送数据,应用进程并不要求远端进程进行确认。
  • TCP(Transmission Control Protocol)为应用程序之间提供面向连接的可靠的字节流服务。TCP为全双工协议,提供流控制机制,即允许接收方控制发送方的发送速度,此外还提供拥塞控制功能。

UDP协议

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!

UDP 适用于一次只传送少量数据、对可靠性要求不高的应用环境。通常我们使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实 “ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如, 在默认状态下,一次“ping”操作发送4个数据包。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一 个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

TCP协议

TCP(传输控制协议)是应用程序之间的通信,是用固定的链接,当引用程序希望通过TCP与另一个应用程序通信时,它会发送一个通信请求,这个请求必须被送到一个确切的地址,在双方 “握手”之后,TCP将在两一个应用程序之间建立一个全双工的通信UDP 和 TCP 很相似,但是更简单,同时可靠性低于 TCP。

TCP/IP是个协议组,可分为四个层次:网络接口层、网络层、传输层和应用层。

  • 网络层:在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
  • 传输层:在传输层中有TCP协议与UDP协议。
  • 应用层:在应用层有FTP、HTTP、TELNET、SMTP、DNS等协议。

传输层 - 图1

该图为两个端点之间TCP通信,源主机的TCP进程从上层收集应用进程的数据,并在满足一定条件时发送出去,TCP发送的数据称为分段(Segment)。

TCP报文结构

TCP头部数据格式如下:

传输层 - 图2

各个字段的信息说明如下:

  • Source Port(Destination Port):分别占用16位,表示源端口号和目的端口号;用于区别主机中的不同进程,源端口号和目的端口号配合上IP首部中的源IP地址和目的IP地址就能唯一的确定一个TCP连接。

  • Sequence Number:用来标识从TCP发送端向TCP接收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号,主要用来解决网络报乱序的问题。

  • Acknowledgment Number:发送确认的一端所期望收到的下一个序号,因此,确认序号应当是上次已成功收到数据字节序号加1。不过,只有当标志位中的ACK标志为1时该确认序列号的字段才有效,该字段主要用来解决丢包的问题。此外,TCP采用累计确认,即只有当确认字节之前的所有数据都到达之后才能发送确认,这样就可以用一个数字概括接收到的所有数据。

  • Data offset:用来标识TCP头部的长度,该数字为头部中字(32 bit)的数目,需要这个值是因为任选字段的长度是可变的。这个字段占4bit,因此TCP最多有60字节的头部。然而,没有任选字段,正常的长度是20字节。

  • Reserved:3个保留位,留作以后使用,全部设置为0。

  • 标志位:TCP头部中共有9个标志位,用于操控TCP的状态,主要有URG,ACK,PSH,RST,SYN,FIN,标志位的意思如下:

    • URG:此标志表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据;
    • ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;
    • PSH:表示Push操作,数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队;
    • RST:表示连接复位请求,用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包;
    • SYN:表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被响应的时候,SYN=1,ACK=1;
    • FIN:表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了。
  • Window Size:窗口大小,也就是有名的滑动窗口,用来进行流量控制。指定从被确认的字节算起可以发送多少个字节,窗口大小字段为0是合法的,说明已经接收到了 确认号-1 个字节,但是接收端没有来得及取走数据。

三次握手建立连接

TCP协议提供可靠的连接服务,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,连接是通过三次握手进行初始化的,三次握手的过程如下:

传输层 - 图3

前两次握手,客户端进入连接状态,后两次握手,服务器进入连接状态。所以,三次握手之后,一个全双工的连接就建立起来了,之后,客户端和服务器端就可以开始传送数据。

  • 第一次握手:客户端发送连接请求报文段,将SYN位设为1,SeqNum为随机数A;
  • 第二次握手:服务器返回ACK,确认收到客户端发来的SYN,然后设置AckNum为A+1;此外,服务器发送自己的连接请求报文段,即发送SYN和随机数B作为SeqNum;
  • 第三次握手:客户端返回ACK,确认收到服务器发来的SYN,然后设置AckNum=B+1。

但是我们为什么需要三次握手建立连接?

这是因为为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

考虑下面一种情况:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。server收到此失效的连接请求报文段后,误认为是client发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。

假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。

采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

四次握手断开连接

客户端和服务器数据传送完毕后,需要断开TCP连接,断开连接的时候需要进行四次握手。

传输层 - 图4

四次握手的过程如下:

  • 第一次握手:发起端发送FIN和SeqNum=A,进入FIN_WAIT_1状态,用来关闭发起端到接收端的数据传送,也就是告诉接收端:不会再给你发新数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,发起端依然会重发这些数据),但此时发起段还可以接受数据;

  • 第二次握手:接收端收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(AckNum=A+1),此时接收端仍然可以给发起段发送数据(同意关闭连接请求,但是我还有数据需要传送,稍等…);

  • 第三次握手:接收端向发起端发送FIN,用来关闭到发起端的数据传送,也就是告诉发起端:我的数据也发送完了,不会再给你发数据了。此时接收端进入CLOSE_WAIT状态;

  • 第四次握手:发起端发送ACK报文段,然后进入TIME_WAIT状态,接收端收到ACK报文段以后,就关闭连接。发起端等待2MSL后依然没有收到回复,则证明Server端已正常关闭,此时也可以关闭连接了。

如果要正确的理解四次握手失败的原理,还需要了解四次握手失败过程中的状态变化。

  • FIN_WAIT_1: FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态。(主动方)

  • FIN_WAIT_2:FIN_WAIT_2状态下的SOCKET,表示半连接,也即主动方要求断开连接,得到了被动方的确认,但被动方还有数据要发送,因此主动方还得继续接收。(主动方)

  • TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)

  • CLOSE_WAIT:在CLOSE_WAIT状态下,被动方还有数据需要传送。(被动方)

  • LAST_ACK: 被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

  • CLOSED: 表示连接中断。

那么什么要四次握手断开连接?

TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会中断这次TCP连接。

TIME_WAIT 状态存在的原因:

  1. 可靠地实现TCP全双工连接的终止。

在进行关闭连接四次握手协议时,最后的ACK是由主动关闭端发出的,如果这个最终的ACK丢失,被动关闭方将重发最终的FIN,主动关闭端只有在维护状态信息的情况下才可以重新发送最终的那个ACK。如果不维护这个状态信息,主动关闭端将会响应一个RST,对端会将此响应标记为错误,所以不能进行正常的关闭。

  1. 允许老的重复分节在网络中消逝。

假设TCP协议中不存在TIME_WAIT状态的限制,再假设当前有一条TCP连接:(local_ip, local_port, remote_ip,remote_port),因某些原因,先关闭,接着很快以相同的四元组建立一条新连接。TCP协议栈是无法区分前后两条TCP连接的不同的,在它看来,这根本就是同一条连接,中间先释放再建立的过程对其来说是“感知”不到的。这样就可能发生这样的情况:前一条TCP连接由local peer发送的数据到达remote peer后,会被该remot peer的TCP传输层当做当前TCP连接的正常数据接收并向上传递至应用层(而事实上,在我们假设的场景下,这些旧数据到达remote peer前,旧连接已断开且一条由相同四元组构成的新TCP连接已建立,因此,这些旧数据是不应该被向上传递至应用层的),从而引起数据错乱进而导致各种无法预知的诡异现象。

local peer主动调用close后,此时的TCP连接进入TIME_WAIT状态,处于该状态下的TCP连接不能立即以同样的四元组建立新连接,即发起active close的那方占用的local port在TIME_WAIT期间不能再被重新分配。由于TIME_WAIT状态持续时间为2MSL,这样保证了旧TCP连接双工链路中的旧数据包均因过期(超过MSL)而消失,此后,就可以用相同的四元组建立一条新连接而不会发生前后两次连接数据错乱的情况。

传输层 - 图5

TCP 状态转换图