概述
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
连接的建立与终止
建立连接协议
为了建立一条TCP连接,要完成以下几步:
- 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号。这个SYN段为报文段1。
- 服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
- 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认。
三个报文段完成连接的建立。这个过程也称为三次握手( three-way handshake)。
发送第一个SYN的一端将执行主动打开( active open)。接收这个SYN并发回下一个SYN
的另一端执行被动打开( passive open)当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。
- 连接终止协议
建立一个连接需要三次握手,而终止一个连接要经过 4次握手。这由TCP的半关闭(half -close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端几经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。
首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭。
下图显示了终止一个连接的典型握手顺序:

连接通常是由客户端发起的,这样第一个SYN从客户传到服务器。每一端都能主动关闭这个连接(即首先发送FIN)。然而,一般由客户端决定何时终止连接,因为客户进程通常由用户交互控制,用户会键入诸如“ quit”一样的命令来终止进程。
连接建立的超时
有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。
第一次超时时间
当我们键入telnet命令,将建立一个6秒的定时器(12个时钟滴答(tick)),但它可能在之后的5.5秒~ 6秒内的任意时刻超时。尽管定时器初始化为 12个时钟滴答,但定时计数器会在设置后的第一个 0~500ms中的任意时刻减1。从那以后,定时计数器大约每隔500ms减1,但在第1个500ms内是可变的
下图展示了TCP的500ms定时器:

当滴答计数器为0时,6秒的定时器便会超时,这个定时器会在以后的24秒(48个滴答)重新复位。之后的下一个定时器将更接近24秒,因为当TCP的500ms定时器被内核调用时,它就会被修改一次。
最大报文段长度
最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。
当建立一个连
接时,每一方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值。
一般说来,如果没有分段发生,MSS还是越大越好,报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高
的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端的主机收到了一个连接请求,它能将MSS值设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。
MSS让主机限制另一端发送数据报的长度。加上主机也能控制它发送数据报的长度,这将使以较小MTU连接到一个网络上的主机避免分段。
TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。只有很少的应用程序使用它。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
下图显示了一个半关闭的典型例子

没有半关闭,需要其他的一些技术让客户通知服务器, 客户端已经完成了它的数据传送,但仍要接收来自服务器的数据。使用两个TCP连接也可作为一个选择,但使用半关闭的单连接更好。
TCP的状态变迁图
下图展示了有关发起和终止TCP连接的规则状态变迁:

在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。第二点是两个导致进入ESTABLISH -ED状态的变迁对应打开一个连接,而两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。ESTABLISHED状态是连接双方能够进行双向数据传递的状态。
下图展示了TCP正常连接建立和终止所对应的状态:

2MSL等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
平静时间的概念
对于来自某个连接的较早替身的迟到报文段, 2MSL等待可防止将它解释成使用相同插口对的新连接的一部分。但这只有在处于2MSL等待连接中的主机处于正常工作状态时才有效
TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间(quiet time)。
FIN_WAIT_2 状态
在FIN_WAIT_2状态我们已经发出了FIN,并且另一端也已对它进行确认。除非我们在实行半关闭,否则将等待另一端的应用层意识到它已收到一个文件结束符说明,并向我们发一个 FIN 来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。
这意味着我们这端可能永远保持这个状态。另一端也将处于CLOSE_WAIT状态,并一直保持这个状态直到应用层决定进行关闭。
复位报文段
TCP首部中的RST比特是用于“复位”的。一般说来,无论何时一个报文段发往基准的连接( referenced connection)出现错误,TCP都会发出一个复位报文段
“基准的连接”是指由目的IP地址和目的端口号以及源IP地址和源端口号指明的连接。
到不存在的端口的连接请求
产生复位的一种常见情况是当连接请求到达时,目的端口没有进程正在听。当一个数据报到达目的端口时,该端口没在使用,它将产生一个ICMP端口不可达的信息。而TCP则使用复位。
异常终止一个连接
终止一个连接的正常方式是一方发送FIN。有时这也称为有序释放(orderly release),因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而不是FIN来中途释放一个连接。有时称这为异常释放(abortive release)。
异常终止一个连接对应用程序来说有两个优点:
(1)丢弃任何待发数据并立即发送复位报文段;
(2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段。
检测半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的TCP连接称为半打开(Half-Open)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后再关机。
同时打开
两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开( simultaneous open)。
TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接(其他的协议族,最突出的是OSI运输层,在这种情况下将建立两条连接而不是一条连接)。
下图展示了同时打开期间的报文段交换:

一个同时打开的连接需要交换 4个报文段,比正常的三次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。
同时关闭
TCP协议也允许同时关闭( simultaneous close)。
下图展示了同时关闭期间的报文段交换:

同时关闭与正常关闭使用的段交换数目相同。
TCP 选项
下图展示了TCP选项:

每个选项的开始是1字节kind字段,说明选项的类型。kind字段为0和1的选项仅占1个字节。其他的选项在kind字节后还有len字节。它说明的长度是指总长度,包括kind字节和len字节。
设置无操作选项的原因在于允许发方填充字段为 4字节的倍数。
设置无操作选项的原因在于允许发方填充字段为 4字节的倍数。
大多数的TCP服务器进程是并发的。当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的操作系统使用不同的技术来调用新的服务器进程。如果系统支持,也可使用轻型进程,即线程(thread)。
TCP服务器端口号
通过观察任何一个TCP服务器,我们能了解TCP如何处理端口号。
限定的本地IP地址
如果我们指明一个IP地址(或主机名),并将它作为服务器,那么该IP地址就成为处于LISTEN服务器的本地IP地址。
限定的远端IP地址
下图展示了TCP服务器本地和远端IP地址及端口号的规范:

在三种情况中,lport是服务器的熟知端口,而localIP必须是一个本地接口的IP地址。表中行的顺序正是TCP模块在收到一个连接请求时确定本地地址的顺序。最常使用的绑定(第 1行,如果支持的话)将最先尝试,最不常用的(最后一行两端的 I P地址都没有制定)将最后尝试。
呼入连接请求队列
一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达多个连接请求。
在大多数TCP/IP的具体实现中,就是如果服务器的连接队列未满时,TCP将接受传入的连接请求(即SYN),但并不让应用层了解该连接源于何处(即不告知源IP地址和源端口)。这不是TCP所要求的,而只是共同的实现技术果一个API如TLI向应用程序提供了解连接请求的到来的方法,并允许应用程序选择是否接受连接。当应用程序假定被告知连接请求已经到来时,TCP的三次握手已经结束!其他运输层的实现可能将连接请求的到达与接受分开(如OSI的运输层),但TCP不是这样。
这种行为也意味着TCP服务器无法使客户进程的主动打开失效。当一个新的客户连接传递给服务器的应用程序时,TCP的三次握手就结束了,客户的主动打开已经完全成功。如果服务器的应用程序此时看到客户的IP地址和端口号,并决定是否为该客户进行服务,服务器所能做的就是关闭连接(发送FIN),或者复位连接(发送RST)。无论哪种情况,客户进程都认为一切正常,因为它的主动打开已经完成,并且已经向服务器程序发送过请求。