TCP提供一种面向连接的、可靠的字节流服务。
面向连接指,发送和接收方在交换数据前必须建立一个TCP连接。顺便说下,一个TCP连接只有两方,因此广播和多播是不能应用于TCP的。
字节流指,两个应用程序通过TCP连接交换8bit字节构成的字节流。
可靠性:
1. 应用数据被分割成TCP认为最适合发送的数据块。TCP传给IP的叫报文段,也叫段(segment)
2. 当TCP发出一个段后,启动一个定时器,等待目的端确认收到。如果不能及时收到,发送端将重发这个报文段。
3. 接收端收到TCP数据,将发送一个确认。并非立即发送,要推迟几分之一秒。
4. TCP将保持它首部和数据的校验和。端到端的校验和。
5. 由于IP数据报可能会有重复,因此TCP要丢弃重复的。
6. TCP还有流量控制。通告窗口(接收端的流量控制):TCP接收端有个缓冲区,发送端不会发出超过缓冲区,直到缓冲区有空间了,避免溢出;拥塞窗口(发送端的流量控制):每收到一个ACK,拥塞窗口就增加一个报文段。
TCP首部: TCP报文段也是依赖在IP数据报中。TCP首部和IP首部长度类似,如果都不算选项长度,那么都是20个字节。
TCP首部如图:
1. 开头是两个16bit的源端口和目的端口。用来区分用户进程。这两个和IP首部的两个IP地址,共同确定TCP的一个连接。
2. 32bit序号: 序号用来标识TCP发端到收端的数据字节流,表示在这个报文段的第一个数据字节。类似于TCP用序号对每个字节进行了计数。
建立新连接时,有个ISN初始序号(发送sys请求连接时,客户机要用这个序号),这样当连接(3次握手)建立好以后,客户机要发送第一个数据(注意建立连接时,只有TCP的首部交换,没有数据交换)时,它利用的序号就是ISN+1。
3. 32bit的确认序号: 确认序号是接收端所期望的下一个序号。
4. 4bit长度,表示最大60字节(1个bit表示32bit长度)和IP首部一样
5.
URG: 紧急指针有效 Urgent
ACK: 确认序号有效 Acknowledgement
PSH: 接收方尽快交给应用程序 Push
RST: 重新建立连接 Reset
SYN: 同步序号用来发起一个连接 Syschronized
FIN: 发端完成任务 Finish
6. 16bit的窗口大小: TCP的流量控制就是利用的这个窗口大小。窗口大小为字节数。发送和接收两端都有各自的窗口大小。
7. 16bit检验和: 首部和数据的检验和。和UPD类似,都需要个伪首部支撑计算。
8. 16bit紧急指针: 只有当URG为1的时候,紧急指针才有效。
9. 选项: 最常见的是最长报文大小,又称为MSS,通常都在通信的第一个报文段里指定这个选项。MSS不计算IP、TCP的首部长度(一共40个字节长)
10. 数据部分: 这个就不是首部内容了。一个TCP报文段不一定需要数据。比如建立连接和结束断开连接的时候就没有数据,只有头部在传输。
=======TCP连接的建立和终止============
上图再说:(示例是一个telnet命令连接到服务器)
-建立连接协议:
为了建立一条TCP连接:
1.) 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN,在这个例子中为1415531521)。这个SYN段为报文段1。
2.) 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时,将确认序号设置为客户的ISN加1以对客户的SYN报文段进行确认。
3.) 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)。这三个报文段完成连接的建立。这个过程也称为三次握手(three-way handshake)。
备注:当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。RFC793[Postel 1981c]指出ISN可看作是一个32bit的计数器,每4ms加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。
-终止连接协议:
建立一个连接需要3次握手,而终止连接则需要4次。因为TCP协议是全双工的,关闭是半关闭的(关闭了数据发送,仍然可以进行接收数据)。
收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的,尽管在实际应用中只有很少的TCP应用程序这样做。
首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭。双方也可以都执行主动关闭。
如图中报文段4发起终止连接,它由Telnet客户端关闭连接时发出。这在我们键入quit命令后发生。它将导致TCP客户端发送一个FIN,用来关闭从客户到服务器的数据传送(发送FIN通常是应用层进行关闭的结果,因为客户进程通常由用户交互控制,用户会键入诸如“quit”一样的命令来终止进程)。 当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段6),客户必须发回一个确认,并将确认序号设置为收到序号加1(报文段7)。
-连接建立的超时:
TCP的一个可靠性体现在超时重传上,在建立连接的时候一样。
BSD版的TCP软件采用一种500ms的定时器。当我们键入telnet命令,将建立一个6秒的定时器(12个时钟滴答(tick)),但它可能在之后的5.5秒~6秒内的任意时刻超时。尽管定时器初始化为12个时钟滴答,但定时计数器会在设置后的第一个0~500ms中的任意时刻减1。从那以后,定时计数器大约每隔500ms减1,但在第1个500ms内是可变的(我们使用限定词“大约”是因为在TCP每隔500ms获得系统控制的瞬间,系统内核可能会优先处理其他中断)当滴答计数器为0时,6秒的定时器便会超时,这个定时器会在以后的24秒(48个滴答)重新复位。
-最大报文段长度:
最大报文段长度(MSS,区别于MTU)表示TCP传往另一端的最大块数据的长度(MSS不包括首部的长度)。当一个连接建立时,连接的双方都要通告各自的MSS。当
建立一个连接时,每一方都有用于通告它期望接收的MSS选项(MSS选项只能出现在SYN报文段中。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节(这个默认值允许20字节的IP首部和2 0字节的TCP首部以适合576字节IP数据报)。
一般说来,如果没有分段发生,MSS还是越大越好,这样相对IP和TCP首部有更高的网络利用率。
如果目的I P地址为“非本地的(nonlocal)”,MSS通常的默认值为536。区分是否本地,要根据网络号和子网号、子网掩码。
如果两端的主机都连接到以太网上,比如都采用536的MSS,但中间网络采用296的MTU,也将会出现分段。使用路径上的MTU发现机制是关于这个问题的唯一方法。
-TCP的半关闭:
前边讲过TCP终止连接的时候,4次握手就是由于半关闭。
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓半关闭。
如图:
命令 #rsh bsdi sort < datafile #在远程机器上进行文件排序
在远端主机bsdi上,rshd服务器将执行sort程序,它的标准输入和标准输出都是 TCP连接。
重点理解这两个TCP的状态变迁图:
-2MSL等待:
TIME_WAIT状态也称为2MSL等待状态。每个具体 TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。实现中的常用值是30秒,1分钟,或2分钟。
处理的原则是:当TCP执行一个主动关闭(是主动关闭的一方才有这个TIME_WAIT状态),并发回最后一个LAST ACK(看上图),该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN,主要原因就是这个)。
值得注意的是,这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口对(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
比如试验中,客户端主动关闭了连接服务器的连接,然后再以同样的客户端端口号重新再连接,那么就会提示地址在使用中。
-平静等待时间:
TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间(quiet time)。
-复位报文段 Reset
TCP首部中的RST比特是用于“复位”的。一般说来,无论何时一个报文段发往基准的连接(referenced connection)出现错误,TCP都会发出一个复位报文段(这里提到的“基准的连接”是指由目的IP地址和目的端口号以及源IP地址和源端口号指明的连接)。
几种常见的发送复位报文段:
1. TCP到不存在的端口的连接请求
前面讲UDP和ICMP报文时,如果发送一个UDP包到不存在的端口,目的主机会返回给源主机一个ICMP端口不可达类型的报文。
对于TCP来说,目的主机会给源主机返回一个复位报文段。
2. 异常终止一个连接时:
终止一个连接的正常方式是一方发送FIN。有时这也称为有序释放(orderly release),因为在所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而不是FIN来中途释放一个连接。有时称这为异常释放(abortive release)。
异常终止一个连接对应用程序来说有两个优点:(1)丢弃任何待发数据并立即发送复位报文段;(2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭。
RST报文段中包含一个序号和确认序号。需要注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。(注意:双方都可以主动进行异常终止连接)
3. 检测半打开连接
如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的TCP连接称为半打开(Half-Open)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
举一个常见的造成半打开连接的例子: 当客户主机突然掉电而不是正常的结束客户应用程序后再关机。
这可能发生在使用PC机作为Telnet的客户主机上,例如,用户在一天工作结束时关闭PC机的电源。当关闭PC机电源时,如果已不再有要向服务器发送的数据,服务器将永远不知道客户程序已经消失了。当用户在第二天到来时,打开PC机,并启动新的Telnet客户程序,在服务器主机上会启动一个新的服务器程序。这样会导致服务器主机中产生许多半打开的TCP连接。
比如:一个客户端程序和服务端已经建立好了一个TCP连接,此时服务器掉电了,而客户端还保持着这个连接(当客户不发送数据的时候,彼此不知道这个连接已经断了)。当服务器的对应服务进程重启好了(当然原来的连接对服务端来说毛都不知道了),此时客户端的那个连接发来消息数据了,到了服务端,服务端懵逼了,这时候服务端就会发送一个复位报文段告知客户端,这个连接得复位了~
-TCP的同时打开、同时关闭:
这块就不介绍了...用的也比较少。详情看书
-TCP服务器的设计:
大多数的TCP服务器进程是并发的。当一个新的连接请求到达服务器时,服务器接受这个请求,并调用一个新进程来处理这个新的客户请求。不同的操作系统使用不同的技术来调用新的服务器进程。在Unix系统下,常用的技术是使用fork函数来创建新的进程。如果系统支持,也可使用轻型进程,即线程(thread)。
利用netstat命令查看信息:
如图:
*.23表示这个主机的所有接口都可以提供telnet服务(一个机器可能有多块网络接口卡,即多个IP)。
图上证明服务器主机有两个不同的IP被客户端连接了。
值得注意的是,只有这个Listen的进程能接收新的连接请求。处于ESTABLISHED的进程将不能接收SYN报文段,而处于LISTEN的进程将不能接收数据报文段。
TCP服务器本地和远端IP地址及端口号的规范:
如图:
表中行的顺序正是TCP模块在收到一个连接请求时确定本地地址的顺序。最常使用的绑定(第1行,如果支持的话)将最先尝试,最不常用的(最后一行两端的IP地址都没有制定)将最后尝试。
呼入连接请求队列:
一个并发服务器调用一个新的进程来处理每个客户请求,因此处于被动连接请求的服务器应该始终准备处理下一个呼入的连接请求。那正是使用并发服务器的根本原因。但仍有可能出现当服务器在创建一个新的进程时,或操作系统正忙于处理优先级更高的进程时,到达多个连接请求。当服务器正处于忙时,TCP是如何处理这些呼入的连接请求?
伯克利的TCP实现:
1. 正等待连接请求的一端有一个固定长度的连接队列,该队列中的连接已被TCP接受(即三次握手已经完成),但还没有被应用层所接受。注意区分TCP接受一个连接是将其放入这个队列,而应用层接受连接是将其从该队列中移出。
2. 应用层将指明该队列的最大长度,这个值通常称为积压值(backlog),一般大小是5。
3. 当一个连接请求(即SYN)到达时,TCP使用一个算法,根据当前连接队列中的连接数来确定是否接收这个连接。
4. 如果队列里的连接数没到,那么完成建立。
5. 如果连接队列中已没有空间,TCP将不理会收到的SYN。也不发回任何报文段(即不发回RST)。客户将等待着超时。
-