TCP协议学习笔记

       最近做网络抓包分析,从上半年的看snort的TCP重组代码到现在几乎每天都离不开它。但是每次都是遇到问题零零散散的看了一些资料,也没有做一个系统的总结。趁着还没全忘掉就好好整理一下收集的学习资料,大部分来源于伟大的学长之前做网页还原时的总结材料,以及《TCP/IP详解一:协议》、rfc793、网上整理出来的资料,最后配上wireshark的抓包分析。

  • TCP简介

      TCP TCP/IP 协议栈中传输层协议, TCP 为应用层提供了一种面向连接的、可靠的字节流服务。面向连接意味着两个 使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据之前必须先建立一个TCP连接。
  • TCP可靠性主要通过以下方法去保证:
  •  应用程序被分割TCP认为最适合发送的数据块。
  •  当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  •  当TCP收到发自TCP连接另一端的数据,它将发送一个确认
  •  TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个文段和不确认收到此报文段(希望发端超时并重发)。
  •  既然TCP报文段作为IP数据报来传输,而I P数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
  •  既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据
  • TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
  • TCP首部分析

    学习TCP,首先自然得从TCP包头开始。下面是TCP首部的示意图:
   TCP协议学习笔记_第1张图片
         图中展示了整个TCP首部的组成,下面就每个具体的字段来详细了解一下;

1. 源和目的端口号  

       前32位分别是,16位的源和目的端口号,用于寻找发送端和接收端应用进程。可以这样来看,端口号用来区别主机中的不同进程,而IP地址则是用来标识不同的主机。这两个源端口值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。


2.  32位的序列号

        序列号是用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节在数据流中的序号。如果将字节流看作在两个应用程序间的单向流动,则TCP用序列号对每个字节进行计数。序号是32bit的无符号数。当建立一个新的连接时,SYN标志变1。序号字段包含由这个主机选择的该连接的初始序号ISN(Initial SequenceNumber),该主机要发送数据的第一个字节序号为这个ISN加1,因为SYN标志消耗了一个序号(PS:F I N标志也要占用一个序号)。


3.  32位的确认序列号

         既然每个传输的字节都被计数,32位确认序列号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当是上次已成功收到数据字节序号加1。不过,只有当标志位中的ACK标志(下面介绍)为1时该确认序列号的字段才有效。


4.  4位的首部长度

        4位的首部长度给出首部中32 bit字的数目。需要这个值是因为任选字段的长度是可变的。这个字段占4bit(最多能表示15个32bit的的字,即4*15=60个字节的首部长度),因此TCP最多有60字节的首部。然而,没有任选字段,正常的长度是20字节

5.  6位标识位

        跳过6位保留字段,在TCP首部中有6个标志比特。它们中的多个可同时被设置为1。依次为URG,ACK,PSH,RST,SYN,FIN。(可以参考这里,有更多详细的解释: http://apps.hi.baidu.com/share/detail/40940922 )

  • URG:此标志表示TCP包的紧急指针域(后面马上就要说到)有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据; 
  • ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0; 
  • PSH:这个标志位表示Push操作。所谓Push操作就是指在数据包到达接收端以后,立即传送给应用程序,而不是在缓冲区中排队; 
  • RST:这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包; 
  • SYN:表示同步序号,用来建立连接。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1,ACK=0;连接被相应的时候,SYN=1,ACK=1;这个标志的数据包经常被用来进行端口扫描。扫描者发送一个只有SYN的数据包,如果对方主机响应了一个数据包回来 ,就表明这台主机存在这个端口;但是由于这种扫描方式只是进行TCP三次握手的第一次握手,因此这种扫描的成功表示被扫描的机器不很安全,一台安全的主机将会强制要求一个连接严格的进行TCP的三次握手; 
  • FIN: 表示发送端已经达到数据末尾,也就是说双方的数据传送完成,没有数据可以传送了,发送FIN标志位的TCP数据包后,连接将被断开。这个标志的数据包也经常被用于进行端口扫描

6.  16位窗口大小

       TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。窗口大小是一个16 bit字段,因而窗口大小最大为65535字节。

7.   16位校验和

       检验和覆盖了整个的TCP报文段: TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。

8.   16位紧急指针

      只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。

9.   可选项字段
       TCP的选项是可选的,但是几乎在每一个SYN报文段中都含有TCP选项字段。常见的TCP选项如下:(http://blog.sina.com.cn/s/blog_4290ece1010008ew.html )

  Kind=0:选项表结束(1字节)

             End of Option List

            +--------+

            |00000000|

            +--------+

             Kind=0

  Kind=1:无操作(1字节)

        No-Operation
        +--------+
        |00000001|
        +--------+
         Kind=1   

 Kind=2:最大报文段长度(4字节) 

          Maximum Segment Size 即MSS选项

        +--------+--------+---------+--------+

        |00000010|00000100|   max seg size   |

        +--------+--------+---------+--------+

         Kind=2   Length=4 

Kind=3:窗口扩大因子(4字节)

          TCP Window Scale Option(WSopt):

         +-----------+-----------+-----------+

          | Kind=3|Length=3|shift.cnt|

          +-----------+-----------+-----------+

          Kind: 3 Length: 3 bytes 

Kind=8:时间戳(10字节)

          TCPTimestamps Option (TSopt):

        +------+-----+------------------+----------------------+ 

        |Kind=8 |  10   |   TS Value(TSval)  |TS Echo Reply (TSecr)| 

        +------+-----+------------------+----------------------+     

            1          1                   4                           4
        Kind: 8       Length: 10 bytes

       每个选项的开始是1字节kind字段,说明选项的类型。kind字段为0和1的选项仅占1个字节。其他的选项在kind字节后还有len字节。它说明的长度是指总长度,包括kind字节和len字节。设置无操作选项的原因在于允许发方填充字段为4字节的倍数(应为tcp首部长度记录的是按32bit的字长计算包头长度,其必然为4字节的倍数)。下面详细地介绍下几个常见的选项。

      kind=3 窗口扩大因子通过将原窗口大小进行移位操作来进行扩大,一个字节的移位记数器取值最小为0(没有扩大窗口的操作),最大为14。这个最大值14表示窗口大小为1 073 725 440字节(65535<<14)。窗口扩大因子有如下三个约定:

1、  只有主动连接方第一个SYN可以发送窗口扩大因子;

2、  被动连接方接收到带有窗口扩大因子的选项后,如果支持,则可以发送自己的窗口扩大因子,否则忽略该选项;

3、  如果双方支持该选项,那么后续的数据传输则使用该窗口扩大因子。

       kind=2 最大报文段长度(MSS)是TCP提供可靠性的一种重要的方式。通过MSS,应用数据被分割成TCP认为最适合发送的数据块。跟最大报文段长度最为相关的一个参数是网络设备接口的MTU,以太网的MTU是1500,基本IP首部长度为20,TCP首部是20,所以MSS的值可达1460(MSS不包括协议首部,只包含应用数据)。同窗口扩大因子一样,MSS也只能出现在SYN报文段中进行协商,如果协商不成功,则默认为536字节。

       kind=8 时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值,从而允许发送方为每一个收到的ACK计算RTT(Round-Trip Time: 往返时延,从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延)。 

  • TCP连接

       TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。TCP连接的建立与首部的标志比特有关,如上文介绍包头中有6个bit的标志位依次为URG 、ACK、 PSH、 RST、 SYN、 FIN,具体代表的意义上文已经详细介绍了,这里不再赘述。(本节可以参看 http://blog.chinaunix.net/space.php?uid=20587912&do=blog&id=405055 )

1.  3次握手--建立连接

  在TCP/IP协议中,TCP协议提供可靠的连接服务连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换 TCP窗口大小信息。

  第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

  第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态

  第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

  完成三次握手,客户端与服务器开始传送数据。

    

     可以用wireshark实际抓包来验证三次握手,下图中前三个数据包分别为三次握手的数据包:

2. 4次挥手--断开连接

       第一次, 主机1(连接的任意一端,可以为服务端,也可以为客户端)发送FIN报文段。

       第二次, 主机2收到这个FIN报文段,发回一个ACK报文段,确认序号为收到序号加1。

       第三次, 主机2发回一个FIN报文段。

       第四次, 主机1收到这个FIN报文段,发回一个ACK报文段,确认序号为收到序号加1。

       一个TCP连接是全双工的(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭,所以正常的终止一个连接需要四次握手。收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这个FIN)执行被动关闭。通常一方完成主动关闭而另一方完成被动关闭。

      如果在两倍最大分组生存期内FIN的应答没有到达的话,FIN的发送方就会直接释放连接。一方最终也会注意到,好像对方已经不再监听该连接了,因而也会超时。虽然理论上不完美,但实际中很少出现问题。

    同样可以用wireshark捕捉最后四个挥手包,从高亮的一行起:

TCP协议学习笔记_第2张图片

3. 连接状态转换

    TCP协议学习笔记_第3张图片

      上面给出了TCP正常的状态变迁图,其中左边为服务器端程序的状态变迁,右端为客户端程序的状态变迁。其中8和9的顺序可以互换。另外在上一小节中也已经说明了,可以由任意一端首先发送FIN包终止连接。

     在状态变迁图中,2、3、4、5对应了建立连接的三次握手过程,6、7、8、9、10、11对应了终止连接的四次挥手过程。

     12号状态为2MSL超时,使用2MSL超时可以防止刚被释放的端口立即再次被重用,超时时间在RFC中指出为120s。

     下面给出每个状态的意义:

  1. CLOSED: 这个没什么好说的了,表示初始状态。

  2. LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处 于监听状态,可以接受连接了。

  3. SYN_RCVD: 这个状态表示接受到了SYN报 文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手 过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文 后,它会进入到ESTABLISHED状态。

  4. SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。

  5. ESTABLISHED:这个容易理解了,表示连接已经建立了。

  6. FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报 文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当接受到对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况 下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。

  7. FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点 数据需要传送给你,稍后再关闭连接。

  8. TIME_WAIT: 表示收到了对方的FIN报 文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标 志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

  9. CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发 送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报 文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报 文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一 个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

  10. CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一 个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文 给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话, 那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。

  11. LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报 文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态  了。

4.  PS:附上两个网上常见的问题:

        1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

       这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。(个人理解就是,3次握手的时候双方都是准备好通话的即初衷一致,所以不需要对双方的状态作区分,服务器的应答ACK时会同时包含了自己的同步SYN报文。而会话结束的时候,双方可能在对待结束的态度不一致,一方请求结束连接FIN时,另一方还有数据没有传输完毕,所以不能在应答ACK的同时结束连接FIN)

         2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

      这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

  • TCP连接的异常情况

     上面一节介绍了正常的连接情况,但是有时候会有一些异常情况,我们将在下面进行介绍:

1.连接超时

      当服务器处于LISTEN状态收到SYN报文段时,服务器可能无法进行响应(如服务器资源不足),这时服务器不发送SYN,ACK报文段,这就进入了连接超时状态。处于连接超时状态下,客户端将多次发送SYN报文段进行重连,一般发送三次,每次之间的间隔时间增大,以给服务器端足够的时间从忙状态进入空闲状态。在程序中,通过对纯SYN包不进行处理的方式避免了重复的SYN。

2.同时打开

       两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开(simultaneous open)。同时打开对应于两个应用程序同时发送SYN报文段,都进入SYN_SENT状态。根据RFC793,在SYN_SENT状态下收到SYN报文段,将发送SYN,ACK报文段,然后进入到SYN_RCVD状态,在SYN_RCVD状态收到SYN,ACK报文段就是收到了ACK报文段,两个应用程序同时进入到ESTABLISHED状态。

3.同时关闭   

      双方都执行主动关闭也是可能的,TCP协议也允许这样的同时关闭(simultaneous close)。对于同时关闭需要增加一个状态-CLOSING状态。当应用程序发出FIN报文段后进入FIN_WAIT_1状态,在第8步中未收到ACK报文段,而是收到一个FIN报文段则发送一个ACK,并进入CLOSING状态,在CLOSING状态下收到ACK则进入TIME_WAIT状态。这样两个程序就进行了同时关闭。

4.复位报文段

     TCP首部中RST标志是用于复位的。一般说来,无论何时一个报文段发往基准的连接(referenced connection)出现错误, TCP都会发出一个复位报文段(这里提到的“基准的连接”是指由目的IP地址和目的端口号以及源IP地址和源端口号指明的连接)。

     可能服务程序并未启动进入到LISTEN状态,这时TCP发送复位报文段以通知客户端程序端口未处于监听状态

     我们可以看到,通过四次握手TCP终止一个连接,这种方式称为有序释放。但也可以直接使用复位报文段来中途释放一个复位报文段,这称为异常释放。异常终止时,发送方会清空所有待发送数据,接收方会区分发送方进行的是异常关闭还是正常关闭,以进行相应的处理。

     如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的TCP连接称为半打开(Half-Open)的。任何一端的主机异常(如突然掉电)都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。当仍处于连接状态的一方向已经处于异常状态的另一方继续发送数据时,由于接收方不知道数据报文段中提到的连接,则使用复位报文段进行应答。


  • TCP实际会话图

     用wireshark展示一个完整的tcp会话流图:


你可能感兴趣的:(tcp,socket,网络,list,服务器,测试)