Linux —— 传输层协议TCP

目录

[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的为结束报文段

结论:

  1. 所有的握手都是4次,4次是最能直观的体现出建立共识这一点的,断开连接能建立共识,建立连接也是可以建立共识的。
  2. 四次挥手和四次握手就是建立共识的一种手段,但是在大多数的教材中写的是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就可以了)
相关推荐
祖力551 小时前
Linux应用软件编程:目录IO与framebuffer
linux·运维·算法·framebuffer·目录io
词却1 小时前
Linux基础:常用基本命令与软件包管理
linux
深圳市宝华视联1 小时前
Python 读取 IP 视频流简单 Demo
python·嵌入式硬件·opencv·网络协议·tcp/ip·实时音视频·嵌入式实时数据库
4311媒体网2 小时前
帝国CMS网站自定义搭建技巧
服务器·开发语言·前端
Dachui_11222 小时前
企业内网大模型公网访问方案:Ollama + OpenWebUI + ZeroNews 实战部署记录
运维·服务器·安全·远程工作·内网穿透
2401_890603402 小时前
Linux 进程控制
linux·运维·服务器
啦啦啦啦啦zzzz3 小时前
时间轮定时器
linux·服务器·网络·c++·定时器
DLYSB_3 小时前
让 PLC“开口说话”:基于 Modbus TCP 的声光语音告警设计与 Python 调试
python·网络协议·tcp/ip·报警灯
神威难绷泪3 小时前
Linux应用软件编程:目录IO framebuffer
linux