这是计算机网络相关的第五篇文章。面试讲到TCP,那么基本都会问三次握手和四次挥手的过程,以及比如对于握手,为什么是三次,而不是两次或者四次,本章详细探讨其中的门道。

1.复习

首先针对http协议,我们有必要复习一下最重要的东西。

HTTP协议即超文本传送协议(Hypertext Transfer Protocol ),是Web联网的基础,也是手机联网常用的协议之一,HTTP协议是建立在TCP协议之上的一种应用。

在HTTP 1.0以0.9版本中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接。

在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求。

由于HTTP在每次请求结束后都会主动释放连接,因此HTTP连接是一种“短连接”,要保持客户端程序的在线状态,需要不断地向服务器发起连接请求。

通常的做法是即使不需要获得任何数据,客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明知道 客户端“在线”。

若服务器长时间无法收到客户端的请求,则认为客户端“下线”,若客户端长时间无法收到服务器的回复,则认为网络已经断开。

2.SOCKET原理

2.1 套接字(socket)概念

初次接触这个名词:“套接字”,说实话,心里是蒙蔽的,这是啥玩意,但是可以去搜索一下什么是套接管:

image

我们可以看出来,两个管子可能直接连的话连不起来,那么可以通过中间一个东西连接起来。

那么,现在就好理解了,两个程序要通信,需要知道对方的一些信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

image

它是什么呢?它是网络通信过程中端点的抽象表示,这个抽象里面就包含了网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

那为什么一定要用它呢?

在同一台计算机上,TCP协议与UDP协议可以同时使用相同的port而互不干扰。 操作系统根据套接字地址,可以决定应该将数据送达特定的进程或线程。这就像是电话系统中,以电话号码加上分机号码,来决定通话对象一般。

因为我们电脑上可能会跑很多的应用程序,TCP协议端口需要为这些同时运行的程序提供并发服务,或者说,传输层需要为应用层的多个进程提供通信服务,每个进程起一个TCP连接,那么这多个TCP连接可能是通过同一个 TCP协议端口传输数据。

如何区别哪个进程对应哪个TCP连接呢?

许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

2.2 建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发 给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

2.3 SOCKET连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。

3.TCP基本字段

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

针对协议中的字段,我们只需要了解一下:ACK、SYN、序号这三个部分。

  • ACK : 确认

    • TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1
  • SYN : 在连接建立时用来同步序号。

    • 当SYN=1而ACK=0时,表明这是一个连接请求报文。
    • 对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1.
    • 因此, SYN置1就表示这是一个连接请求或连接接受报文。
  • FIN 即终结的意思, 用来释放一个连接。

    • 当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。

4.三次握手(重要,细读)

image

首先,TCP作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率,而用三次恰恰可以满足以上两方面的需求!

然后,要明确TCP连接握手,握的是啥?

答案:通信双方数据原点的序列号

我们在上面一篇文章知道,消息的完整是靠给每个消息包搞一个编号,依次地ACK确认。确认机制是累计的,意味着 X 序列号之前(不包括 X) 包都是被确认接收到的。

TCP可靠传输的精髓:TCP连接的一方A,由操作系统动态随机选取一个32位长的序列号(Initial Sequence Number)

假设A的初始序列号为1000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,1001,1002,1003…,并把自己的初始序列号ISN告诉B。

让B有一个思想准备,什么样编号的数据是合法的,什么编号是非法的,比如编号900就是非法的,同时B还可以对A每一个编号的字节数据进行确认。

如果A收到B确认编号为2001,则意味着字节编号为1001-2000,共1000个字节已经安全到达。

同理B也是类似的操作,假设B的初始序列号ISN为2000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,2001,2002,2003…,并把自己的初始序列号ISN告诉A,以便A可以确认B发送的每一个字节。如果B收到A确认编号为4001,则意味着字节编号为2001-4000,共2000个字节已经安全到达。

好了,在理解了握手的本质之后,下面就可以总结上面图的握手过程了。

对于A与B的握手过程,可以总结为:

  1. A 发送同步信号SYN + A's Initial sequence number(丢失会A会重传)
  2. B 确认收到A的同步信号,并记录 A's ISN 到本地,命名 B's ACK sequence number
  3. B发送同步信号SYN + B's Initial sequence number (丢失B会周期性超时重传,直到收到A的确认)
  4. A确认收到B的同步信号,并记录 B's ISN 到本地,命名 A's ACK sequence number

很显然2和3 这两个步骤可以合并,只需要三次握手,可以提高连接的速度与效率

这里就会引出一个问题,两次不行吗?

  1. A 发送同步信号SYN + A’s Initial sequence number
  2. B发送同步信号SYN + B’s Initial sequence number + B’s ACK sequence number

这里有一个问题,A与B就A的初始序列号达成了一致,但是B无法知道A是否已经接收到自己的同步信号,如果这个同步信号丢失了,A和B就B的初始序列号将无法达成一致。

所以A必须再给B一个确认,以确认A已经接收到B的同步信号。

如果A发给B的确认丢了,该如何?

A会超时重传这个ACK吗?不会!TCP不会为没有数据的ACK超时重传。

那该如何是好?B如果没有收到A的ACK,会超时重传自己的SYN同步信号,一直到收到A的ACK为止。

写到这里,其实我们已经明白了,握手其实就是各自确认对方的序列号。因为后面的数据编号就会以此为基础,从而保证后续数据的可靠性。

谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

但是有的人指出,其实这只是表象,或者说并不是三次握手的设计初衷,我表示认同,这个防止已失效的连接请求报文段应该只是附加的一些好处,而不应该是解释为什么是三次握手的原因。

TCP初始阶段为什么是三次握手,原因总结如下:

为了实现可靠数据传输, TCP 协议的通信双方, 都必须维护一个序列号, 以标识发送出去的数据包中, 哪些是已经被对方收到的。

三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤,如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。

5.四次挥手

image

挥手过程的大概说明:

当主机A发出FIN报文段时,只是表示主机A已经没有数据要发送了,主机A告诉主机B,它的数据已经全部发送完毕了;

但是,这个时候主机A还是可以接受来自主机B的数据;

当主机B返回ACK报文段时,表示它已经知道主机A没有数据发送了,但是主机B还是可以发送数据到主机A的;

当主机B也发送了FIN报文段时,这个时候就表示主机B也没有数据要发送了,就会告诉主机A,我也没有数据要发送了;

主机A告诉主机B知道了,主机B收到这个确认之后就立马关闭自己。

主机A等待2MSL之后也关闭了自己。

说明完了四次挥手的过程,下面着重解释一下为什么主机A要等待2MSL之后才关闭自己。

针对最后一条消息,即主机A发送ack后,主机B接收到此消息,即认为双方达成了同步:双方都知道连接可以释放了,此时主机B可以安全地释放此TCP连接所占用的内存资源、端口号。

所以被动关闭的主机B无需任何wait time,直接释放资源。

但是主机A并不知道主机B是否接到自己的ACK,主机A是这么想的

  • 如果主机B没有收到自己的ACK,主机B会超时重传FiN,那么主机A再次接到重传的FIN,会再次发送ACK
  • 如果主机B有收到自己的ACK,也不会再发任何消息,包括ACK

无论是情况1还是2,主机A都需要等待,要取这两种情况等待时间的最大值,以应对最坏的情况发生,这个最坏情况是:

主机B没有收到主机A的ACK,那么超时之后主机B会重传FIN,也就是说,要浪费一个主机A发出ACK的最大存活时间(MSL)+FIN消息的最大存活时间(MSL)

不可能时间再多了,这个已经针对最糟糕的状况。

等待2MSL时间,主机A就可以放心地释放TCP占用的资源、端口号,此时可以使用该端口号连接任何服务器。

在等待的时间内,主机B可以重试多次,因为2MSL时间为240秒,超时重传只有0.5秒,1秒,2秒,16秒。

当主机B重试次数达到上限,主机B会reset连接。

那么为什么是2MSL我们已经了解了,但是为什么要等这个时间呢?

如果不等,释放的端口可能会重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新TCP连接报文冲突,造成数据冲突,为避免此种情况,需要耐心等待网络老的TCP连接的活跃报文全部死翘翘,2MSL时间可以满足这个需求。

整理自: