当前位置: 首页 > >

TCP连接的建立与终止

发布时间:

TCP是一种面向连接的单播协议,在发送数据之前,通信双方必须在彼此之间建立连接。虽然TCP和UDP使用相同的网络层(IPv4或IPv6),但是TCP给应用程序提供了一种与UDP完全不同的服务??面向连接(connection-oriented)、字节流传输。


TCP可靠性

TCP提供了一个字节流接口,TCP必须把一个发送应用程序的字节流转换成一组IP可以携带的分组,这被称为组包(packetization)。这些分组包含序列号,该序列号在TCP中实际代表了每个分组的第一个字节在整个数据流中的字节偏移,而不是分组号,这允许分组在传送中是可变大小的,并允许它们组合,称为重新组包(repacketization)。应用程序数据被打散成TCP认为的最佳大小的块来发送,一般使得每个报文段按照不会被分片的单个IP数据报的大小来划分,这一点与UDP不同。


TCP维持了一个强制的校验和,该校验和涉及其头部、任何相关应用程序数据和IP头部的所有字段。这是一个端到端的伪头部,它用于检测传送中引入的比特差错。如果一个带无效校验和的报文段到达,那么TCP会丢弃它。


当TCP发送一组报文段时,它通常设置一个重传计时器,等待对方的确认接收。TCP不会为每个报文段设置一个不同的重传计时器。相反,发送一个窗口的数据,它只设置一个计时器,当ACK到达时再更新超时。如果一个确认没有及时接收到,这个报文段就会被重传。


当TCP接收到连接的另一端数据时,它会发送一个确认。这个确认可能不会立即发送,而一般会延迟片刻。TCP使用的ACK是累积的,从某种意义来讲,一个指示字节号N的ACK暗示着所有直到N的字节(不包含N)已经被成功接收了。这对于ACK丢失来说带来了一定的鲁棒性??如果一个ACK丢失,很有可能后续的ACK就足以确认前面的报文段了。


TCP给应用程序提供一种双工服务??数据可向两个方向流动,两个方向互相独立。因此,连接的每个端点必须对每个方向维持数据流的一个序列号。一旦建立了一个连接,这个连接的一个方向上的包含数据流的每个TCP报文段也包含了相反方向上的报文段的一个ACK。每个报文段也包含一个窗口通告以实现相反方向上的流量控制。因此,一个完整的TCP连接是双向和对称的,数据可以在两个方向上*等地流动。


使用序列号,一个TCP接收端可丢弃重复的报文段和记录以杂乱次序到达的报文段。因为TCP是一个字节流协议(没有边界记录),所以TCP绝不会以杂乱的次序给应用程序发送数据。因此,TCP接收端可能会被迫先保持一个大序列号的数据不交给应用程序,直到缺失的小序列号的报文段(一个“洞”)被填满。


TCP头部



每个TCP头部包含了源端口号和目的端口号,这两个值与IP头部中的IP地址一起作为每个连接的唯一标识。这种IP地址+端口号的组合被称为端点(endpoint)或套接字(socket)。每个TCP连接由一对套接字唯一地标识。


序列号(Sequence Number)字段标识了TCP发送端到TCP接收端的数据流的一个TCP报文,而确认号(Acknowledge)字段(也称ACK)包含的值是该确认号的发送方期待接收的下一个序列号,即最后被成功接收的数据字节的序列号加1。这个字段只有在ACK位字段被启用的情况下才有效,这个ACK位字段通常位于除了初始和末尾报文段之外的所有报文段。发送一个ACK与发送任何一个TCP报文段的开销是一样的,因为这个32位的ACK一直都是头部的一部分,ACK位也是一样。


当建立一个新连接时,从客户机发送至服务器的第一个报文段的SYN位字段被启用,这样的报文段被称为SYN报文段。然后序列号字段包含了在本次连接的这个方向上要使用的第一个序列号,后续序列号和返回的ACK号都与这个初始序列号(Initial Sequence Number,ISN)有关。初始序列号并不是从0和1开始,而是随机选择的。SYN报文会消耗一个序列号,这也意味着使用重传进行可靠传输,而不消耗序列号的ACK则不是。为什么ACK不需要可靠传输呢?因为TCP使用的策略是累积ACK,即使一部分ACK丢失也不影响TCP对报文段的确认。


TCP实际上定义了8位的标志位,尽管一些老的实现只理解它们中的后6位。它们中的一个或多个可被同时启用。


?CWR??拥塞窗口减(发送方降低它的发送速率)


?ECE??ECN回显(发送方接收到了一个更早的拥塞通告)


?URG??紧急(紧急指针字段有效,很少被使用)


?ACK??确认(确认号字段有效,连接建立后一般都是启用状态),TCP规定在连接建立后所有传送的报文段中ACK都必须置1


?PSH??推送(接收方应尽快给应用程序传送这个数据)


?RST??重置连接(连接取消,经常是因为错误)


?SYN??用于初始化一个连接的同步序列号。当SYN=1,ACK=0时表示这是一个连接请求报文段,若对方同意连接,则响应报文段中SYN=1,ACK=1


?FIN??FIN位置1时表示该报文段的发送方已经结束向对方发送数据,并要求断开连接


初始序列号

当一个连接打开时,任何拥有合适的IP地址、端口号、符合逻辑的序列号(即在窗口中)以及正确校验和的报文段都将被对方接收。然而,这也引入了另一个问题。在一个连接中,TCP报文段在经过网络路由后可能会存在延迟抵达与排序混乱的情况。为了解决这一问题,需要仔细选择初始序列号(Initial Sequence Number,ISN)。


在发送用于建立连接的SYN之前,通信双方会选择一个初始序列号。初始序列号会随时间而改变,因此每一个连接都拥有不同的初始序列号。[RFC0793]指出初始序列号可被视为一个32位的计数器。该计数器的数值每4微秒加1。此举的目的在于为一个连接的报文段安排序列号,以防止出现与其他连接的序列号重叠的情况。尤其对于同一个连接的不同实例而言,新的序列号也不能出现重叠的情况。


由于一个TCP连接是被一对端点所唯一标识的,其中包括由2个IP地址与2个端口号构成的4元组,因此即便是同一个连接也会出现不同的实例。如果连接由于某个报文段的长时间延迟而被关闭,然后又以相同的4元组被重新打开,那么可以允许延迟的报文段又会被视为有效数据重新进入新连接的数据流中。通过采取一些步骤来避免连接实例间的序列号重叠问题,能够将风险降至最低。


如前文所述,一个TCP报文段只有同时具备连接的4元组与当前活动窗口的序列号,才会在通信过程中被对方认为是正确的。然而,这也从另一个侧面反映了TCP的脆弱性:如果选择合适的序列号、IP地址以及端口号,那么任何人都可以伪造出一个TCP报文段,从而打断TCP的正常连接。一种抵御上述攻击的方法是使初始序列号(或者临时端口号)变得相对难以猜出,另一种方法则是加密。


现代系统通常采用半随机的方法选择初始序列号。Linux系统采用一个复杂的过程来选择它的初始序列号。它采用基于时钟的方法,并且针对每一个连接为时钟设置随机的偏移量。随机偏移量是在连接表示(即4元组)的基础上利用加密散列函数得到的。散列函数的输入每隔5分钟就会改变一次。在32位的初始序列号中,最高的8位是一个保密的序列号,而剩余的各位则由散列函数生成。


TCP连接的建立与终止

一个TCP连接由一个4元组构成,它们分别是发送方的IP地址和端口号以及接收方的IP地址和端口号。更准确地说,一个TCP连接是由一对端点(套接字)构成,其中通信的每一端都由一对(IP地址,端口号)所唯一标识。




建立

一个TCP连接的建立需要完成以下步骤:


1.连接的主动开启者(通常称为客户端)发送一个SYN报文段,并指明自己想要连接的端口号和客户端自身的初始序列号(记为ISN(c))。通常,客户端还会借此发送一个或多个选项。


2.服务器也发送自己的SYN报文段作为响应,并包含了服务器自身的初始序列号(记为ISN(s))。此外,为了确认客户端的SYN,服务器将其包含的ISN(c)数值加1后作为返回的ACK数值。因此,每发送一个SYN,序列号就会自动加1。这样如果出现丢失的情况,该SYN段将会重传。3.为了确认服务器的SYN,客户端将ISN(s)的数值加1后作为返回的ACK数值。


总结起来,TCP连接的建立过程实际上是客户端与服务器利用SYN报文段交换彼此的初始序列号(包括客户端的初始序列号和服务器端的初始序列号)。详细来说,三次握手的目的不仅在于让通信双方了解一个连接正在建立,还在于利用数据包的选项来承载特殊的信息,交换初始序列号(Initial Sequence Number,ISN)。


TCP连接的建立为什么是三次握手?前述可知,TCP连接建立的前两次握手的主要目的在于交换连接双方的初始序列号ISN。而第三次握手的目的是为了防止失效的连接请求到达服务器端,让服务器错误打开连接。客户端发送的连接请求或服务器端的ACK如果在网络中长期滞留,客户端可能会重新请求连接,如果不进行三次握手,服务器端就会打开两次连接。如果有第三次握手,客户端会忽略服务器之后发送的对滞留连接请求的连接确认,不进行第三次握手,就不会再次打开连接。


终止

TCP连接的终止(关闭)也叫四次握手。连接的任何一方都能够发送一个关闭操作。此外,该过程还支持双方同时关闭连接的操作。通常一个关闭操作是由应用程序提出关闭连接的请求而引发的(例如使用系统调用close)。TCP协议规定通过发送一个FIN段来发起关闭操作。只有当连接双方都完成关闭操作后,才构成一个完整关闭,过程如下:


1.连接的主动关闭者发送一个FIN段指明接收者,向接收者发送自己当前的序列号(假设为K)。FIN段还包含了一个ACK段用于确认对方最*一次发来的数据(假设为L)。


2.连接的被动关闭者将K的数值加1作为响应的ACK值,以表明它已经成功接收到主动关闭者发送的FIN。此时,上层的应用程序会被告知连接的另一端已经提出了关闭请求。通常,这将导致应用程序发起自己的关闭操作。接着,被动关闭者将身份转变为主动关闭者,并发送自己的FIN,该报文段的序列号为L。


3.为了完成连接的关闭,最后发送的报文段还包含一个ACK用于确认上一个FIN。如果出现FIN丢失的情况,那么发送方将重新传输直到接收到一个ACK确认为止。


如上所述,7个报文段是每一个TCP连接的正常建立与关闭的基本开销。因此当只需要交换少量的数据时,一些应用程序更愿意选择在发送与接收数据之前不需要建立连接的UDP协议。


TCP半关闭

TCP连接的半关闭状态指连接的一方已经完成了数据发送工作并发送一个FIN给对方,但它仍然希望接收来自另一方的数据直到对方发送一个FIN给我。


半关闭操作过程与普通TCP连接类似。首先发送的两个报文段与TCP正常关闭完全相同:初始者发送的FIN,接着是接收者发送回应这个FIN的ACK。由于接收到半关闭的一方仍然能够发送数据,因此后续操作与正常TCP操作不同。



同时打开与关闭

连接同时打开在如下情况下可能发生:通信双方在接收到来自对方的SYN之前必须先发送一个SYN;两个SYN必须经过网络送达对方。该场景还要求通信双方都拥有一个IP地址和端口号,并且将其告知对方。上述情况十分少见。


一个同时打开过程需要交换4个报文段,比普通的三次握手增加了一个。由于通信双方都扮演了客户端与服务器的角色,因此不能够将任何一方称作客户端或服务器。同时关闭并没有太大区别。



在同时打开中,与正常的连接建立过程相比,需要增加一个报文段。数据包的SYN位置位直到一个ACK数据包为止。


在同时关闭中交换的报文段与正常关闭相似,只是报文段的顺序是交叉的。同时关闭需要交换与正常关闭相同数量的报文段。两者真正的区别在于报文段序列式交叉的还是顺序的。


?


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?本文部分内容摘自《TCP/IP详解 卷1:协议 (中文版)第2版》



友情链接: