如果对网络编程这一块有浓厚兴趣,也不用到处找资料了,我们一起吧,看这个专栏:与我一道重学网络编程,我们一起学习网络编程。
这个专栏将会在全部写完的那一天变成粉丝可见,支持作者创作,码字不易哦。
声明:本系列文章参考《卷一》而成。博主也是原作者W·Richard Stevens的忠实粉丝。
由于博主整不到原文链接,所以只能先设置原创了。
文章目录
- 引言
- 连接的连接与终止
- 三次握手
- 四次分手
- 连接建立超时
- TCP的半关闭
- TCP状态变迁图
- 2MSL等待状态
- TCP服务器设计
- 小结
引言
TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。
本篇精品
连接的连接与终止
三次握手
为了建立一条TCP连接:
1) 请求端(通常称为客户)发送一个 SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。这个SYN段为报文段1。
2) 服务器发回包含服务器的初始序号的 SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。一个SYN将占用一个序号。
3) 客户必须将确认序号设置为服务器的 ISN加1以对服务器的SYN报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手( 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应用程序这样做。
图 18-3中的报文段4发起终止连接,它由Telnet客户端关闭连接时发出。它将导致TCP客户端发送一个FIN,用来关闭从客户到服务器的数据传送。当服务器收到这个 FIN,它发回一个ACK,确认序号为收到的序号加 1(报文段5)。和SYN一样,一个FIN将占用一个序号。同时 TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的 TCP端发送一个FIN(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
图18-4显示了终止一个连接的典型握手顺序。我们省略了序号。在这个图中,发送FIN将导致应用程序关闭它们的连接,这些FIN的ACK是由TCP软件自动产生的。
连接建立超时
有很多情况导致无法建立连接。一种情况是服务器主机没有处于正常状态。
TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭。
虽然很少软件用,反正我是被坑过。
为了使用这个特性,编程接口必须为应用程序提供一种方式来说明“我已经完成了数据传送,因此发送一个文件结束( FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)”。
如果应用程序不调用 close而调用shutdown,且第2个参数值为1,则插口的A P I支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
我现在知道当时是为什么被坑惨了,一定要手动close()!!!
虽然我很快就反应过来要close(),但是原因我是今天才知道。
图1 8 - 1 0显示了一个半关闭的典型例子。让左方的客户端开始半关闭,当然也可以由另一端开始。开始的两个报文段和图1 8 - 4是相同的:初始端发出的 FIN,接着是另一端对这个 FIN的ACK报文段。但后面就和图1 8 - 4不同,因为接收半关闭的一方仍能发送数据。我们只显示一个数据报文段和一个ACK报文段,但可能发送了许多数据报文段。当收到半关闭的一端在完成它的数据传送后,将发送一个FIN关闭这个方向的连接,这将传送一个文件结束符给发起这个半关闭的应用进程。当对第二个 F I N进行确认后,这个连接便彻底关闭了。
TCP状态变迁图
密集恐惧症跳过
2MSL等待状态
TIME_WAIT状态也称为2MSL等待状态。每个具体 TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。我们知道这个时间是有限的,因为 TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
RFC 793 [Postel 1981c] 指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟,或2分钟。
在实际应用中,对 I P数据报T T L的限制是基于跳数,而不是定时器。
对一个具体实现所给定的 MSL值,处理的原则是:当 TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在 TIME_WAIT状态停留的时间为 2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的 FIN)。
这种2MSL等待的另一个结果是这个 TCP连接在2MSL等待期间,定义这个连接的插口(客户的I P地址和端口号,服务器的 I P地址和端口号)不能再被使用。这个连接只能在 2MSL结束后才能再被使用。
遗憾的是,大多数 TCP实现(如伯克利版)强加了更为严格的限制。在 2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
某些实现和API提供了一种避开这个限制的方法。使用插口API时,可说明其中的SO_REUSEADDR选项。它将让调用者对处于2MSL等待的本地端口进行赋值,但我们将看到TCP原则上仍将避免使用仍处于2MSL连接中的端口。
在连接处于2MSL等待时,任何迟到的报文段将被丢弃。因为处于 2MSL等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来自该连接的一个较早替身( incarnation)的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例( instance)称为该连接的替身)。我们说图1 8 - 1 3中客户执行主动关闭并进入 TIME_WAIT是正常的。服务器通常执行被动关闭,不会进入TIME_WAIT状态。这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口。这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口。如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于 2MSL连接的一部分。在重新启动服务器程序前,它需要在1 ~ 4分钟。
TCP服务器设计
找我入门啊
Socket/Epoll/Pthread 你必须拥有
小结
两个进程在使用 T C P交换数据之前,它们之间必须建立一条连接。完成后,要关闭这个连接。本章已经详细介绍了如何使用三次握手来建立连接以及使用 4个报文段来关闭连接。
弄清TCP操作的关键在于它的状态变迁图。
一个TCP连接由一个4元组唯一确定:本地 IP地址、本地端口号、远端 IP地址和远端端口号。无论何时关闭一个连接,一端必须保持这个连接,我们看到 TIME_WAIT状态将处理这个问题。处理的原则是执行主动打开的一端在进入这个状态时要保持的时间为 TCP实现中规定的MSL值的两倍。