HTTP三次握手四次挥手面试题

HTTP三次握手四次挥手面试题

学习资源 小林coding 2022.4.6 哇 好多 QAQ

TCP基本认识

TCP格式

TCP 头格式

  • 源端口号 目的端口号 各2字节 确定发送的起始和目标结束位置

  • 序列号 建立连接时间由计算机生成的随机数作为其初始值 通过SYN包传给接收端的主机

    每发送一次数据 就累加一次该数据字节数的大小 解决网络包乱序的问题

  • 确认应答号 指下一次期望收到的数据的序列号 发送端收到这个确认应答之后可以认为在这个序号之前的数据都已经被正常接受 用来解决丢包的问题

  • 控制位

    • ACK: 位为1时 确认应答的字段变的有效 TCP规定除了最开始建立连接时的SYN包之外该位必须为1
    • RST: 位为1时 表示TCP连接中出现异常必须强制断开连接
    • SYN: 位为1时 希望表示建立连接 并在其序列号的字段中进行序列号初始化值的设置
    • FIN:该位为1时 表示今后不会再有数据发送 希望断开连接 通信结束希望断开连接时 通信双方的主机之间就可以相互交换FIN位为1的TCP段

为什么需要TCP协议 TCP工作在哪一层?

IP层是不可靠的 不保证网络包的交付 不保证网络包的按序交付 也不保证数据的完整性

OSI参考模型(七层)

  • 应用层
  • 表示层
  • 会话层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

TCP/IP网络模型(四层)

  • 应用层
  • 传输层
  • 网络层
  • 网络接口层

如果要保障网络数据包的可靠性 那么就需要由上层(传输层)的TCP协议来负责

TCP是一个工作在传输层的可靠的数据传输的服务 确保接收端接受的网络包是无损坏 无间隔 非冗余 按序的

什么是TCP

TCP是面向连接的可靠的 基于字节流的传输层通信协议

  • 面向连接 一对一 不能像UDP协议 一个主机同时向多个主机发送消息
  • 可靠的 无论网络链路中出现了怎样的链路变化 TCP都可以保证一个报文一定能到达接收端
  • 字节流 消息是没有边界的 无论消息多大都可以传输 而且消息是有序的 当前一个消息没有收到的时候 即使先收到了后面的字节 也不能扔给应用层去处理 同时对重复的报文会自动丢弃

什么是TCP连接

用于保证可靠性和流量控制维护的某些状态信息 这些信息的组合 包括Socket 序列号 窗口大小 称为连接

  • Socket IP地址 和 端口号组成
  • 序列号 解决乱序问题
  • 窗口大小 流量控制

如何唯一确定一个TCP连接

TCP四元组

  • 源地址 IP头 主机
  • 源端口 TCP头 进程
  • 目的地址 IP头
  • 目的端口 TCP头

由一个IP的服务器监听了一个端口 他的TCP最大连接数是多少

服务器通常固定在某个本地端口上监听 等待客户端的连接请求

因此 客户端IP和端口都是可变的

最大TCP连接数 = 客户端的IP数目 * 客户端的端口数

对于IPV4 IP数最大 232 客户端端口数最多为 216 最大连接数 2 48

服务器最大并发限制

  • 文件描述符限制 每个TCP连接都是一个文件 如果文件描述符被占满了 too many open files
    • 系统级 当前系统可打开的最大数量 cat /proc/sys/fs/file-max
    • 用户级 指定用户可打开的最大数量 cat /etc/security/limits.conf
    • 进程级 单个进程可以打开的最大数量 cat /proc/sys/fs/nr_open
  • 内存限制 OOM

UDP和TCP的区别 应用场景

HTTP三次握手四次挥手面试题_第1张图片

  • 目标/源端口 告诉发送给哪个进程
  • 包长度 保存了UDP首部的长度跟数据的长度之和
  • 校验和 校验和为提高可靠的UDP首部和数据而设计 防止收到在网络传输中受损的UDP包

TCP和UDP区别

  1. 连接
    • TCP是面向连接的传输层协议 传输数据之前要先建立连接
    • UDP是不需要连接 即刻传输书数据
  2. 服务对象
    • TCP 面向连接 一对一
    • UDP 一对一 一对多 多对多
  3. 可靠性
    • TCP可靠交付数 无差错 不丢失 不重复 按需到达
    • UDP尽最大努力交付 不保证可靠交付数据
  4. 拥塞控制 流量控制
    • TCP由拥塞控制和流量控制机制 保证数据传输的安全性
    • UDP没有 即使网络非常拥堵 不会影响UDP的发送速率
  5. 首部开销
    • TCP首部长 不使用选项 20字节
    • UDP首部 8字节 固定
  6. 传输方式
    • TCP流式传输 没有边界 保证顺序和可靠
    • UDP是一个包一个包的发送 有边界 可能会丢包乱序
  7. 分片不同
    • TCP如果大于MSS 在传输层分片 目标主机收到后 在传输层组装TCP数据包 中途丢失一个分片只需要传输丢失的分片
    • UDP的数据大小如果大于MTU大小 则会在IP层分片 目标主机收到后 在IP层组装完数据 接着传给传输层

TCP UDP应用场景

TCP面向连接 可靠

  • FTP文件传输
  • HTTP/HTTPS

UDP无连接 随时发送 简单高效

  • 包总量较小的通信 DNS SNMP
  • 视频 音频 多媒体通信
  • 广播通信

为什么UDP头部没有首部长度字段 TCP有

TCP有可变长的选项字段 UDP头部长度是不会变化的 无需多一个字段去记录UDP的首部长度

为什么UDP头部有包长度字段 TCP没有

TCP计算负载数据长度

TCP数据的长度 = IP总长度 - IP首部长度 -TCP首部长度

为了网络设备硬件设计和处理方便 首部长度需要是4字节的整数倍

TCP连接建立

TCP三次握手和状态变迁 又来辣!

[外链图片转存中…(img-uEAhAikm-1649216290876)]

  • 一开始 客户端和服务器都处于 CLOSED 状态 显示服务器主动监听某个端口 处于 LISTEN 状态

  • 客户端会随机初始化序号(client_isn) 将次序号至于TCP首部的序号字段中 同时把SYN标志位置1

    表示SYN报文 接着把第一个SYN报文发送给服务端 表示发送连接 随后客户端进入 SYN-SENT状态

  • 服务端收到客户端的SYN报文之后 先初始化序号 将此序号填入TCP首部的序号中 其次确认应答号为client_isn +1 接着把SYN ACK置一 服务端处于SYN-RCVD状态

  • 客户端收到服务器报文后 向服务器回应最后一个应答报文 ACK置1 确认应答号字段填入server_isn +1,之后客户端处于ESTABLISHED状态

  • 服务器收得到客户端的应答报文后 也进入ESTABLISHED状态

第三次握手时可以携带数据的 前两次握手不可以携带数据

查看TCP状态 linux

netstat -napt

为什么是三次握手 不是两次、四次

TCP连接:用于保证可靠性 和 流量控制维护的某些状态信息 这些信息的组合 称为连接

  1. 避免历史连接

    防止旧的重复连接初始化造成混乱

    三次握手避免历史连接

    • 旧SYN报文比新SYN报文早到达了服务端
    • 服务端会回一个SYN+ACK报文给客户端
    • 客户端收到后根据自身的上下文 判断这是一个历史连接 客户端发送RST报文终止连接

    两次握手情况下 被动发起方没有中间状态给主动发起方阻止历史连接 导致被动发起方可能建立一个历史连接 造成资源浪费

  2. 同步双方初始序列号

    • 接收方可以去除重复的数据
    • 可以根据序列号按序接受
    • 可以标识发送出的数据包中 哪些已经被对方收到
    • 一来一回 才能保证双方的初试序列号可以被可靠的同步
    • 四次握手也可以 将第二次握手拆开 只不过消耗资源更大
  3. 避免资源浪费

    • 在SYN网络阻塞后 会重复发送多次SYN报文 服务器收到请求后会建立多个冗余的无效链接 造成不必要的资源浪费

为什么每次建立TCP链接时 初始化的序列号都要求的不一样呢

  • 防止历史报文被下一个相同四元组的链接接受
  • 为了安全性 防止黑客伪造的相同序列号的TCP报文被对方接受

初始化序列号不一样 很大程度上避免了历史报文被下一个相同四元组的链接接受

初始序列号ISN如何随机产生的?

起始ISN基于时钟 每4微秒+1 转一圈4.55小时

RFC793提到初始化序列号随机生成算法

ISN = M+F(localhost localport remotehost remoteport)

  • M 是一个计时器 每四微秒+1
  • F 是 hash算法 md5

既然IP层会分片 为什么TCP还需要MSS

HTTP三次握手四次挥手面试题_第2张图片

  • MTU 一个网络包的最大长度
  • MSS 除去IP和TCP头部 一个网络包所能容纳的TCP数据的最大长度

如果TCP的整个报文交给IP层分片:

IP上又超过MTU大小的数据要发送 那么IP层就得分片 把数据分片成若干片 保证每一片都小于MTU

重新组装 交给上一层TCP传输层

如果一个IP分片丢失 整个IP报文的所有分片都得重传

为达到最佳传输效能 : 在建立连接时候通常要协商双方的MSS值 当TCP层发现数据超过MSS时 先分片

就不会大于MTU 不用IP分片

如果重发:以MSS为单位 不用重传所有的分片 大大增加了重传的效率

第一次握手丢失了 会发生什么?

客户端:SYN SYN_SENT

如果等不到服务端SYN-ACK报文 就会触发超时重传机制 重传SYN报文

超时时间与内核有关系

Linux中 客户端的SYN报文最大重传次数由tcp_syn_retries内核参数控制 默认值为5

每次超时重传的时间时上次的两倍

当第五次超时重传后 会继续等待32秒 服务端如果仍然没有回应 客户端就不再发送SYN包 然后断开TCP连接

总耗时 1+2+4+8+16+32=63

第二次握手丢失了 会发生什么?

服务端收到客户端的第一次握手后 就会回SYN-ACK报文给客户端 第二次握手 此时服务端回进入SYN_RCVD状态

第二次握手的SYN-ACK报文目的:

  • 第二次握手里的ACK 是对第一次握手的确认报文
  • 第二次握手里的SYN是服务端发起建立TCP连接的报文

客户端触发超时重传机制 重传SYN报文

客户端收到后 需要给服务端发送ACK确认额报文

服务端也会触发超时重传机制 重传SYN-ACK

第三次握手丢失了 会发生什么?

客户端收到服务端的SYN-ACK报文后 会给服务端回一个ACK报文 也就是第三次握手

此时客户端状态进入到ESTABLISH状态

服务端会重传SYN-ACK报文 直到收到第三次握手 或者达到最大重传次数

ACK报文时不会有重传的 当ACK丢失了 就由对方重传对应的报文

什么是SYN攻击 如何避免SYN攻击

TCP 连接建立 需要三次握手 假设攻击者短时间伪造不同地址的SYN报文

服务端每接收到一个SYN报文 就进入SYN_RCVD状态 但服务器发送出去的ACK+SYN报文 无法得到未知IP主机的ACK应答

占满服务端的半连接队列 使得服务器不能为正常用户服务

  • 修改Linux内核参数 控制队列大小 队列满处理

  • HTTP三次握手四次挥手面试题_第3张图片

  • 当服务端接受到客户端的SYN报文时 将其加入到内核的SYN队列中

  • 接着发送SYN + ACK给客户端 等待客户端回应ACK报文

  • 服务端接收到ACK报文后 从SYN队列移除到Accept队列

  • 应用调用socket接口 从队列取出连接

应用程序过慢: accept队列占满

SYN攻击: SYN队列满

解决方案

  • tcp_syncookies

    net.ipv4.tcp_syncookies=1
    
    • 当SYN队列满之后 后续收到SYN包 不进入 SYN队列
    • 计算出一个cookie值 以SYN+ACK中的序列号返回给客户端
    • 服务端收到客户端应答报文后 检查ACK合法性 合法就放入accept队列
    • 应用调用socket接口 取出连接

TCP连接断开

TCP的四次握手过程和状态变迁

TCP断开连接方式是通过四次挥手方式

客户端主动关闭连接 —— TCP 四次挥手

  • 客户端打算关闭连接 发送FIN=1的报文 即FIN报文 之后客户端进入FIN_WAIT_1状态
  • 服务端收到报文后 向客户端发送ACK应答报文 接着服务端进入CLOSED_WAIT状态
  • 客户端收到服务端的ACK应答报文后 进入 FIN_WAIT_2
  • 服务端处理完数据之后 向客户端发送FIN报文 服务端进入LAST_ACK状态
  • 客户端收到服务端的FIN报文后 回一个 ACK应答报文 之后进入TIME_WAIT状态
  • 服务器收到了ACK应答报文之后 进入了CLOSED状态 至此服务器已经完成连接的关闭
  • 客户端在经过2MSL一段时间后 自动进入CLOSED状态 至此客户端也完成连接的关闭

四次挥手:每个方向都需要由一个FIN和一个ACK

主动关闭连接的 才有 TIME_WAIT状态

为什么挥手要四次

  • 关闭连接时 客户端向服务端发送FIN时 仅仅代表客户端不再发送数据了 但是还能接受数据
  • 服务器收到客户端的FIN报文时 先回一个ACK应答报文
  • 服务端可能还有数据需要处理 服务端不发送数据 才会发送 FIN报文

第一次挥手丢失?

超时重传 FIN报文 如果次数超过 直接进入CLOSED状态

第二次挥手丢失

ACK报文丢失 不会重传ACK报文 -> 客户端触发超时重传机制 重传FIN报文

直到收到服务端的第二次挥手 或者达到最大的重传次数

在收到第二次挥手之后 客户端处于FIN_WAIT2状态 等服务端发送第三次挥手 服务端的FIN报文

对于close函数关闭的连接 由于无法发送和接受数据 FIN_WAIT2状态不可以持续太久

tcp_fin_timeout控制默认持续时长 60s 超时客户端会直接关闭

如果主动关闭方使用shutdown函数关闭连接且指定只关闭发送方向 接受方向没有关闭

意味着主动关闭方还可以接收数据 如果主动关闭方一直没收到三次挥手 主动关闭方的连接将会一直处于FIN_WAIT2状态 (tcp_fin_timeout无法控制shutdown关闭的连接)

三次挥手丢失?

服务端收到客户端FIN 自动回复ACK

连接处于CLOSE_WAIT状态 等待应用进程调用close函数关闭连接

内核没有权利替代进程关闭连接 必须由进程主动调用close函数来触发服务端发送FIN报文

服务端调用close函数 内核发送FIN报文 连接进入LAST_ACK状态 等待客户端返回ACK来确认连接关闭

如果迟迟收不到ACK服务端重发FIN报文

第四次挥手丢失

客户端收到第三次握手的FIN报文后 就会回复ACK报文 此时客户端连接进入TIME_wAIT状态

LINUX系统中 TIME_WAIT状态会持续2MSL 后才会进入关闭状态

服务端没有收到报文前 还是LAST_ACK状态

ACK没有到达 重发FIN

为什么TIME_WAIT等待的时间是2MSL

MSL 报文最大生存时间 是任何报文在网络上存在的最长时间

超过这段时间会被丢弃

IP头中有TTL字段 是IP数据包可以经过的最大路由数 每经过一个处理他的路由此值就减一

当值为0则数据包被丢弃 同时发送ICMP报文通知源主机

  • MSL是时间
  • TTL是经过路由跳数
  • MSL应该大于等于TTL消耗为0的时间

TTL的值一般是64 Linux将MSL设置成30s

Linux认为数据报文经过64个路由器的时间不会超过30s 如果超过说明报文消失在网络中了

为何等待2MSL 一来一回等待二倍的时间

2MSL时长 至少允许报文丢失一次

2MSL -> 从客户端接受FIN到发送ACK开始计时的

为什么需要TIME_WAIT状态

主动发起关闭连接的一方才有TIME_WAIT状态

  • 防止历史连接数据 被后面向他四元组连接错误接受
    • 序列号,TCP头部字段 32位无符号数 到达4G循环回0
    • 初始序列号 客户端和服务端各自生成的初始序列号 基于时钟
    • 如果没有等待时间或时间过短
      • 上一个连接的报文刚好在客户端新连接的接受窗口内
      • 2MSL足够让两个方向上的数据包都被丢弃 使得原来连接的数据包在网络中都自然消失 再出现的一定是新连接产生的
  • 保证被动关闭连接的一方能被正确的关闭
    • 等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。
      • 如果没有TIME_WAIT 发送完ACK就关闭
      • 如果丢失重传 会报RST 异常终止
      • 等待2MSL的时间可以处理服务端可能重传的报文

TIME_WAIT过多的危害

  • 内存占用

  • 端口资源占用

    • net.ipv4.ip_local_port_range
      

      端口可以通过参数设置

客户端端口 65536 被占满无法创建新的连接

服务端 一个四元组变送TCP连接 TCP连接过多 占用资源

优化TIME_WAIT

  • 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
  • net.ipv4.tcp_max_tw_buckets
  • 程序中使用 SO_LINGER ,应用强制使用 RST 关闭。
  1. 复用处于TIME_WAIT的socket连接(需要打开TCP时间戳支持)
  2. 一旦超过值(18000)将TIME_WAIT值重置 暴力
  3. 非常危险 不提倡

已经建立连接 客户端抽风了怎么办

保活机制

每隔一段时间 发送一个探测报文 连续没有响应 TCP连接死亡 错误信息通知

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • 保活时间

  • 检测间隔

  • 无响应最多次数

  • 对端程序正常工作 TCP保活时间充重置

  • 崩溃重启 RST报文

  • 对端程序不可达 死亡

时间有点小长 ->心跳机制 (eureka中也有!)

指定超时时间 如果在超时时间没有发送新的请求 回调函数 结束连接

已经建立连接 客户端进程崩溃

服务端FIN报文 四次挥手

Socket编程

HTTP三次握手四次挥手面试题_第4张图片

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket

连接建立 read write函数读写

listen 参数backlog?

Linux内核中两个队列

  • SYN

  • Accept

  • int listen (int socketfd, int backlog)
    
    • 文件描述符
    • backlog

    现在通常认为backlog是accept队列 队列长度 = min(backlog, somaxconn)。

accept 发生在三次握手的哪一步?

[外链图片转存中…(img-w4JI1WOj-1649216290877)]

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYN_SENT 状态;
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYN_RCVD 状态;
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。

客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

HTTP三次握手四次挥手面试题_第5张图片

  • 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
  • 接着,当处理完数据后,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得服务端发出一个 FIN 包,之后处于 LAST_ACK 状态;
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
  • 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;

你可能感兴趣的:(计算机网络,网络协议)