TCP
-
可靠性
-
为什么网络中会存在不可靠?
- 根本原因就是距离太远,不可控因素太多,TCP因此就应运而生,是一种保证可靠性的协议
-
TCP协议格式
- 源/目的端口:表示数据从那个进程来,要到对端的那个进程去
- 32位序号/32位确认序号:分别代表TCP报文中每个字节数据的编号以及对对方的确认,是TCP保证可靠性的一种重要手段
- 4位首部长度:表示TCP报头中的长度,以4字节为单位,范围是0101~1111
- 6位保留字段:TCP报头中暂未使用的6个比特位
- 16位窗口大小:保证TCP可靠性和效率提示机制的重要字段
- 16位检验和:有发送端填充,采用CRC效验,接收端效验不通过,则认为数据有问题(检验和包含TCP首部+TCP数据部分)
- 16位紧急指针:标识紧急数据在报文中的偏移量,需要配合标志字段中的URG来使用
- 选项字段:TCP报头中允许携带额外的选项字段,最多40字节
-
TCP中的6个标志位
- URG:紧急指针是否有效
- ACK:确认序号是否有效
- PSH:提示接收端应用程序立刻将TCP缓冲区数据取走
- RST:要求对方重新建立连接,通常称携带RST标志的报文为复位报文
- SYN:请求与对方建立连接,通常把SYN标志的报文为同步报文
- FIN:通知对端,本端要关闭了,通常把携带FIN标志的报文称为结束报文
-
序号和确认序号
-
了解确认应答机制
- 在网络中,我们发送一个数据并不能知道对方是否收到,只有对方回复收到后,我们才能确定数据成功发送,这样才能保证可靠性,但是如果对端回复收到在传输过程中丢了,那么我们就不知道对方是否收到了,因此互联网中并没有100%的可靠性
-
32位序号
- 由于串行通信效率太低,因此允许客户端可能讲一个报文分成多个报文顺序同时发送,那么服务端收到报文可以用32位序号进行排序,这也是保证可靠性的一种方式
-
32位确认序号
- 确认序号是告诉客户端已经收到了那些数据,你的下次数据应该从哪里开始发(是告诉客户端你开始发的位置之前的数据全部收到了)
-
序号内容
- 缓冲区中每个字节都有一个编号,发送序号就是发送的首个字节下标,确认序号就是接收缓冲区接收到的最后一个有效数据下一个位置的下标
-
为什么要用两套序号机制?
- 因为TCP是全双工的,客户端和服务端都可以收发信息,如果用一套序号那么就不能保证序号意义的唯一性
-
窗口大小
-
TCP的接收缓冲区和发送缓冲区
- TCP协议具有发送缓冲区和接受缓冲区
- 数据是由上层的应用层写入到缓冲区的,具体什么时候发,怎么发都是TCP决定的,因此TCP被称为传输层控制协议
-
TCP的发送缓冲区和接收缓冲区存在的意义?
- 由于发送的时候可能出现某些错误导致需要重传,此时就需要一个缓冲区保存发送出去的数据,以免需要重传,只有发出去的数据被对端收到后,这部分数据才可以被覆盖
- 由于接收端处理数据的速度是有限的,保证没来得及处理的数据不会被丢弃,因此TCP必须得用一个接收缓冲区来保存数据
- 这属于典型的生产者消费者模型!
-
窗口大小
- 发送端将数据发给接收端,如果速度太快,对端被打满,就容易丢包,如果速度太慢,对端太过于空闲,导致效率过低,因此16位窗口就应运而生
- 16位窗口填写的是只剩接受缓冲区的剩余空间大小,这样发送端就可以根据接收端响应的滑动窗口来调整发送速度
-
6个标志位
-
为什么存在标志位?
- 因为TCP报文有多种多样,比如建立报文,普通报文,断开链接的报文等等,因此使用标记位来表示报文的含义,0为假,1为真
-
SYN
- SYN被设为1的话,表明该报文是请求报文
- 只有建立连接阶段SYN才会被标记,正常通信SYN不会被标记
-
ACK
- ACK被设为1 的话,表示该报文可以对收到的报文进行确认
- 一般除了第一个发送的报文没有被设为1以为,其余都会设为1,因为报文不仅仅代表发送数据,也代表着确认上一条报文被收到
-
FIN
- FIN被设为1,表名该报文是一个请求断开的报文
- 只有断开连接时才会被设置
-
URG
- URG被设为1,表示紧急指针要被使用
- 由于接收端接受数据是有序的,有时候有一些紧急数据想要快点被处理,就可以使用紧急指针,紧急指针指向的是数据中的一个位置,因此只能发送一个字节,具体含义不深究
- recv的第四个参数flags有一个MSG_OOB选项,其中OOB表示带外数据的简称,如果上层想要读取到紧急数据就得设置,send同理
-
PSH
- PSH被设为1,表示告诉对方尽快将你的接受缓冲区数据交付给上层
- 其实接受缓冲区是有一个水准线的,当数据高于水准线才会被read(内核态和用户态切换需要成本),否则就阻塞,PSH就可以忽略水准线,直接全部交付给上层
-
RST
- RST被设置为1,表示需要让对方重新连接
- 通信未建立好或者建立好的连接出现异常,一方发数据,另一方响应数据就会将RST设为1
-
超时重传机制
- 双方进行网络通信时,发送方发送数据在特定时间内没有得到对方答复就会进行数据重发,这就是TCP超时重传机制
- 丢包的情况:发送时丢包,对方响应丢包,导致发送方认为没有发送成功(响应丢包并不会导致数据重复接受,接收方会根据序号判断是否接受过这个报文)
- 超时重传的等待时间和数据发送一样不能太快也不能太慢,太慢会导致丢包后对方长时间收不到数据,太快会导致对方收到大量重复报文
- 超时重传时间设置:Linux中以500ms为一个单位,每次超时,等待重传时间就会之前的2倍,当累计到一定参数,TCP就会认为网络或者对端出现异常,就会进行强行关闭连接
-
连接管理机制
-
TCP的各种可靠性机制都不是主机到主机的,而是基于连接的.与连接是强相关的,如果不是基于连接的话,那么多个客户端发送数据,服务端只用一个缓冲区接受,那么会导致数据混乱
-
操作系统对连接的管理
- 操作系统对连接管理采用"先描述,在组织",在操作系统中一定是有一个描述连接的数据结构,该结构体保存连接的各种属性字段,所有定义出来的的连接结构体数据都会以某种数据结构组织起来,然后操作系统对其管理就变成了增删查改
-
三次握手
-
TCP在通信之前建立连接叫做三次握手
-
第一次握手:客户端向服务端发送SYN为1的报文,表示请求连接
-
第二次握手:服务端向客户端发送SYN和ACK为1的报文,表示请求连接并且接收到第一次握手(同意客户端的连接)
-
第三次握手:客户端发送ACK为1的报文,表示接收到第二次握手(同意服务端的连接)
-
三次握手会协商最大报文长度 (Maximum segment size(MSS)是TCP期望从对端接收的最大的报文长度),下面讲到的滑动窗口分组就是按照这个划分的
-
为什么是三次握手?
- 因为TCP是全双工通信的,因此连接建立的核心要务实际是,验证双方的通信信道是否是连通的。
- 而三次握手恰好是验证双方通信信道的最小次数,通过三次握手后双方就都能知道自己和对方是否都能够正常发送和接收数据。
- 在客户端看来,当它收到服务器发来第二次握手时,说明自己发出的第一次握手被对方可靠的收到了,证明自己能发以及服务器能收,同时当自己收到服务器发来的第二次握手时,也就证明服务器能发以及自己能收,此时就证明自己和服务器都是能发能收的。
- 在服务器看来,当它收到客户端发来第一次握手时,证明客户端能发以及自己能收,而当它收到客户端发来的第三次握手时,说明自己发出的第二次握手被对方可靠的收到了,也就证明自己能发以及客户端能收,此时就证明自己和客户端都是能发能收的。
- 既然三次握手已经能够验证双方通信信道是否正常了,那么三次以上的握手当然也是可以验证的,但既然三次已经能验证了就没有必要再进行更多次的握手了。
- 三次握手能够保证连接建立时的异常连接挂在客户端,因为三次握手是客户端先建立连接,最后第三次握手出现异常,也不会影响服务端
-
三次握手时的状态变化
- 最开始客户端和服务端都是CLOSED状态
- 服务端为了要接受客户端发送的连接请求,需要将状态变成LISTEN状态
- 客户端发送第一次握状态变成SYN_SENT(发送连接状态)
- 处于LISTEN状态的服务器收到客户端连接,将连接请求放到内核等待队列中,并且向客户端发送第二次握手,状态变成SYN_RCVD(接收到连接的状态)
- 当客户端收到服务端的第二次握手后紧接着想服务端发送第三次握手,自己状态变成ESTABLISHED(确认连接状态)
- 服务端收到客户端的第三次握手将自己的状态变为ESTABLISHED(确认连接状态)
-
套接字和三次握手的关系
- 客户端发起建立请求前,服务端必须是LISTEN状态,这个时候需要服务端调用listen函数
- 服务端进入LISTEN状态,客户端可以进行三次握手了,调用connect函数(connect不参与底层的三次握手,只是发起三次握手,当connect返回时,要么成功,要么失败)
- 完成三次握手后会建立一个链接,但是这个连接在在内核的等待队列中,需要使用accept函数将连接获取上来
- 获取到连接之后就可以像文件一样使用read/recv和write/send函数进行数据交互了
-
四次挥手
-
由于双方维护连接都是需要成本的,因此当双方TCP通信结束之后就需要断开连接,断开连接的这个过程我们称之为四次挥手。
-
第一次挥手:客户端向服务端发送FIN为1的报文请求与服务端断开连接
-
第二次挥手:服务端收到客户端的请求后对其响应
-
第三次挥手:服务器收到客户端断开连接的请求,且已经没有数据需要发送给客户端的时候,服务端向客户端发送FIN为1的报文请求与服务端断开连接
-
第四次挥手:客户端收到服务端的请求后对其响应
-
为什么是四次挥手?
- 因为TCP是全双工的,不仅要断开客户端和服务端的连接,也要断开服务端和客户端的连接
-
为什么不能将第二次和第三次挥手合并?
- 因为服务端收到客户端的连接时并不会马上发起断开连接,因为服务端可能还会有某些数据发给客户端,只有发完这些数据后才能发起断开连接
-
四次挥手的状态变化
- 在挥手之前客户端和服务端都处于ESTABLISHED(连接状态)
- 客户端主动向服务端发起断开请求,状态变成FIN_WAIT_1
- 服务端收到客户端的断开连接请求,状态变成CLOSE_WAIT
- 客户端接受到服务端的响应状态变成FIN_WAIT_1
- 服务端没有数据发给客户端,服务端会像客户端发起断开连接请求,等待第四次挥手的ACK到来,此时状态变成LASE_ACK
- 客户端收到服务端发出的第三次挥手后,会向服务端发送最后一个响应报文,此时进入TIME_WAIT状态
- 服务端收到客户端发来的第四次挥手,就会彻底关闭连接,状态变成CLOSED状态
- 客户端会等待2MSL(报文最大生存时间)才会进入CLOSED状态
-
套接字和四次挥手的关系
- 客户端发起断开连接调用close函数
- 服务端发起断开连接调用close函数
- 一个close是两次挥手,双方都调用close就是四次挥手
- 发送FIN是应用程序发送的,属于用户态不是内核态
-
CLOSE_WAIT
- 客户端发送断开请求服务端会先进入CLOSE_WAIT状态,而CLOSE_WAIT状态是服务端还没有进行断开连接的的状态,也就是说服务端还开启这大量文件描述符,如果没有不及时关闭就会导致文件描述符泄漏,也会导致连接资源没有完全释放,这其实是内存泄漏的一种,如果发现存在大量CLOSE_WAIT状态一定要看看有没有调用CLOSE函数关闭对应文件描述符
-
TIME_WAIT
-
四次挥手中前三次丢包时解决方法
- 第1次:客户端得不到服务端应答,进行超时重传
- 第2次:客户端得不到服务端应答,进行超时重传
- 第3次:服务端得不到客户端应答,进行超时重传
- 第4次:服务端得不到客户端应答,进行超时重传
-
如果发出第4次挥手时就断开连接,那么第4次挥手丢包,服务端发起超时重传已经得不到响应了
-
虽然说过超时重传若干次后会也会强制关闭连接,但是服务端这段时间还在维护连接,这对服务端非常不友好,因此客户端并没有立刻断开连接,而是进入到TIME_WAIT状态
-
TIME_WAIT状态的必要性:
- 防止第四次挥手丢包,客户端还能再一段时间内接受服务端的超时重传的FIN报文,能够大概率的保证第四次挥手的ACK让服务端收到
- 客户端发出最后一次挥手时,双方历史通信数据还可能没有发送到对方,因此客户端四次挥手后进入TIME_WAIT状态,还能保证双方通信信道上的数据尽可能的消散
-
因此TCP并不能保证建立和断开的可靠性,但是可以保证建立后和断开前的通信的可靠性
-
TIME_WAIT时间不能太长,也不能太短
- TCP协议规定,主动关闭连接的一方在四次挥手后要处于TIME_WAIT状态,等待两个MSL(Maximum Segment Lifetime,报文最大生存时间)的时间才能进入CLOSED状态。
- MSL在RFC1122中规定为两分钟,但是各个操作系统的实现不同,比如在Centos7上默认配置的值是60s。我们可以通过cat /proc/sys/net/ipv4/tcp_fin_timeout命令来查看MSL的值。
- MSL是TCP报文的最大生存时间,因此TIME_WAIT状态持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失。
- 同时也在理论上保证最后一个报文到达时间
-
流量控制
-
TCP支持接收端的数据接收能力来决定发送端发送的速度,这个机制叫做流量控制
-
发送端通过接收端发送过来的窗口大小控制自己的发送数据
-
如果发送端接收到的窗口大小为0,此时发送端不应该发送数据,那么发送端如何得知什么时候可以继续发送呢??
- 1.主动询问:发送端可以隔一段时间就给接收端发送一个不带有数据的报头来询问窗口大小
- 2.等待告知:接收端上层将数据读走后,接收端会向发送端告知自己的窗口大小
-
16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
- 实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是 窗口字段的值左移 M位;
-
滑动窗口
-
TCP为了保证发送效率,采取了连续发送多个数据的策略,而保证这个策略的机制叫做滑动滑动窗口
-
滑动窗口本质上就是两个整数来维护一段区间,我们假设记为left和right,left的左边是已经成功发送过去的数据,right的右边是待发送的数据,而这区间内是可能发送但是没收到应答或者待发送的数据
-
那么滑动窗口的left和right怎么滑动呢??
- left:通过接收方的发送的报文里的确认序号,那么就可以保证确认序号之前的都被成功接收了,那么left=确认序号
- right:根据接收方的窗口大小和下面要说的拥塞窗口而定,right=min(窗口大小,拥塞窗口)
-
滑动窗口如何分组呢?
-
快重传
- 我们知道发送一系列报文,我们响应的确认序号一定是第一个没有到达的报文,因此当某个数据包丢了,其他数据包发送到接收方,接收方响应的确认序号一定是丢失的这个数据包,而发送方大量收到(三次即可)同一个确认序号就会立刻重新发送报文,这就是快重传机制,而不会静静等待超时重传
-
拥塞控制
-
延迟应答
-
捎带应答
-
面向字节流
- 面向字节流是指将数据看做一连串的字节流,不考虑数据的边界和大小,发送方将数据不断写入一个缓冲区,接收方则不断从缓冲区读取数据。这种方式的特点是数据传输可靠,但无法保证数据的边界和完整性,因此需要在应用层上进行额外的数据解析和处理。
-
粘包
-
粘包的包是应用层的数据包,因此解决方案在于应用层
-
什么叫粘包?
- 应用层读取到数据可能多了,也可能少了,这就是粘包问题,就好比包子在笼子中,我们拿包子就可能多拿一点点,也可能少拿一点点,那么如何解决呢?? 明确界限!!
-
解决方案:划清界限
- 1.应用层规定每个数据的长度(不足用空行代替),那么接收端就按照这个长度读取即可
- 2.应用层可以在数据前面添加有效数据长度,这样接收端可以根据有效数据长度来判断后面的大小
- 3.采用特殊分隔符分开,需保证分隔符和数据不会冲突
-
UDP有没有粘包问题?
- UDP是面向数据报,UDP一次性就会将所有的数据发送过去,因此接收方发现UDP报文不完整 (根据UDP16位检验和决定是否丢弃) 就会丢弃,所以应用层拿到数据包不会出现粘包问题
-
TCP异常情况
-
进程终止:会自动调用close,此时双方会在底层调用四次挥手
-
机器重启:机器重启和进程终止一样,会在重启前调用四次挥手
-
断电/断网:如果客户端断电/断网,服务端是无法知道的,因此它们之间的连接不会立刻断开,但是也不会一直维持,TCP有保活策略
- 1.服务端会定期向客户端发送报文检测是否在线,如果多次没有收到ACK应答,那么服务端就会关闭这个连接
- 2.客户端也会定期向服务端定期发送报文"报平安",如果服务端长时间收不到客户端消息就会关闭连接
-
TCP小结
-
保证可靠性的机制
- 检验和
- 序号/确认序号
- 确认应答
- 超时重传
- 连接管理
- 流量控制
- 拥塞控制
-
提高性能的机制
-
基于TCP的常见应用层协议:
- HTTP(超文本传输协议)
- HTTPS(安全数据传输协议)
- SSH(安全外壳协议)
- Telnet(远程终端协议)
- FTP(文件传输协议)
- SMTP(电子邮件传输协议)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)