本篇是socket编程系列文章的第一章节,后续还将更新TCP的可靠传输、拥塞控制与流量控制等文章。希望通过总结socket网络编程这个系列,整理清楚TCP的基础知识,形成知识的内循环。本文主要讲述TCP的基本概念与连接、断开的过程,并对一些在连接断开过程中常见问题的分析。
首先,在了解TCP之前,我们先聊聊互联网的七层网络模型,OSI 模型(Open System Interconnection model)是一个由国际标准化组织提出的概念模型,试图提供一个使各种不同的计算机和网络在世界范围内实现互联的标准框架。分别包括物理层(负责最后将信息编码成电流脉冲或其它信号用于网上传输)、数据链路层(通过物理网络链路供数据传输。不同的数据链路层定义了不同的网络和协 议特征,其中包括物理编址、网络拓扑结构、错误校验、数据帧序列以及流控)、网络层(负责在源和终点之间建立连接)、传输层(向高层提供可靠的端到端的网络数据流服务)、会话层(建立、管理和终止表示层与实体之间的通信会话)、表示层(供多种功能用于应用层数据编码和转化,以确保以一个系统应用层发送的信息 可以被另一个系统应用层识别)、应用层(包括各种应用协议).
而我们的TCP协议,位于七层模型的第四层——传输层,IP在第三层——网络层,ARP在第二层——数据连路程。用wireshar抓取tcp报文可以发现,应用程序的数据首先被添加到TCP协议中,接着TCP的报文信息被添加在IP协议中,最后将IP协议信息加入到以太网Ethernet的Frame中,传到对端后,各个层解析自己的协议。
TCP报文格式
Source Port: 16 bits 发送端端口
Destination Port: 16 bits 接收端端口
Sequence Number: 32 bits 报文序列号,可有效解决包乱序的问题
Acknowledgment Number: 32 bits 报文响应ack序号
Data Offset: 4 bits TCP报头长度,开始为32位
Reserved: 6 bits 保留位
Control Bits: 6 bits 报文类型控制位
Window: 16 bits 划窗
Checksum: 16 bits 校验位
Urgent Pointer: 16 bits 紧急指针
Options: variable 可选标志,长度可变
Padding: variable 填充标志,确保TCP头结束后数据从32位边界开始 长度可变
TCP建立连接与断开连接
TCP建立连接过程就是我们常说的3次握手,客户端与服务器在此阶段主要完成序列号同步,确保在数据收发过程中传输数据可靠。四次挥手断开TCP连接,保证连接稳定断开。
网上经常会看到有问为什么建立连接需要3次,而断开却要四次,这是因为TCP是全双工通信,建立连接过程客户端与服务器建立两条通信链路,在断开连接时,A方发送FIN,表示A方没有数据在发送,B方会ack后却不一定马上回FIN,可能B还有数据需要发给A,所以B在确认没有数据发送给A时,才会向A发FIN。有同学也许会好奇A已经关闭了数据发送通道,为什么还能回Back?这是因为A放FIN后并没有完全断开与B的连接,进入了半连接状态,该状态下能接收B下发的数据,并返回ACK。所以断开要四次交互,但不一定必须要四次交互
TCP状态转换
RFC973中介绍了TCP协议的基础知识并对TCP建立连接与断开连接的状态进了介绍,上面文档给出的TCP的状态转换图。
另外,有几个事情需要注意一下
关于建连接时SYN超时。试想一下,如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了,server端没有收到client回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。
关于 MSL 和 TIME_WAIT。MSL是Maximum Segment Lifetime,表示tcp包在网络中存活的最大时间。我们注意到,在TCP的状态图中,从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是 2*MSL(RFC793定义了MSL为2分钟,Linux设置成了30s)为什么要这有TIME_WAIT?为什么不直接给转成CLOSED状态呢?主要有两个原因:1)TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到Ack,就会触发被动端重发Fin,一来一去正好2个MSL,2)有足够的时间让这个连接不会跟后面的连接混在一起(确保链路重复数据段从网络中消失)
关于TIME_WAIT数量太多。从上面的描述我们可以知道,TIME_WAIT是个很重要的状态,但是如果在大并发的短链接下,TIME_WAIT 就会太多,这也会消耗很多系统资源。只要搜一下,你就会发现,十有八九的处理方式都是教你设置两个参数,一个叫tcp_tw_reuse,另一个叫tcp_tw_recycle的参数,这两个参数默认值都是被关闭的,后者recyle比前者resue更为激进,resue要温柔一些。另外,如果使用tcp_tw_reuse,必需设置tcp_timestamps=1,否则会出问题。如果使用tcp_tw_reuse,会出现网络中上一次的TCP包被当前连路接收的问题,而解决办法就是增加选项tcp_timestamps,在每次发包时都记录当前时间,若新连接的时间大于收到的tcp数据包的时间,则将该数据包丢弃。另外,还可以修改tcp_max_tw_buckets的值,该值表示系统允许的最大TIME_WAIT,默认为180000
清风 | 文 【原创】
如果本篇博客有任何错误,请批评指教,不胜感激 !