TCP协议详解(理论篇)
1. 与UDP不同的是,TCP提供了一种面向连接的、可靠的字节流服务。面向连接比较好理解,就是连接双方在通信前需要预先建立一条连接,这犹如实际生活中的打电话。助于可靠性,TCP协议中涉及了诸多规则来保障通信链路的可靠性,总结起来,主要有以下几点:
(1)应用数据分割成TCP认为最适合发送的数据块。这部分是通过“MSS”(最大数据包长度)选项来控制的,通常这种机制也被称为一种协商机制,MSS规定了TCP传往另一端的最大数据块的长度。值得注意的是,MSS只能出现在SYN报文段中,若一方不接收来自另一方的MSS值,则MSS就定为536字节。一般来讲,在不出现分段的情况下,MSS值还是越大越好,这样可以提高网络的利用率。
(2)重传机制。设置定时器,等待确认包。
(3)对首部和数据进行校验。
(4)TCP对收到的数据进行排序,然后交给应用层。
(5)TCP的接收端丢弃重复的数据。
(6)TCP还提供流量控制。(通过每一端声明的窗口大小来提供的)
2. TCP包的首部: www.2cto.com
![](http://up.2cto.com/2012/0820/20120820010645591.jpg)
(1) 若不计选项字段,TCP的首部占20个字节。
(2) 源端口号以及目的端口号用于寻找发端和接收端的进程,一般来讲,通过端口号和IP地址,可以唯一确定一个TCP连接,在网络
编程中,通常被称为一个socket接口。
(3) 序号是用来标识从TCP发端向TCP接收端发送的数据字节流。
(4) 确认序号包含发送确认的一端所期望收到的下一个序号,因此,确认序号应该是上次已经成功收到数据字节序号加1.
(5) 首部长度指出了TCP首部的长度值,若不存在选项,则这个值为20字节。
(6) 标志位(flag):
URG: 紧急指针有效
ACK:确认序号有效
PSH:接收方应尽快将这个报文段交给应用层
RST:重建连接
SYN:同步序号用来发起一个连接
FIN: 发端完成发送任务(主动关闭)
【解释】
◆ TCP提供解决方式是为了使一端告诉另外一端某些“紧急数据”已经放置在普通的数据流中,让接收端对紧急数据做特别处理。此时,URG位被置为1,并且16位的紧急数据被置为一个正的偏移量,通过此偏移量与TCP首部中的序号字段相加,可以得出紧急数据的最后一个字节的序号,常见的应用有传输中断键(在通过telnet连接过程中)。
◆ RST: 复位字段被用于当一个报文发送到某个socket接口而出现错误时,TCP则会发出复位报文段。常见出现的情况有以下几种: www.2cto.com
发送到不存在的端口的连接请求:此时对方的目的端口并没有侦听,对于UDP,将会发出ICMP不可达的 错误信息,而对于TCP,将会发出设置RST复位标志位的数据报。异常终止一个连接:正常情况下,通过发送FIN去正常关闭一个TCP连接,但也有可能通过发送一个复位 报文段去中途释放掉一个连接。在socketAPI中通过设置socket选 项SO_LINGER去关闭这种异常关闭的情况。
3. TCP的连接与终止过程:
![](http://up.2cto.com/2012/0820/20120820010646840.jpg)
(1) 三次握手:
建立一个TCP连接,必须经历三次握手过程,其中发送第一个SYN的一端将执行主动打开,接收这个SYN并发回下一个SYN的另一端执行被动打开。
(2) 四次释放:
要释放一个TCP连接,需要通过四次握手过程,这是由TCP的半关闭特性造成的,因为TCP连接时全双工的,因此,需要TCP两端要单独执行关闭。值得注意的是,主动关闭的一端在发送FIN之后,依然还能正常接收对方的数据,只是通知对方它已经没有数据需要发送了,同理,被动关闭的一端在收到FIN之后,仍然可以发送数据,直到它自身同样发出FIN之后,才停止发送数据。
(3) TCP连接的超时问题:
完成一个TCP连接,中间涉及到一个超时的问题,大多数伯克利系统的超时时限为75s,Solaris9的超时时限为240s,因此,一般认为是在75-240之间。
【引申】在具体的实现中,如何由用户自己去完成设置socket连接超时时间?
【解决方法】目前实现socket超时连接主要是通过select来完成的。具体步骤如下:
◆ 建立socket
◆ 将socket设置为非阻塞模式(若是阻塞模式,那么时间设置就毫无意义)
◆ 调用connect去进行连接
◆ 使用select检查socket是否可写,并同时判断其结果(为什么是可写?因为需要检测socket是否收到ACK。)
◆ 将socket转化为阻塞模式
(4)TCP的半关闭 www.2cto.com
所谓“半关闭”,是指连接的一端在结束它的发送之后还能接收到对方发过来的数据的能力。具体表现在,当完成三次握手的双方,其中有一端发出FIN,此时它将进入半关
闭状态,此时它关闭了自身的发送功能,但是它依然可以接收到对方的数据,如对方发过来的ACK消息。那么在实际开发中,是怎么实现的呢?
这牵涉到系统中shutdown和close函数的区别问题。
int shutdown(int s, int how) <sys/socket.h>
shutdown是用来终止参数s所指定的socket接口,参数how主要有以下几种情况:
how = 0 终止读取操作
how = 1 终止写入操作
how = 2 终止读取和写入操作
返回的errorcode可能有:
EBADF /* Bad file descriptor */
ENOTSOCK /* Socket operation on non-socket */
ENOTCONN /* Socket is not connected */
【引用】
Big difference between shutdown and close on a socket is thebehavior when the socket is shared by other processes.A shutdown() affects all copies of the socket whileclose() affects only the file descriptor in one process.
Even if you close() a TCP socket, it won't necessarily beimmediately reusable anyway, since it will be in a TIME_WAITstate while the OS makes sure there's no outstandingpackets that might get confused as new information if you were to immediatelyreuse that socket for something else.
【注意】
当shutdown关闭读取部分时,则会丢掉接收缓冲区中的任何数据,并关闭该端的连接,若是关闭写入部分,TCP则会发送剩余的数据,然后终止连接的写入端。
4. TCP的状态变迁图:
![](http://up.2cto.com/2012/0820/20120820010647807.jpg)
几个状态解析:
(1) TIME_WAIT状态
这种状态也称为2MSL等待状态,MSL即一个报文段的最长生存时间,也就是报文在网络中被丢弃前的最长时间。那么为什么需要等待2倍的MSL呢?这是因为在TIME_WAIT状态之后,需要执行主动关闭,发送ACK,同时还需要加上一倍的MSL,为了等待对方的反馈结果(是否收到重发的FIN),这是因为再发送ACK之后,可能因为诸多原因而导致ACK发送失败,此时Server端会在此发送FIN。
正常情况下,client在2MSL期间,对应的socket是不能再被使用的,但是在具体的实现中(如伯克利),则可以通过SO_REUSEADDR选项重用此接口。
(2) FIN_WAIT_2状态
当对方对自己发送的FIN进行了确认,此时将进入FIN_WAIT_2状态。
(3) CLOSE_WAIT状态与FIN_WAIT_1状态
当连接中的一方收到对方发过来的FIN时,它将进入CLOSE_WAIT状态,而另一端则进入FIN_WAIT_1状态。 www.2cto.com
5. TCP中流量控制机制——滑动窗口
受到诸多因素的影响,如硬件(双方网卡吞吐量差异)、网络环境,网络极易出现各种各样的拥塞,目前所采取的措施主要有以下两种:改进拥塞算法以及控制发送端和接收端的流量。本节主要讲述如何在接收端和发送端进行流量控制。
(1)滑动窗口——接收端
在讲解滑动窗口协议之前,可以回顾下最初人们为了在接收端实施流量控制所提出了的经典算法——停止等待算法,其核心思想是:接收端在收到一个数据报之后,停止接收新的数据报,直到发出ACK(对收到的数据报的确认)之后进行恢复。算法思想以及实现非常简单,但是却遇到一个效率的问题,特别是随着网络设备的数据处理能力得到了很大的提高,效率低下显得尤其明显,后来人们也尝试了多种改进措施,如本节的滑动窗口就是其中之一。
其基本原理是:在接收端存在一个接收缓存区,用来接收来自于发送方的数据,只有当应用进程从接收缓存区中取出数据(可能只是部分)并发出其ACK后,才算作这部分数据已经接收,然后调节此时的滑动窗口大小。发送方根据返回的窗口大小,计算出所能发送的数据大小。因此,可以这么理解,滑动窗口算法是接收端作为主动方根据自身的缓存以及处理能力主动去调节对方的发送流量的一种调节算法。
下面通过一个滑动窗口模型图了解下发送方是如何处理所收到的滑动窗口的?
![](http://up.2cto.com/2012/0820/20120820010648344.jpg)
在发送方依然存在一个缓存区(发送缓存区),其发送的数据可以是下面几种状态:
◆ 发送并确认 (1-3)
◆ 发送但未确认 (4-6) www.2cto.com
◆ 可以发送 (7-9)
◆ 不能够发送 (10以后)
值得注意的是,滑动窗口是基于所收到的确认序列号的。当发送方根据所收到的确认序列号以及窗口大小,不断向后移动,并相应更新数据状态。
(2)滑动窗口——发送端(拥塞窗口)
网络拥塞出现的原因是多方面的,除了收发两段的硬件差异之外,还与网络通信链路相关,如通信链路中转发
路由器的缓存,试想这样一种情况,若发送端与接收端的处理能力以及吞吐能力很强,若它仅仅通过接收端所返回的滑动窗口大小,难以阻止数据报在被路由器转发过程中因为阻塞而被丢弃的情形,因为与发送端相连的路由器因为自身的缓冲空间的限制,难以存储并转发这么多数据而出现丢包现象。那么这种情况如何避免呢?最好的机制是中间的路由器也要参与给发送端反馈窗口大小的事情,也正是本节所说的拥塞窗口。
综合上面的阐述,发送端会收到两个窗口大小,分别是来自于接收端和中间路由器,注意前者在每次的数据报中都会出现,而后者只是在网络中出现拥塞时由中间路由器发送。那么,发送方此时取接收端的窗口大小与拥塞窗口中的最小值作为发送上限值。