TCP深度解析


前言

不管是客户端还是服务端开发,网络协议这一块都是要学习和了解的

 

 

工作和面试中,网络协议都会用到,虽然学习网络对编码没有实质的帮助,但是对你处理网络连接问题,帮助很大。下面来学习一下;

 

缘起

在世界上各地,各种各样的电脑,运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候,所使用的方法是千差万别的。

计算机使用者意识到,计算机知识单兵作战,并不会发挥太大的作用,只有把他们联合起来,电脑才会发挥它最大的潜力。

于是人们就想发设发的,用电线把电脑连接到一起。但是简单的连接一起是远远不够的,就好像语言不通的两个人互相见了面,完全不能交流信息。因为他们需要定义一些共通的东西来进行交流,TCP/IP就诞生 了

TCP/IP不是一个协议,而是一个协议族的统称。里面包括了IP协议,IMCP协议,TCP协议,以及我们更加熟悉的http,ftp协议等等。电脑有了这些,就好像学会了外语一样,就可以和其他的计算机终端做自有的交流了。

 

什么是TCP

TCP(传输控制协议)是一种面向连接的,可靠的,基于字节流的传输层通信协议,它完成第四层传输层所指定的功能,网络模型下面介绍。

TCP协议的特点是:

 

 

1,面向连接:一定【一对一】才能连接,不能像UDP协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的。

2,可靠交付:无论网络链路中出现了怎样的链路变化,TCP都可以保证一个报文一定能够到达接收端。

3,面向字节流:也就是说仅仅把上层协议传递过来的数据当成字节传输。

 

网络模型

七层模型

国际标准化组织ISO,在1981年正式推荐了一个网络系统结构一七层参考模型,也叫开放系统互联模。由于这个标准模型的建立,使得各种计算机网络均向它靠拢,大大推动了网络通信的发展。

这个ISO层网络模型各层的名字,主要功能对应的典型设备和传输单位如下图:

 

 这个七层网络模型在数据的传输过程中还会对数据进行封装,如下图:

 

 

ISO 层网络模型中,当一台主需要传送用户数据data时,数据首先通过应用层的接口进入应用层。

先看几个常见报头属于简写:

1,应用层报头:Ppplication Header 简称 AH 

2,表示层报头:Presentation Header 简称 PH

3,会话层报头:Session Header 简称: SH

4,传输层报头:Transport Header 简称 TH。

5,网络层报头:NetWork Header 简称:NH

6,数据链路层报头:Data link Header 简称 DH 

7,应用层协议数据单元:Protocol Data Unit 简称 PDU

8,数据链路层报尾:Data link Terimination 简称 DT.

在应用层,用户的数据被加上应用层的报头AH,形成应用层协议数据单元PDU,然后被递交到下层表示层。

表示层并不关心上层应用层的数据格式,而是把整个应用层递交的数据包,看成一个整体进行封装,即佳航表示层的报头PH,然后,递交到下层会话层。

同样,会话层,传输层,网络层(假设用TCP传输,则是TCP数据+IP包头)、数据链路层(把上层的TCP数据+IP头同意称为帧数据,即帧+帧数据+帧尾(CRC)也都要分别给上层递交下来的数据加上自己的报头)

他们是:会话层报头SH,传输层报头TH,网络层报头NH和数据链路层报头DH。其中,数据链路层还要给网络层递交的数据加上数据链路层报尾形成最终的一帧数据。

当一帧数据,通过物理层传送到目标主机的物理层时,该主机的物理层把它递交到上层------数据链路层。数据链路层负责去掉数据帧的帧头部和尾部,如果数据没有出错,则递交到上层网络层。

同样,网络层、传输层、会话层、表示层、应用层、也要做类似的工作,最终,原始数据被递交到目标主机的具体应用程序中。

 

五层网络模型

五层模型的网络体系也经常被提到,这五层的名字和功能分表如下所述:

1,应用层: 确定进程之间通行的性质,以满足用户的需求。应用层协议有很多。如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议等等

2,传输层:负责主机间不同进程的通信。这一层的协议有面向连接的TCP(传输控制协议),无连接的UDP(用户数据报协议);数据传输的单位称为报文段或者用户数据报。

3,网络层:负责分组交换网中不同主机间的通信。作用为:发送数据时,将运输层中的报文段或者用户数据报封装成IP数据报,并选择合适路由。

4,数据链路层:负责将网络层的IP数据报组装成帧。

5,物理层:头明明的传输比特流。

 

四层网络模型

前面的两种模型都是学术上的概念,使用并不广泛,还有一个四层模型,使用最为广泛 ----TCP/IP分层模型。几种模型如下图:

 

 

TCP/IP分层的四层模型的协议层分别完成一下的功能:

1,网络接口层:包括用户协作IP数据,在已有网络截止上传输的协议。实际上TCP/IP标准并不定义与ISO数据链路层和物理层相对应的功能。相反,它定义了像ARP(地址解析协议)这样的协议,提供了TCP/IP协议的数据结构和实际物理硬件之间的接口。

2,网络层:网络层对应于IOS七层参考模型的网络层。本层包含IP协议,RIP协议(路由信息协议),负责数据的包装,寻址和路由。同时还包含ICMP(网间控制报文协议)用来提供网络诊断信息。

3,传输层:传输层对应于IOS七层考考模型的传输层,它提供两种端到端的通信服务。其中TCP协议提供可靠的数据流运输服务,UDP协议提供不可靠的用户数据报服务。

4,应用层:应用层对应于IOS七层参考模型的应用层和表示层。因特网的应用层协议包括FTP(文件传输协议),HTTP(超文本传输协议),Telent(远程终端协议),SMTP(简单邮件传送协议),IRC(因特网终端会话),NNTP(网络新闻传输协议)等

 

综上所述,我们需要知道TCP协议在网络的IOS的七层模型的第四层传输层,IP协议在第三层网络层,ARP协议在第二层的数据链路层;在第二层上的数据叫Frame,在第三层上的数据叫Packet,第四层的数据叫Segment所有程序的数据首先会打包到TCP的Segment中。

然后TCP的Segment会打包到IP的Packet,然后在打包到以太网Ehternet的Frame中,传到端后,各自解析自己的协议,然后把数据交给更高层的协议处理。

 

TCP头格式

在学习TCP连接之前,还要学习一下TCP头部格式。因为TCP连接建立,需要用到TCP包来交换和管理数据,下面看一下TCP头部格式。

 

 

TCP头部里每个字段都为管理TCP连接和控制数据流起了重要作用。

16位端口号:告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或者应用程序(目的端口)的

进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。所有知名服务使用的端口号都定义在/etc/services文件中

32位序号(sequence number):一次TCP通信(从TCP连接建立到断开)过程中传输方向上的字节流的每个字节编号。

32位确认号(acknowledgement number):用作对另一方发送来的TCP报文段的相应其值是收到的TCP报文段的序号值加1.

4位头部长度(header length):标识该TCP头部有多少个32bit(4byte 因为最大能表示15,所以TCP 头部最长是60 byte)

6位标志位包含如下几项:

 ·URG 标志,表示紧急指针是否有效。

 ·ACK标志,表示确认号是否有效,一般称携带ACK标志的TCP报文段为“确认报文段”

 ·PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不降接收到数据读走,他们就会停留在TCP接收缓冲区中)

 ·RST标志,表示要求地方重新建立连接,一般称携带RST标志的TCP报文段为“复位报文段”

 ·SYN标志,表示请求建立连接,一般称携带SYN标志的TCP报文段为“同步报文段”。FIN标志,表示通知对方本端要关机连接了,一般称携带FIN标志的TCP报文段为“结束报文段”

16位窗口大小(window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。

16位校验和(TCP checksum):有发送端填充,接收端对TCP报文段执行CRC算法,以检验TCP报文段在传输过程中是否有损坏。注意,这个校验不进包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。

16位紧急指针(urgent pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。确切的说,这个字段是紧急指针相对当前序号的偏移,不妨称之为“紧急偏移”。TCP的紧急指针是发送端向接收端发送紧急数据的方法。

综上,你需要注意如下几点:

TCP的包没有IP地址的,那是IP层上的事,但是有源端口和目的端口。

一个TCP链接需要四元组(src_ip,src_port ,dst_ip,dst_port)来表示是同一个连接,准备说是五元组,还有一个是协议,但是因为这里只是强调TCP协议,搜易只说四元组

 

Sequence Number 是包的序号,用来解决网络报的乱序问题。

Acknowledgement Number 就是ACK,用户确认收到,永爱解决不丢包的问题。

Window Advertised Window 也就著名的滑动窗口,用来解决流量控制问题。

TCP Flag 也就是包的类型,主要是用于操控TCP的状态机的。

 

TCP三次握手

其实,网络上的传输是没有连接的,TCP是一样的TCP所谓的“连接”,其实只不过是在通信的双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP的状态变换是非常重要的。

先来看一下著名的三次握手图

 

 

 TCP连接的建立可以简单的称为三次握手,而连接的中止则可以称为四次握手。

建立连接TCP/IP协议中,TCP协议提供可靠的链接服务,采用三次握手建立一个链接。

1,第一次握手:建立连接时,客户端发送SYN包到服务器,并进入SYN_SEND状态,等待服务器确认。

2,第二次握手:服务器接收到SYN包,必须确认客户的SYN,同时自己也发送一个SYN包,即SYN+ACK包,此时服务器进入SYN_RECV状态。

3,第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入ESTABLISHE状态。

完成三次握手,客户端和服务器开始传送数据,也就是ESTABLISHED状态。

 

连接建立中的异常

建连接时SYN超时问题

如果server端因为某种情况没有收到client回来的ACK,那么,这个连接还处于一个未建立的状态。于是,server端如果在一定时间内没有收到,则server端的TCP会重发SYN_ACK。

在linux下,默认重试次数是5次,重试的间隔时间从1s开始每次都翻倍,5次的重试时间间隔为1s,2s,4s,8s,16s,总共31s,第5此发出后还要等32s都知道第5此也超时了,如果第五次重传之后,还未收到客户端的ACK,server端的TCP才会把断开这个连接。

 

关于SYN Flood 攻击

攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

 

避免方式

设置 tcp_syncookies = 1。当 SYN 队列满了后,TCP 会通过源地址端口、目标地址端口和时间戳打造出一个特别的 Sequence Number 发回去(又叫cookie)。

如果是攻击者则不会有响应,如果是正常连接,则会把这个 SYN Cookie 发回来,然后服务端可以通过 cookie 建连接。

设置 netdev_max_backlog 的值,确定链接队列的大小。当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。

通过设置 netdev_max_backlog 的值,确定 SYN_RCVD 状态连接的最大个数。

通过设置 tcp_abort_on_overflow 的值。当超出处理能时,对新的 SYN 直接回报 RST,丢弃连接。

 

TCP四次握手

 

 

TCP的链接断开

TCP 一个特别的概念叫做半关闭,这个概念是说,TCP的链接是全双工(可以同时发送和接收)链接,因此在关闭连接的时候,必须关闭传和送两个方向上的链接。

客户端给服务端FIN的TCP报文,然后服务器返回给客户端一个确认的ACK报文,并且发送一个FIN报文,当客户机回复ACK报文后,连接就结束了。

在简历连接的时候,通信的双方要互相确认对方的最大报文长度(MSS),以便通信。

一般这个SYN长度是MTU减去固定IP的首部和TCP首都长度。对于一个以太网,一般可以达到1460byte。当然如果对于非本地的IP,这个MSS可能就只有536byte,而且如果中间的传输网络的MSS更加的小的话,这个值还会变得更小。

 

为什么建连接要三次握手,而断开连接需要四次握手

对于建连接的三次握手,主要是要初始化 Sequence Number 的初始值。通信的双方要互相通知对方自己的初始化的 Sequence Numbe,所以叫 SYN 。

这个号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序( TCP 会用这个序号来拼接数据)。

对于四次挥手,其实仔细看则是两次,因为 TCP 是全双工的,所以,发送方和接收方都需要 FIN 和 ACK。

只不过,有一方是被动的,所以看上去就成了所谓的四次挥手 。如果两边同时断连接,那就会就进入到 CLOSING 状态,接着就是TIME_WAIT 状态。

 

断开连接中的异常

TIME_WAIT数量太多

从上面的描述可以知道,TIME_WAIT 是个很重要的状态,但是如果在大并发的短链接下,TIME_WAIT 就会太多。TIME_WAIT过多会占用大量的内存资源和端口资源。

优化法一:tcp_tw_reuse

设置tcp_tw_reuse = 1,则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。

有一点需要注意的是,tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用。

使用 tcp_timestamps = 1 选项,还有一个前提,需要打开对 TCP 时间戳的支持,即这个时间戳的字段是在 TCP 头部的「选项」里,用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。

由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。

优化法二:tcp_max_tw_buckets

这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将后面的 TIME_WAIT 连接状态重置。

这个方法过于暴力,而且治标不治本,带来的问题远比解决的问题多,不推荐使用。

 

TCP状态流转

接下来再看一下著名的 TCP 状态流转图。

 

 

CLOSED状态:表示初始状态。

LISTEN状态:表示服务器端的某个 socket 处于监听状态,可以接受连接。

SYN_SENT状态:在服务端监听后,客户端 socket 执行 CONNECT 连接时,客户端发送 SYN 报文,此时客户端就进入 SYN_SENT 状态,等待服务端的确认。

SYN_RCVD状态:表示服务端接收到了SYN 报文,在正常情况下,这个状态是服务器端的 socket 在建立 TCP 连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用网络查询工具 netstat 是很难看到这种状态的。因此这种状态时,当收到客户端的 ACK 报文后,它会进入到 ESTABLISHED 状态。

ESTABLISHED状态:表示连接已经建立了。

FIN_WAIT_1状态:这个是已经建立连接之后,其中一方请求终止连接,等待对方的 FIN 报文 。

FIN_WAIT_1 状态是当 socket 在 ESTABLISHED 状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该 socket 即进入到 FIN_WAIT_1 状态。而当对方回应 ACK 报文后,则进入到 FIN_WAIT_2 状态。

当然在实际的正常情况下,无论对方处于何种情况,都应该马上回应 ACK 报文,所以 FIN_WAIT_1 状态一般是比较难见到的,而 FIN_WAIT_2 状态还可以用 netstat 看到。

FIN_WAIT_2状态:实际上 FIN_WAIT_2 状态下的 socket ,表示半连接,即有一方要求关闭连接,但另外还告诉对方:我暂时还有点数据需要传送给你,请稍后再关闭连接。

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

CLOSING状态:这种状态比较特殊,实际情况中应该是很少见。正常情况下,当发送 FIN 报文后,按理来说是应该先收到(或同时收到)对方的ACK 报文,再收到对方的 FIN 报文 。但是 CLOSING 状态表示你发送 FIN 报文后,并没有收到对方的 ACK 报文,反而收到了对方的 FIN 报文 。

如果双方几乎在同时关闭一个 socket 的话,那么就出现了双方同时发送 FIN 报文的情况,就会出现 CLOSING 状态,表示双方都正在关闭 socket 连接。

CLOSE_WAIT状态:表示在等待关闭。当对方关闭一个 socket 后发送 FIN 报文给自己时,系统将毫无疑问地会回应 ACK 报文给对方,此时则进入到 CLOSE_WAIT 状态。

接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有,那么你也就可以关闭这个socket了,发送 FIN 报文给对方,即关闭连接 。CLOSE _WAIT 状态下,需要完成的事情是等待你去关闭连接。

LAST_ACK状态:这个状态还是比较好理解的,它是被动关闭 方在发送 FIN 报文后,最后等待对方的 ACK 报文。

CLOSED状态:当收到 ACK 报文后,也即可以进入到 CLOSED 可用状态了。

2MSL 等待状态:在 FIN_WAIT_2 发送了最后一个 ACK 数据报以后,要进入 TIME_WAIT 态,这个状态是防止最后一次握手的数据报没有传送到对方那里而准备的。

由于 socket 2MSL 状态,使得应用程序在 2MSL 时间内无法再次使用同一个 socket ,对于客户程序还好 些,但是对于服务程序(httpd),它总是要使用同一个端口来进行服务,而在 2MSL 时间内,启动 httpd 就会出现错误(插口被使用)。

为了避免这个错误,服务器给出了一个平静时间的概念,这是说在 2MSL的时间内,虽然可以重新启动服务器,但是这个服务器还是要平静地等待 2MSL 的时间才能进行下一次连接。

FIN WAIT_2 状态:这就是著名的半关闭状态了,这是在关闭连接时,客户端和服务器两次握手之后的状态 。

在这个状态下,应用程序还有接收数据的能力。已经无法发送数据,但是也有一种可能是,客户端处于FIN_WAIT_2 状态,而服务器则一直处于 WAIT_CLOSE 状态,直到应用层来决定关闭这个状态。

RST 同时打开和同时关闭:RST 是另一种关闭连接的方式,应用程序应该可以判断RST 包的真实性,即是否为异常中止 而同时打开和同时关闭则是两种特殊的 TCP 状态,发生的概率很小。

 

总结

本文主要讲述了网络分层模型,以及各层的作用,数据包是怎么组装和拆包的。TCP 包结构也大致学习了下,还有 TCP 连接的建立和断开。

TCP 连接建立之后才开始发数据包,所以 TCP 三次握手很重要。TCP 三次握手中也可能存在一些异常,只有彻底搞懂三次握手才能正确处理这些异常。

TCP 四次挥手也很重要,server 中经常要接受和断开连接。对应断开连接中的异常,以及服务器请求量过多,只有在搞懂 TCP 四次挥手以后,处理这些问题才能得心应手。

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM