目录
[1. TCP协议](#1. TCP协议)
[2. 验证TCP协议格式](#2. 验证TCP协议格式)
[2.1 如何分离报头和有效载荷??------ 通过报文len - 整个报头长度](#2.1 如何分离报头和有效载荷??—— 通过报文len - 整个报头长度)
[2.2 如何分用?? ------ 通过16位目的端口!!](#2.2 如何分用?? —— 通过16位目的端口!!)
[3. TCP报头为主,报头 + 理论](#3. TCP报头为主,报头 + 理论)
[3.1 16位窗口大小 ---- 流量控制:](#3.1 16位窗口大小 ---- 流量控制:)
[3.2 6个标志位 ---- 实际上是8个标志位,但是6个标志位足够了](#3.2 6个标志位 ---- 实际上是8个标志位,但是6个标志位足够了)
[3.2.1 什么是标志位?](#3.2.1 什么是标志位?)
[3.2.2 为什么要有标志位?](#3.2.2 为什么要有标志位?)
[编辑3.2.3 这些标志为各自是什么?](#编辑3.2.3 这些标志为各自是什么?)
[3.2.3.1 ACK](#3.2.3.1 ACK)
[3.2.3.2 SYN](#3.2.3.2 SYN)
[3.2.3.3 FIN](#3.2.3.3 FIN)
[3.2.3.4 PSH](#3.2.3.4 PSH)
[3.2.3.5 RST](#3.2.3.5 RST)
[3.2.3.6 URG(不常用)](#3.2.3.6 URG(不常用))
[4. 确认应答(ACK)机制](#4. 确认应答(ACK)机制)
[5. 超时重传机制](#5. 超时重传机制)
[6. 连接管理机制](#6. 连接管理机制)
[7. 流量控制](#7. 流量控制)
[8. 滑动窗口](#8. 滑动窗口)
[9. 拥塞控制](#9. 拥塞控制)
[9.1 拥塞控制的基本概念](#9.1 拥塞控制的基本概念)
[9.2 拥塞之后,如何控制?](#9.2 拥塞之后,如何控制?)
[9.3 拥塞控制算法](#9.3 拥塞控制算法)
[10. 延时应答](#10. 延时应答)
[11. 捎带应答](#11. 捎带应答)
[12. 面向字节流](#12. 面向字节流)
[13. 粘包问题](#13. 粘包问题)
[14. TCP异常情况](#14. TCP异常情况)
[15. TCP小结](#15. TCP小结)
[16. 基于TCP应用层协议](#16. 基于TCP应用层协议)
[17. TCP/UDP对比](#17. TCP/UDP对比)
1. TCP协议
TCP 全称为 "传输控制协议( Transmission Control Protocol "). ⼈如其名, 要对数据的传输
进⾏⼀个详细的控制;

2. 验证TCP协议格式

2.1 如何分离报头和有效载荷??------ 通过报文len - 整个报头长度
固定长度的报头20字节中的4位首部长度!!!
4位首部长度不是标准的20字节,有可能是整个报头+选项后的长度。
0000,1111 -> 0,15 -> 对应的单位是:4字节 -> 0,60 字节 -> 报头的长度是 20, 60
所以tcp选项最多是:60 - 20 = 40;
如果是标准的报头的话: x * 4 = 20 -> x = 5 -> 0101
2.2 如何分用?? ------ 通过16位目的端口!!
问题:TCP报文中,怎么没有报文的总长度?或者是有效载荷的长度???
TCP不需要!!!不关心收到的报文是否是完整的,TCP是面向字节流的,缓冲区中合起来的就是字节流数据。报文交给缓冲区或者是上层,报文的解析工作由你自己来做。
3. TCP报头为主,报头 + 理论
再谈序号之前,需要一个储备知识:


TCP常规的通信模式1

以此来得出几个细节:

所以确认应答的机制,被应答的报文,就能保证100%可靠!!

再回到下面的图中:

客户端向服务端发送4个报文,但是客户端只收到了3个应答,客户端是怎么知道收到的是哪几个报文的应答呢??如何得知哪个报文是丢失的??--- 报文是需要序号的!!应答中是要有确认序号的!!
如果,报文200 和 报文300 丢失,报文100 和 报文400收到,服务端应答的是多少呢??应答的是 101 !!!:意味着服务端只收到了101之前的报文,下次再发送就从101开始发送。收到报文100应答101,收到报文400,应答的也是101。

问题1. 来回互相发送的是什么??

问题2. 为什么在TCP报头中要有两个序号??面试题

因为捎带应答的存在,任何一个报文既可能是确认也可能是一个要给对方发消息的报文,所以序号和确认序号同时存在!!!
3.1 16位窗口大小 ---- 流量控制:
问题:如果对方接受能力为0??
发送方不发数据,生产满了,生产者就会阻塞,不会阻塞OS,只是逻辑上的,不发了,但是又是如何知道对方的剩余空间为0呢??这个问题后面会回答!!!
3.2 6个标志位 ---- 实际上是8个标志位,但是6个标志位足够了
3.2.1 什么是标志位?
就是结构位段中的比特位!! 要么为 0(该标志位无效) 要么为 1(该标志位有效) ---- 把标志位具象化!
3.2.2 为什么要有标志位?
3.2.3 这些标志为各自是什么?
标志位是TCP内部用来做管理的,应用层基本碰不到的
3.2.3.1 ACK
ACK:确认号

3.2.3.2 SYN
SYN:请求建立连接的报文,我们把SYN标识置为1的称为**同步报文段,**主要负责建立连接的。


3.2.3.3 FIN
FIN:通知对方,本端要关闭了,我们称将FIN标识置为1的为结束报文段


结论:
- 所有的握手都是4次,4次是最能直观的体现出建立共识这一点的,断开连接能建立共识,建立连接也是可以建立共识的。
- 四次挥手和四次握手就是建立共识的一种手段,但是在大多数的教材中写的是3次握手,因为服务器端是一个"舔狗"(服务器无条件的接受客服端的请求),通常将第二第三次捎带应答了。
3.2.3.4 PSH

3.2.3.5 RST
TCP建立连接其实赌的就是最后一次的ACK是否被对方收到

此时,client收到携带有RST为1的报文,就会意识到原来连接并没有建立成功,双方建立共识没有建立好,客户端释放连接,重新发起三次握手,这个就是连接重置的过程!!!
3.2.3.6 URG(不常用)

URG不常用的原因是:建立两条连接,一条TCP连接用来发数据,一条TCP链接用来传输控制指令!相当于有两个缓冲区,一个保存命令,一个保存数据,多线程处理。是有取代方案的,所以不常用!!
4. 确认应答(ACK)机制
将TCP的发送缓冲区和接受缓冲区想象成为线性结构,将应用层的数据文本还是图片拷贝到发送缓冲区中,此时每一个字节天然就有序号了,数据(1~1000)本质上是会将发送缓冲区的数组空间指定的一段范围内的数据打成sk_buff,形成报文给对方推过去,以此类推。之前说报文要带序号,序号会直接or间接转换成数组的下标。

实际上的TCP发送和接受缓冲区在物理上存储空间并不是连续的空间:

5. 超时重传机制

超时的时间如何确定?
- 最理想的情况下,找到一个最小的时间,保证"确认应答一定能在这个时间内返回"。
- 但是这个时间的长短,随着网络环境的不同,是有差异的。
- 如果超时时间设的太长,会影响整体的重传效率。
- 如果超时时间设的太短,有可能会频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
- Linux中(BSD Unix 和 Windows 也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
- 如果重发一次之后,仍然得不到应答,等待2*500ms 后再进行重传
- 如果仍然得不到应答,等待 4*500ms 进行重传依此类推,以指数的形式递增
- 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。

6. 连接管理机制
服务端不调用accept,仅仅是listen状态,客服端能连接listen状态的服务器吗?

将服务器全部的方法注释掉,不调用accept等等方法




两条连接是因为服务器和客户端在同一台机器上,如果是在两台机器上看到的就是一条连接

关键是客服端47664到服务器8080连接已经建立成功,在没有调用accept的情况下,所以得出结论:accept不参与三次握手,只获取已经建立好的连接!!
conect发起三次握手,accept不参与三次握手,三次握手是由双方OS自动完成的。
三次握手建立成功之后,接着write将数据拷贝到TCP的发送缓冲区中,数据多久发?一次发多少完全由内核自己完成。
三次握手时,客服端发完SYN时客服端的状态叫做SYN_SENT(同步发送),服务器收到SYN并且给对方返回一个SYN+ACK,此时服务端的状态是SYN_RCVD(同步收到)的状态,此时客服端收到服务端的SYN+ACK并且发出ACK此时的状态被称为ESTABLISHED(连接建立成功),最后服务端收到ACK后的状态也是ESTABLISHED。

问题:三次握手,为什么是三次??(经典面试题)
- 验证全双工!
- 建立双方要通信的共识,双方都知道,我要和对方进行通信了

问题:四次挥手!!!

一般情况下,客服端将数据发完了,不会意味着服务端也发完了数据,这就是服务端一般不会捎带应答的原因,也就是说client将自己的fd关闭后,服务端也是可以给client发送消息的。例如:HTTP

所以在第二次和第三次挥手之间,可能会存在server会一直向client一直发消息的情况。close()是触发四次挥手的函数,调用一次close()关闭一对FIN和ACK,双方都是需要调用close()函数的。
那么,此时一端调用了close(),对端并没有调用close(),此时对端会给我发送数据,那么关掉的一端是如何进行读取对端发来的数据呢??在Linux系统当中会存在对应的系统调用:shutdown()

close() 和 shutdown() 都是两次握手,两者的区别是:close()将读写都关闭,文件描述符整体释掉;shutdown()继续两次握手,但是会将文件信息保留下来,让你通过文件描述符能够继续读。
四次挥手:
主动断开连接的一方,client发送出FIN的时候状态是FIN_WAIT_1,server接受到FIN并发送出ACK时,状态为CLOSE_WAIT,如果服务端一直不调用close的话,没办法发FIN,服务端就会一直处在CLOSE_WAIT的状态。

获得一个新连接,accept()之后,但是对这个新连接不做任何的处理,不读不写也不关闭,由客户端来进行关闭,此时的服务器的状态为CLOSE_WAIT:

客服端一退出,主动触发两次握手:


如果服务端存在大量的CLOSE_WAIT的状态,意味着有fd未关闭,fd对应的就是一条连接,client越来越多,一直不关闭fd的话就会导致服务端有大量的CLOSE_WAIT,反向的当看见服务器卡顿,看见大量的CLOSE_WAIT,就会意识到服务器端有bug,有文件描述符泄露的问题。
用自己的电脑的浏览器访问8080端口,关闭,重连,关闭,重连:

就会出现很多的CLOSE_WAIT的状态:

服务端调用close()之后,服务器就会成为LAST_ACK的状态,客户端收到FIN之后并发送ACK此时客户端已经完成4次挥手了,但是此时状态为TIME_WAIT。


如何验证?
服务器直接关掉:


服务器直接关闭之后,一直循环立即重启,就会发现一直都是绑定失败:

绑定失败的原因是:主动断开连接的一方其实他的连接并没有被释放,因为还能被查到,源端口和目的端口还被占用着。
作为服务器端是不能随便改动端口号的,过一段时间就可以重启了,因为等待了2MSL的时间。



服务器重启之后,有一个time_wait状态还是存在的,还有一个是listen状态的,服务端是无视了time_wait状态,自己是listen状态,启动的是那个listen的。,时间一到,系统自动将time_wait状态的那个释放掉,

所以:服务端写的时候,创建套接字,地址复用这个功能必须设置上
查看一般系统的time_wait 的时间是多长的命令:cat /proc/sys/net/ipv4/tcp_fin_timeout

7. 流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继⽽引起丢包重传等等⼀系列连锁反应.
因此TCP⽀持根据接收端的处理能⼒, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
- 接收端将⾃⼰可以接收的缓冲区剩余空间大小放⼊ TCP ⾸部中的 "窗口大小" 字段, 通过ACK端通知发送端;
- 窗⼝⼤⼩字段越⼤, 说明网络的吞吐量越⾼;
- 接收端⼀旦发现⾃⼰的缓冲区快满了, 就会将窗⼝⼤⼩设置成⼀个更⼩的值通知给发送端;
- 发送端接受到这个窗⼝之后, 就会减慢⾃⼰的发送速度;
- 如果接收端缓冲区满了, 就会将窗⼝置为0; 这时发送⽅不再发送数据, 但是需要定期发送⼀个窗⼝探测数据段, 使接收端把窗⼝⼤⼩告诉发送端.

接收端如何把窗⼝⼤⼩告诉发送端呢? 回忆我们的TCP⾸部中, 有⼀个16位窗⼝字段, 就是存放了窗⼝⼤⼩信息;
那么问题来了, 16位数字最⼤表⽰65535, 那么TCP窗⼝最⼤就是65535字节么?
实际上, TCP⾸部40字节选项中还包含了⼀个窗⼝扩⼤因⼦M, 实际窗⼝⼤⼩是 窗⼝字段的值左移 M 位;

所以:真实情况下的缓冲区大小也是不确定的!!!
8. 滑动窗口
刚才的确认应答策略, 对每⼀个发送的数据段, 都要给⼀个ACK确认应答. 收到ACK后再发送下⼀个数据段. 这样做有⼀个⽐较⼤的缺点, 就是性能较差. 尤其是数据往返的时间较⻓的时候。

既然这样⼀发⼀收的⽅式性能较低, 那么我们⼀次发送多条数据, 就可以⼤ 的提⾼性能(其实是将多个段的等待时间重叠在⼀起了)。

- 窗口大小指的是⽆需等待确认应答⽽可以继续发送数据的最大值。 上图的窗⼝大小就是4000个字节(四个段)。
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
- 收到第⼀个ACK后, 滑动窗⼝向后移动, 继续发送第五个段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗⼝, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉。
- 窗⼝越⼤, 则⽹络的吞吐率就越⾼;







9. 拥塞控制

上述的图中,明显就是网络的问题才会导致丢包率那么高!!!以此引出 拥塞控制 的概念!!!

9.1 拥塞控制的基本概念
虽然TCP有了滑动窗⼝这个⼤杀器, 能够⾼效可靠的发送⼤量的数据. 但是如果在刚开始阶段就发送⼤量的数据, 仍然可能引发问题.
因为⽹络上有很多的计算机, 可能当前的⽹络状态就已经⽐较拥堵。 在不清楚当前⽹络状态下, 贸然发送⼤量的数据, 是很有可能引起雪上加霜的。此时就要做 拥塞控制!!!

9.2 拥塞之后,如何控制?
TCP引⼊ 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的⽹络拥堵状态, 再决定按照多⼤的速度传输数据。此时就要引入 拥塞窗口。
**拥塞窗口的定义:拥塞窗口在TCP/IP协议栈中只是一个整型变量,是用来动态衡量网络的动态程度的!!!**如果拥塞窗口为10,如果是11、12的话,就可能会引起拥塞,如果是小于10,是8、9的话就不会发生网络拥塞。

- 发送开始的时候, 定义拥塞窗⼝大小为1;
- 每次收到⼀个ACK应答, 拥塞窗⼝加1;
- 每次发送数据包的时候, 将拥塞窗⼝和接收端主机反馈的窗⼝⼤⼩做⽐较, 取较⼩的值作为实际发送的窗⼝;
9.3 拥塞控制算法
前期是指数级增长。

像上⾯这样的拥塞窗⼝增⻓速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增⻓速度⾮常快.
- 为了不增⻓的那么快, 因此不能使拥塞窗⼝单纯的加倍.
- 此处引⼊⼀个叫做慢启动的阈值
- 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长


- 当TCP开始启动的时候, 慢启动阈值等于拥塞窗口最大值;
- 在每次超时重发的时候, 慢启动阈值会变成原来的⼀半, 同时拥塞窗口置回1;
少量的丢包, 我们仅仅是触发超时重传; ⼤量的丢包, 我们就认为⽹络拥塞;一般丢包1% ~1.5%正常,3% 属于拥堵,5% 非常拥堵。
当TCP通信开始后, ⽹络吞吐量会逐渐上升; 随着⽹络发⽣拥堵, 吞吐量会⽴刻下降;
拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对⽅, 但是⼜要避免给⽹络造成太⼤压⼒的折中⽅案.

10. 延时应答
如果接收数据的主机立刻返回ACK应答, 这时候返回的窗⼝可能⽐较小。
- 假设接收端缓冲区为1M. ⼀次收到了500K的数据; 如果⽴刻应答, 返回的窗⼝就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到⾃⼰的极限, 即使窗⼝再放⼤⼀些, 也能处理过来;
- 如果接收端稍微等⼀会再应答, ⽐如等待200ms再应答, 那么这个时候返回的窗⼝⼤⼩就是1M;
⼀定要记得, 窗⼝越⼤, ⽹络吞吐量就越⼤, 传输效率就越⾼. 我们的⽬标是在保证⽹络不拥塞的情况下尽量提⾼传输效率;
那么所有的包都可以延迟应答么? 肯定也不是;

11. 捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应⽤层也是 "⼀发⼀收" 的. 意味着客户端给服务器说了 "How are you", 服务器也会给客户端回⼀个 "Fine, thank you";
那么这个时候ACK就可以搭顺⻛⻋, 和服务器回应的 "Fine, thank you" ⼀起回给客户端。
捎带应答的细节:
发送消息之前需要先进行三次握手,在前两次的握手是不能携带数据的,只有在第三次握手的时候可以携带数据给对方,第二次的SYN + ACK 就是一种捎带应带(报文级别的)。
捎带应答的本质也是为了提高TCP通信的效率的!!!

12. 面向字节流
创建一个TCP的socket,同时在内核中创建一个 发送缓冲区 和 接受缓冲区
- 调⽤write时, 数据会先写⼊发送缓冲区中;
- 如果发送的字节数太⻓, 会被拆分成多个TCP的数据包发出;
- 如果发送的字节数太短, 就会先在缓冲区⾥等待, 等到缓冲区⻓度差不多了, 或者其他合适的时机发送出去;
- 接收数据的时候, 数据也是从⽹卡驱动程序到达内核的接收缓冲区;
- 然后应⽤程序可以调⽤read从接收缓冲区拿数据;
- 另⼀⽅⾯, TCP的⼀个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这⼀个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双⼯
由于缓冲区的存在, TCP程序的读和写不需要⼀ 匹配, 例如:
- 写100个字节数据时, 可以调⽤⼀次write写100个字节, 也可以调⽤100次write, 每次写⼀个字节;
- 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以⼀次read 100个字节, 也可以⼀次read⼀个字节, 重复100次;

13. 粘包问题
- ⾸先要明确, 粘包问题中的 "包" , 是指的应⽤层的数据包.
- 在TCP的协议头中, 没有如同UDP⼀样的 "报⽂⻓度" 这样的字段, 但是有⼀个序号这样的字段.
- 站在传输层的⻆度, TCP是⼀个⼀个报⽂过来的. 按照序号排好序放在缓冲区中.
- 站在应⽤层的⻆度, 看到的只是⼀串连续的字节数据.
- 那么应⽤程序看到了这么⼀连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是⼀个完整的应⽤层数据包.
那么如何避免粘包问题呢? 归根结底就是⼀句话, 在应用层,明确两个包之间的边界. (传输层是面向字节流的)
- **对于定⻓的包,**保证每次都按固定⼤⼩读取即可; 例如上⾯的Request结构, 是固定⼤⼩的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
- 对于变⻓的包, 可以在包头的位置, 约定⼀个包总⻓度的字段, 从⽽就知道了包的结束位置; ------ 自描述字段
- 对于变⻓的包, 还可以在包和包之间使⽤明确的分隔符(应⽤层协议, 是程序猿⾃⼰来定的, 只要保证分隔符不和正⽂冲突即可);
思考: 对于UDP协议来说, 是否也存在 "粘包问题" 呢? ---- 不存在!!
- 对于UDP, 如果还没有上层交付数据, UDP的报⽂⻓度仍然在. 同时, UDP是⼀个⼀个把数据交付给应⽤层. 就有很明确的数据边界.
- 站在应⽤层的站在应⽤层的⻆度, 使⽤UDP的时候, 要么收到完整的UDP报⽂, 要么不收. 不会出现"半个"的情况
14. TCP异常情况
- 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别。
- 机器重启: 和进程终止的情况相同。重启时,会提示:当前有尚未终止的进程,是否关闭?意味着:重启之前,需要先进行4次挥手,自动杀掉启动进程!杀掉启动进程等同于进程终止。所以当电脑上启动了很多客户端或者是手机上的应用,重启时就会变得很慢,但是如果是当电脑才开机直接重启的话,就会非常迅速。这是因为你的网络客户端正在跟对方建立连接通信着的呢,重启的时候是在做4次挥手断开连接,所以会慢一点点。

- 机器掉电/网线断开: 接收端认为连接还在, ⼀旦接收端有写⼊操作, 接收端发现连接已经不在了, 就会进⾏reset. 即使没有写⼊操作, TCP⾃⼰也内置了⼀个保活定时器, 会定期询问对⽅是否还在. 如果对⽅不在, 也会把连接释放。 客户端网线直接拔了,双方是不可能进行4次挥手的!甚至对端是不知道另一端的网线已经掉了,站在服务端的视角,客户端在很长的时间内就不会向服务端发消息了,服务端的连接不可能一直维持着,所以TCP有一个策略:**TCP保活机制** 。站在TCP协议层,某一个客户端跟我连着,但是长时间不活跃,服务器会主动的只发送TCP报头,不携带数据,在TCP报头中带一个询问的选项,服务器寻问若干次客户端无反应,服务端已经判定客户端掉线了,此时服务器会自动的关掉连接。所以将网线拔了不用担心对端的连接长时间挂着,TCP保活机制会定期的询问对方客户端是否存在,如果存在,继续访问,如果不存在,直接将连接关闭掉了。真实情况下:其实TCP保活机制用处并不大!! 一场王者一般是20min左右,但是TCP保活机制是1小时后才会询问,所以用处不大!!!所以保活机制往往是由应用层来做,也是最合适的!!!那么如何做保活呢??? ----- 给对方发送一个按照对方所对应的协议的报文,只要对方有应答就可以,就是之前写的echoserver,echoserver就是在做保活!!(客户端先挂掉的话,服务器可以对客户端做保活,同样的服务器挂掉的话,客户端可以对服务器做保活)
OS是如何得知网卡上有数据了?? --- 硬件中断。网线拔了,网卡就变化了,是要通过中断告诉OS,OS就会将所有的连接全部释放掉,所以一旦将网线拔了客户端连接就可以被释放了,对端不知道会做心跳保持。
客户端和服务端同时将连接拔掉呢?客户端和服务端同时将连接释放掉就完了。
客户端的网线刚一拔立马再插上,如果客户端给服务端发了一个数据请求,服务器正在处理,在处理期间直接将网线一拔,之后将网线又立马插上,所以客户端没连接,服务器不知道,处理完请求要给客户端应答,此时客户端收到应答十分奇怪,通信之前为什么不先建立连接???所以客户端会给服务器发连接重置!!!服务器RESET,在用户层的表现就是write,但是对端连接已经不存在,服务端所以会收到一个SIGPIPE信号,所以在守护进程的时候为什么要忽略SIGPIPE信号?? ---- 是因为服务器不能因为RESET连接重置导致服务端收到SIGPIPE进而导致进程挂掉,不能让服务器挂掉。
另外, 应⽤层的某些协议, 也有⼀些这样的检测机制. 例如HTTP⻓连接中, 也会定期检测对⽅的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接。
结论:
进程如果崩溃了,进程打开的套接字连接就会被自动关闭,关闭过程和进程退出无关,文件描述符关闭,自动走四次挥手,说明四次挥手的过程也是双方操作系统自动完成的,调用close是触发前两次和后两次握手,不调用,进程自己崩掉了,底层也会自动触发四次挥手。
15. TCP小结
为什么TCP这么复杂? 因为要保证可靠性, 同时⼜尽可能的提⾼性能.
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗⼝
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
16. 基于TCP应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
当然, 也包括自己写TCP程序时⾃定义的应⽤层协议;
17. TCP/UDP对比
我们说了TCP是可靠连接, 那么是不是TCP⼀定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单,绝对的进行比较
- TCP⽤于可靠传输的情况, 应⽤于文件传输, 重要状态更新等场景;(包括转账、支付)
- UDP⽤于对⾼速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输、直播领域等. 另外UDP可以⽤于⼴播;
归根结底, TCP和UDP都是程序员的⼯具, 什么时机用, 具体怎么⽤, 还是要根据具体的需求场景去判定。(建议:1. 自己再用底层协议的时候不知道使用 TCP 还是 UDP??直接无脑TCP。2. 对实时性要求高和可靠性要求并不高,就用UDP)
用UDP实现可靠传输(经典面试题)
参考TCP的可靠性机制, 在应用层实现类似的逻辑;
例如:
- 引⼊序列号, 保证数据顺序;
- 引⼊确认应答, 确保对端收到了数据;
- 引⼊超时重传, 如果隔⼀段时间没有应答, 就重发数据;
- ......(先确认UDP需要哪些可靠性,直接从TCP那,一套就行了,若果是要全部的话,就直接用TCP就可以了)
