目录
一,关于Tcp协议
Tcp全称“传输控制协议(Transmission Control Protocol)”,是当今互联网使用最广泛的传输层协议,因为它基于通信时保证可靠性,并且对于高效传输也有一定策略,是目前应用层底层使用的协议中非常常见的一种协议
我们来认识下“传输控制协议”的“控制”二字含义:
- 之前实现的网络版计算器,用的是http/https,底层就是Tcp,在通信双方内部都有着发送缓冲区和接收缓冲区,我们调用write,read,recv,send等操作其实不是把数据都发送到网络中,而是把数据拷贝到操作系统的内核缓冲区中
- 至于数据什么时候发送,发送多少,出错了怎么办?这个完全由Tcp协议自主决定,所以上面那些函数本质应该叫做拷贝函数
- 这和我们文件操作一样,用write通过文件描述符写入时,都是先把数据拷贝到文件缓冲区里,然后数据从文件缓冲区刷新到磁盘文件上,完全由操作系统和磁盘驱动决定。这么看来,只要把磁盘设备换成网卡,就可以完成数据远程发送或者IO了。
- 而数据发送,本质也就是把数据从我们的发送缓冲区拷贝到对方的接收缓冲区,所以网络发送,“本质也是拷贝”。所以发送就是要通过网络,而传输距离很长,网络可能会出错,所以TCP就是为了应对这些出错做的策略
- 因为Tcp既能接收也能发送,所以双方的地位是对等的,服务器构建响应,其实也就是把响应处理好,然后通过应用层的各种接口把响应拷贝到TCP发送缓冲区,而双方也有接收缓冲区和发送缓冲区,所以发送也是拷贝,你给我拷,我给你拷,你不给我拷,我也可以给你拷,这个过程就叫做“全双工”。
- 所以,文件描述符和套接字既可以写也可以读,因为双方的发送和接收缓冲区是独立的,不会相互影响
所以“传输控制协议”的“控制”,应用层把数据拷贝到发送缓冲区里,什么时候发,发多少,出错了怎么办,其实本质也就是在控制如何发送的问题,这些工作由Tcp协议自主决定,所以我们把TCP叫做“传输控制协议”。
二,Tcp报头字段解析
2.0 协议字段图示
三部分,标准报头(前20字节),选项(暂时忽略),有效载荷(要传输的数据)。前两个字段16位源端口号和16位目的端口号和UDP一样,能够讲报文数据交付给上层的某个协议,
Tcp报头在内核代码中就是一个位段类型,给数据封装Tcp报头时,实际上就是用这个位段类型定义一个变量,然后往该变量中填充Tcp报头的各个属性字段,这个步骤和我们之前填充 sockaddr结构体 的步骤大差不差
可以看到,Tcp协议的报头字段相比Udp多了几倍,因为Tcp要保证可靠性,所以Tcp会在底层做更多的动作,下面我们来详细解释下各字段的作用
2.1 两个老问题
前面说过,学习任何协议,都离不开两个问题:
- 在TCP协议中,标准报头的长度是20个字节,在缓冲区中我们把前20个字节信息分离就拿到了报头
- 报头中有一个“4位首部长度”,表示报头的总长度是多少,因为我们还有选项(可以不带),4位首部长度范围为(0000,1111)--> (0,15),15是小于20的,乍一看不能表示报头的长度,因为4位首部长度在计算的时候,有基本的大小单位:4字节(报头的宽度),所以真正的表示范围为0 -- 60个字节,报头长度是20,所以选项最长是40个字节
- 大部分时间我们不谈选项,所以4位首部长度一般为0101,是5,就能准确代表标准报头长度,就能有效分离报头了
- 然后就和Udp一样,对有效载荷做解析,就能得到16位端口号,然后就可以交付给上层应用了
2.2 16位窗口大小
- 客户端要发送数据给服务器,先把http请求拷贝到发送缓冲区,拷贝到服务器接收缓冲区,在这个过程中,客户端和服务器基于TCP协议进行通信的时候,发送的是完整的报文,即一定携带完整的TCP报头
- 如果发送方一直发数据,但是接收方不读,所以客户端是并不知道服务器对数据的读取情况的,所以客户端就会一直发,但是服务器来不及收导致服务器接收缓冲区被写满,就会出现数据丢包
- 所以双方必须想办法在服务器“接收缓冲区快满”的情况下让客户端发慢一点或者干脆不发了,这种由发送方往接收方发消息,通过控制发送方发送的速度,来让对方来得及接收,从而防止大面积丢包的情况,我们叫做“流量控制”
- 所以我们现在的重点就是“如何让客户端知道接收端缓冲区还要多少剩余空间?”
第一次给对方发报文不知道对方的缓冲区剩余空间大小该咋办呢?我们不管,只要一段时间对方没给我发响应,我就认为对方没收到,重发
TCP有重传机制,如果有大面积丢包,TCP可以重传,但是不合理,因为网络传输是有代价的,数据已经消耗了资源到达了对方,但是却要无缘无故丢掉,这就是一种浪费,所以“流量控制”比“丢包重传”更好
2.3 32位序号和确认序号
- 客户端发tcp数据给服务器,服务器会给客户端发应答,当服务器发了应答后,就能保证服务器到客户端这条方向传输的可靠性
- 服务器发数据给客户端,客户端发应答给服务器,当服务器接收到应答,也就能保证服务器到客户端这条方向的可靠性
- 所以我们宏观上最新的消息不能保证可靠性,但是在局部上两个应答可以间接相互弥补可靠性
- 一段时间客户端没有收到应答,客户端认为数据丢失,会进行重传,服务器同理
下面我们来正式介绍一下序号的作用:
如果捎带应答同时发多个报文,也会有乱序问题,而乱序本身也是不可靠的一种,所以会在报文带上序号,保证数据的按序到达,保证可靠性,所以这个32位序号就是保证数据的按序到达
对于丢包问题,我们到滑动窗口再详细解释
总结:
- 32位序号作用就是保证数据按序到达,同时也是作为对端发送报文时填充32位确认序号的根据
- 32位确认序号,也是为了告诉当前已经收到的字节数据有哪些,发送发下一次发送数据时该从哪一字节序列开始发送
- 序号和确认序号是确认应答机制的数据化表示,确认应答机制就是由序号和确认序号来保证的
- 此外,序号还可以判断是否有数据丢包
2.4 6个标记位
- 当报文中的SYN被设置成1时,表面该报文是一个请求建立连接的报文,就是三次握手时发送的报文
- 只要在三次握手阶段才会设置SYN,正常通信时SYN不会被设置
- 报文中的ACK被设置为1时,标明该报文是一个确认应答报文
- 而且除了三次握手的第一次握手没有设置ACK,往后其余的报文都会设置ACK,因为在进行响应时,会把响应和下一次要发送的数据一起发过去,而且我们发回去的数据本身,就对对方发过来的数据具有一定的确认能力
- FIN被设置成1,表示这个报文是请求断开连接的报文,就是四次挥手时发的
- 只有在断开连接阶段才会被设置,正常通信不会设置
- 该标志位表示:提醒接收端应用立刻从TCP缓冲区把数据读走,因为流量控制,导致上层一直把数据不读走,那么缓冲区的数据会变得越来越多,可用空间越来越小
- 操作系统把接收到的数据放到缓冲区里,然后用户去读取,这也是一个生产者消费者模型,所以所谓的流量控制就是对发送过程的一个同步的过程,当对方缓冲区写满的时候,我写进程就阻塞了,然后我就要等,但是要等多久我不知道,对方缓冲区啥时候会有空位我也不知道,就出现了僵持。
- 所以有两种策略:1,发送方定时询问对方接收缓冲区大小 2,一旦接收方缓冲区空间有更新,给对方发一个通知。两种策略同时存在。
- 但是如果接收方就是不把数据拿走,所以我们可以继续给对方发TCP报文,把PSH标记位设置成1,表示提示对方马上把缓冲区的数据读走,你要是不读走,我就认为你当前不想和我通信了,可能会关闭连接等其它操作
- 由于三次握手的每次握手都有时间差,所以重点不在“握手”而在“三次”上,那么客户端认为链接建立好了,是第三次握手我把ACK发出去就确认了,还是我要确认服务器把消息收到了才确认呢?事实上,最后一个ACK是没有应答的,所以客户端只要把第三次报文发出去了就认为链接建立好了,所以三次握手其实是在“赌”,因为第三次ACK可能会丢,前面两个都有应答不怕丢,所以Tcp是允许链接建立失败的
- 当客户端发送第三次ACK成功,但是服务器没有收到ACK,那么客户端和服务器对与链接是否已经建立好了的认知不一致,导致客户端认为三次握手已经完成直接开始传输,但是服务器还停留在第二次握手上,此时服务器直接收到了来自客户端的数据咋办?
- 服务器就会猜测以上情况可能发生,于是服务器在下一次给客户端应答的报文中,将RST标志位设置为1,告诉客户端刚刚的链接没有建好,就让客户端重新发起三次握手
- 所以RST标志位的作用就是“当链接异常情况下,让双方重新建立链接”,上面说的“第三次握手报文丢失”只是众多链接异常情况下的一种(比如:浏览器连接被重置,服务器压力过大等)
URG表示:紧急标记位,一般在某些特殊情况下才会使用
- 场景:TCP是按序到达的,对方会根据序号排序,但是一些情况下,我们想让一些数据优先处理,也就是“插队”被优先处理,在原始TCP规定下,“插队”情况是不可能存在的,但是我们就是想让一些数据优先处理,那么我们就可以设置URG标记位
- 当没有数据要优先处理时,URG为0,16位紧急指针无效,当URG为1时,16位紧急指针就有效,紧急指针表示这个报文中要优先处理的数据在有效载荷中的偏移量
- 但是紧急数据多大呢?在TCP协议中紧急数据默认只允许携带一个字节
- 什么样的情况下我们才会用这个URG呢?一个机房,机房里有一个服务器,服务器上搭载了一个服务,里面有接收和发送缓冲区。有一个客户端连接了,但是用着用着服务器突然不给我响应了,但是没有四次挥手,服务器也没挂,所以客户端很疑惑服务器为什么不给我响应,所以客户端要询问服务器发生了什么
- 所以我们需要让服务器支持“读取紧急数据”,然后我在服务器里面的软件功能添加某些服务状态(计算,IO,同时做等),给每个状态编号;当服务器又卡顿时,客户端就给它发一个紧急数据,所以服务器会优先处理紧急数据,服务器就会把当前状态也用紧急指针发给客户端(紧急数据在应用层上叫做“带外数据”)
- 报头中的“16位紧急指针”代表的就是紧急数据在报文中的偏移量,只能表示数据端的一个位置,所以Tcp的紧急数据只能是一个字节
recv函数的第四个参数flags有一个叫做MSG_OOB的选项可供设置,OOB表示带外数据(out-of-band)的简称,就是紧急数据,就可以在使用recv函数进行读取,并设置MSG_OOB选项:
与之对应的send函数的第四个参数也有MSG_OOB选项,就可以用send函数写入紧急数据:
三,Tcp保证可靠性策略
3.1 确认应答机制(核心)
确认应答机制就是由Tcp报头字段中的32位序号和32位确认序号来保证的
注意:确认应答机制不是保证双方的通信的可靠性,而是通过收到对方应答,来保证一方通信的可靠性,但是如果双方都这样搞,就是间接保证了双方通信的可靠性
确认应答机制前面已经大量涉及了,这里不再赘述了,只要记住,确认应答机制是Tcp保证可靠性的核心,或者说是基础,其余绝大部分的保证可靠性机制都是建立在确认应答机制基础上的
3.2 超时重传机制
上面的问题就是“丢包”,丢包有两种情况:
- 但是无论是什么情况,统一规定为超时或者丢包,都会进行补发;但是如果补发次数多了,会判定链接出问题了,会申请重新建立链接
- 所以服务器可能会收到重复报文,所以服务器需要“去重”,所以报文的序号还有一个重要的作用就是“去重”
- 当发送缓冲区的数据发出去后,操作系统不会立即将数据从缓冲区中删除或覆盖,会暂时保存,在需要时进行超时重传,具体实现可以看后面的“滑动窗口”
3.3 Tcp连接管理机制
3.3.1 操作系统对连接的管理
面向连接是Tcp可靠性的一种,只有在通信建立好连接才会有各种可靠性机制,而一台服务器上可能会存在很多连接,此时操作系统就不得不对这些连接进行管理
- 操作系统在管理这些连接时需要“先描述,再组织”,所以在操作系统中一定有一个描述连接的结构体,里面包含了关于连接的各种属性字段,最后有很多个这样的结构体,就通过数据结构组织起来,这之后操作系统对连接的管理就变成了对该数据结构的增删查改
- 建立连接,在操作系统层面就是定义了一个结构体变量,然后填充各种属性字段,最后将其插入到管理连接的数据结构当中
- 断开连接,也就是将对应的结构体从数据结构中删除,然后释放该连接增加占用的各种资源
- 所以连接的建立管理和释放都是有成本的,就是管理结构体的时间成本,以及存储连接结构体的空间成本
3.3.2 三次握手(重点)
前面我们一直在说三次握手,但都只是简单的概括了,所以下面我们来详细了解下三次握手的过程
双方在进行网络通信前需要先建立连接,这个建立连接的过程我们称之为三次握手:
上面的SYN和ACK,就是Tcp报头字段中的那两个标志位,而对于第二次握手,除了应答,服务器也要主动建立连接,所以服务器也要发个SYN给客户端,因为Tcp协议中,双方主机地位是相等的:“你要和我通信,那么我也要和你通信”,总结:Tcp是全双工的
- 最开始客户端和服务器都处于CLOSED状态,但是服务器为了能够接收客户端发来的连接请求,所以服务器变为了LISTEN监听状态
- 客户端发起三次握手,当客户端发出第一个SYN后,状态变为SYN_SENT
- 处于监听状态的服务器收到SYN后,将连接放进内核等待队列中,并向客户端发起第二次握手,发出SYN+ACK后,服务器状态变成SYN_RCVD
- 当客户端收到服务器的第二次握手,紧接着发送三次握手的最后一个ACK,之后客户端状态变为ESTABLISHED
- 服务器收到最后一个SYN后,状态也变为ESTABLISGED
下面是一系列问题,有点长,但是如果理解了,对三次握手的细节了解更进一步:
3.3.3 四次挥手
由于双方维护连接都是需要成本的,所以Tcp通信结束之后需要断开连接,这个 过程我们称之为四次挥手,其过程如下图,按照时间轴,每一个箭头都是一次挥手:
- 在挥手前,客户端和服务器都处于正常通信的ESTABLISHED状态
- 客户端最先发起断开连接请求,把FIN标志位设置的报文发给了服务器,然后客户端变为FIN_WAIT_1状态
- 服务器收到了FIN,发送ACK应答给客户端,同时状态变为CLOSE_WAIT状态
- 当服务器没有数据再发给客户端时,轮到服务器主动断开连接了,所以服务器主动发送FIN给客户端,之后状态变为LASE_ACK
- 客户端收到了第三次挥手,然后向服务器发送最后一个ACK应答,之后客户端进入TIME_WAIT状态
- 服务器收到最后一个响应报文后,就彻底关闭连接,也就是将该连接的结构体从数据结构中删除,再释放资源,变为CLOSED状态
- 而客户端变为TIME_WAIT状态后,并不会直接关闭连接,而是会等待一个2MSL(Maximum Segment Lifetime,报文最大生存时间),才会进入CLOSED状态
- 客户端主动断开连接,对应的就是客户端先调用close函数;服务器同理,所以一个close对应两次挥手,双方都要close,所以是四次挥手
- 但是要注意的是,服务器只有调用close才会使状态由CLOSE_WAIT变为LASE_ACK,如果服务器忘记调用close,或者由于某些错误导致close调用失败,会导致系统内出现大量处于CLOSE_WAIT状态的连接,会占用服务器资源,是一种内存泄漏问题
一个服务器只能有10个连接,当第11个连接来的时候,服务器终于不堪负重,挂掉了,第11个连接自然请求失败,但是对前面10个连接来说,服务器就变成了主动断开连接的一方,服务器立马就会有10个连接处于TIME_WAIT状态,带来的后果就是服务器无法立即重启,重启时间取决于TIME_WAIT消失的时间,大约为30 -- 60秒,但是我们不允许服务器重启时间这么久,所以我们需要设置套接字属性,让服务器允许我们进行地址复用:
int opt = 1;
setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法立即重启,TIME_WAIT状态时,立即重启,不要再等了
客户端不会出现这样的问题是因为客户端每次启动用的都是随机端口,但是服务器每次启动都必须绑定一个端口号,而且主动断开连接的一方大部分都是客户端
3.4 流量控制
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK端通知发送端
- 窗口大小字段越大, 说明网络的吞吐量越高
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
四,Tcp提高传输效率策略
4.1 滑动窗口
双方主机在Tcp通信时,可以一次性发送多条数据,这样可以将等待多个响应的时间重叠,进而提高效率
滑动窗口背景:
- TCP允许发送方一次性能发送多个报文,然后接收方再一个个应答回去,如果没收到应答就会进行超时重传 --> 已经发出去,暂时没有收到应答的报文,要被TCP暂时保存起来
- 所以发送方就会存在着多个已经发出去但是暂时还没收到应答的报文。那么这些已经发出去但是还没收到应答的报文会被保存到哪里呢?根本不需要保存,因为报文就是在缓冲区里的,所以我们只需要“把缓冲区做一个简单的区域划分”即可。
- 把缓冲区当作数组来看的话,只需要有一个数组的下标就可以标记区域了,所以缓冲区可以分为三部分:已发送已确认,已发送未确认,待发送,三个部分
- 对于已发送已确认的部分,这部分可被覆盖,用简单的话说就是这个部分里面的数据可以从缓冲区移除或者设置成无效
- 对于已发送未确认,理解为:可以发/已经发,但是就是没有收到应答,我们就把这部分区域叫做“滑动窗口”,是发送缓冲区的一部分
滑动窗口描述的就是发送方不用等待ACK,一次能发送的数据最大量
4.2 快速重传
我们先来讲讲,滑动窗口对于下面两种丢包问题做的措施:
- 假设发6个报文,1000,2000,3000,4000,5000,6000假设我收到了1001,2001,4001的应答,3001和5001的没收到,那么滑动窗口应该是往右滑动到2001就停下来的
- 但是,序号的定义是假设我收到了6001的应答,那么前面6000的报文我全部收到了,所以滑动窗口会直接滑到6001的位置
- 如果6001也丢了,那么也没关系,滑倒5001,然后等待5001和6001超时重发即可
- 如果全丢了呢?那就全超时重发
所以发送端连续发送多个报文数据,部分ACK丢包不要紧,可以根据后续的ACK进行确认
- 6个报文,序号是1到6000,接收方只有1001 - 200的报文没收到丢包了,2001后面的的全收到了,那么后面所有的报文返回应答时,确认序号就全填的1001,表示2001的报文没收到
- 此时发送方会受到很多相同序号的应答,所以发送方有个原则,当收到了3个相同序号的应答报文,会立即对丢包的数据进行重发,就把1001到2001重发,后面的不重发了
- 万一5001丢了也没关系,直接针对5001进行重发,这种策略我们叫做 ——“快重传”
- 当发送端补发了1001-2000的数据后,对方发来的确认序号就会变为6001,表示1-6000的数据我全部收到了
4.3 延迟应答
客户端和服务器都有接收和发送缓冲区,双方的发送缓冲区里都有滑动窗口,客户端发消息给服务器,服务器要给客户端应答
- 发送方一次发送更多的数据,代表它发送的效率越高(也就是一次IO往网卡里塞更多数据,效率高)
- 但是发送方一次发送多少数据,取决于对方告诉我它能接收更多数据,
- 如果接收方,给发送方通告一个更大的窗口大小(TCP报头那个),发送方才能发更多数据
我们把这种收到报文不着急应答的策略,叫做:延迟应答,是一种提高效率的方式,但是不是一定提高效率,如果上层一直不取数据,那么效率会降低,所以这种应答提高效率是有概率的。
- 假设接收端缓冲区为1M. 一次收到了500K的数据
- 如果立刻应答, 返回的窗口就是500K
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M
编程推荐建议:以后我们写TCP服务器的时候,建议尽快把通过read,recv尽快把数据全部从内核拿上来。
4.4 捎带应答
- 捎带应答是Tcp通信时最常规的一种方式
- 主机A给主机B发送了一条消息,当主机B收到消息后要发送ACK
- 但是如果此时主机B刚好要发数据,那么这个ACK就可以“搭个顺风车”,此时就把“数据 + ACK”一起发过去了,完成了数据发送,又完成了ACK应答,这叫做捎带应答
- 所以捎带应答不仅能确保发送的数据被对方可靠地收到了,同时也确保了捎带地ACK应答也被对方收到,在保证可靠性地同时又提高了效率
五,拥塞控制
发送数据如果出现问题,可能是一方主机出现问题,也可能是网络出了问题,两种情况:
- 出现少量丢包
- 出现了大量丢包
而网络出现问题:
- 硬件设备出问题
- 网络中数据吞吐量太大引起阻塞。
如果通信双方出现大量数据丢包问题,Tcp就会判断网络出了问题 --> 我们叫做“网络拥塞”。
所以我们现在就要推出一个概念:拥塞窗口;
- 每次开始的时候,定义拥塞窗口大小为1,每次收到ACK应答后,拥塞窗口大小 *= 2,
- 拥塞窗口大小是主机判断网络健康程度的指标,超过拥塞窗口,会引发网络拥塞,否则不会。因为网络是动态的,所以拥塞窗口本身也是动态的
- 所以滑动窗口大小 = min(16位窗口大小 ,有效数据, 拥塞窗口);16位窗口大小是对方主机的接受能力,但是拥塞窗口考虑的是动态的网络的接收能力
- 通过调整拥塞窗口的大小来动态控制滑动窗口的大小,让它的发送数据量按照我们的要求指数级增长,叫做“慢启动”
滑动窗口 + 接收窗口 + 拥塞窗口,三个窗口相互配合,就可以在Tcp传输的时候既考虑接收问题,又考虑网络问题
六,扩展
6.1 面向字节流 VS 面向数据报
- 写数据和读数据互不相关。数据发多少,读多少,没有那种我发多少你就必须收多少的那种匹配机制。UDP就是你发几次我就必须收几次,所以UDP就是面向数据报
- 发送本质就是把数据从我的发送缓冲区“拷贝”到对方的接收缓冲区,但是对方数据被上层取走多少由用户决定,但是发送方发送多少数据由操作系统和TCP协议决定,
- TCP也不管你上层要发送的数据是什么,在我缓冲区里它就是二进制,就是字节数据,TCP的任务就是保证这个数据能成功被对面接收到
- 同时对方的TCP也只认字节数据,分离报头等等工作由用户层自己做 --> TCP只管发送,对发送的数据不做任何处理,全由上层自己做 --> 所以TCP只有字节的概念
- UDP报头里面是有数据长度的,但是TCP没有,因为TCP的序号能保证数据段本身的按序性,TCP也不区分什么报头和载荷,那是上层的事情,TCP只有字节流的概念,你的缓冲区里什么我的缓冲区里就有什么
- 就像家里的自来水管,自来水公司只负责把水送到你家,你怎么用这个水自来水公司不管。字节流也是类似的概念
- 用户对报文进行处理必须一个一个处理,需要将字节流变成一个一个完成的请求,那就是应用层的事了,与Tcp无关了,这样也很好地进行了功能解耦
6.2 数据包粘包问题
我收到的报文有时候并不是一个完整的报文,可能是半个,或者一个半个报文,这时候上层对报文边读边解析,那么上层就可能读到半个报文的情况,这时候再进行处理时,会多处理或少处理请求,叫做粘包问题
Tcp没有粘包问题,它是上层的问题。要解决粘包问题就是定协议(自定义协议的时候搞过)
6.3 Tcp连接异常情况
- 链接其实和进程没有直接关系,它本身和文件是直接相关的,因为获取套接字其实就是获取一个文件描述符,也就是打开了一个文件
- 而我们曾经讲过,文件的生命周期是随进程的,所以链接间接上也是和进程相关的,所以进程退出 --> 关掉文件描述符 --> 链接也进行正常的四次挥手自动断开
- 如果是正常重启,操作系统会在关机前干掉所有进程,而这也是进程退出,所以机器重启和进程终止地情况是一样地
当客户端正常访问服务器时,客户端突然s了,但是服务器在短时间内不知道,所以会维持与客户端地连接,但是不会一直维护,因为Tcp是有保活策略的:
- 服务器会定期检查客户端的在线情况的,如果连续多次没有ACK应答,服务器会自动关闭连接
- 此外,客户端也会定期向服务器“报平安”,所以如果服务器一段时间内没有收到客户端的消息,服务器也会关闭连接
七,Tcp总结
可以看到Tcp比起Udp复杂了不止一点点,因为Tcp既要保证可靠性,又能尽可能提高效率
可靠性保证:
- 检验和
- 序列号
- 确认应答(核心)
- 超时重传
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
Tcp当中还设立了各种定时器:
- 重传定时器:为了控制丢失的报文和丢弃的报文,也就是对报文段确认的等待时间
- 坚持定时器:专门为对方零窗口通知而设立的,也就是向对方发送窗口探测的时间间隔
- 保活定时器:为了检查空闲连接的存在状态,也就是向对方发送探查报文的时间间隔
- TIME_WAIT定时器:双方在四次挥手之后,主动断开连接的一方需要等待的时长
下面是一些基于Tcp的常见的应用层协议:
- HTTP(超文本传输协议)
- HTTPS(安全数据传输协议)
- SSH(安全外壳协议)
- Telnet(远程终端协议)
- FTP(文件传输协议)
- SMTP (电子邮件传输协议)
当然也包括我们自己Tcp程序时定义的应用层协议:
计算机网络(五) —— 自定义协议简单网络程序-CSDN博客