学习资源 小林coding 2022.4.6 哇 好多 QAQ
TCP格式
源端口号 目的端口号 各2字节 确定发送的起始和目标结束位置
序列号 建立连接时间由计算机生成的随机数作为其初始值 通过SYN包传给接收端的主机
每发送一次数据 就累加一次该数据字节数的大小 解决网络包乱序的问题
确认应答号 指下一次期望收到的数据的序列号 发送端收到这个确认应答之后可以认为在这个序号之前的数据都已经被正常接受 用来解决丢包的问题
控制位
为什么需要TCP协议 TCP工作在哪一层?
IP层是不可靠的 不保证网络包的交付 不保证网络包的按序交付 也不保证数据的完整性
OSI参考模型(七层)
TCP/IP网络模型(四层)
如果要保障网络数据包的可靠性 那么就需要由上层(传输层)的TCP协议来负责
TCP是一个工作在传输层的可靠的数据传输的服务 确保接收端接受的网络包是无损坏 无间隔 非冗余 按序的
什么是TCP
TCP是面向连接的可靠的 基于字节流的传输层通信协议
什么是TCP连接
用于保证可靠性和流量控制维护的某些状态信息 这些信息的组合 包括Socket 序列号 窗口大小 称为连接
如何唯一确定一个TCP连接
TCP四元组
由一个IP的服务器监听了一个端口 他的TCP最大连接数是多少
服务器通常固定在某个本地端口上监听 等待客户端的连接请求
因此 客户端IP和端口都是可变的
最大TCP连接数 = 客户端的IP数目 * 客户端的端口数
对于IPV4 IP数最大 232 客户端端口数最多为 216 最大连接数 2 48
服务器最大并发限制
UDP和TCP的区别 应用场景
TCP面向连接 可靠
UDP无连接 随时发送 简单高效
为什么UDP头部没有首部长度字段 TCP有
TCP有可变长的选项字段 UDP头部长度是不会变化的 无需多一个字段去记录UDP的首部长度
为什么UDP头部有包长度字段 TCP没有
TCP计算负载数据长度
TCP数据的长度 = IP总长度 - IP首部长度 -TCP首部长度
为了网络设备硬件设计和处理方便 首部长度需要是4字节的整数倍
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连接:用于保证可靠性 和 流量控制维护的某些状态信息 这些信息的组合 称为连接
避免历史连接
防止旧的重复连接初始化造成混乱
两次握手情况下 被动发起方没有中间状态给主动发起方阻止历史连接 导致被动发起方可能建立一个历史连接 造成资源浪费
同步双方初始序列号
避免资源浪费
为什么每次建立TCP链接时 初始化的序列号都要求的不一样呢
初始化序列号不一样 很大程度上避免了历史报文被下一个相同四元组的链接接受
初始序列号ISN如何随机产生的?
起始ISN基于时钟 每4微秒+1 转一圈4.55小时
RFC793提到初始化序列号随机生成算法
ISN = M+F(localhost localport remotehost remoteport)
既然IP层会分片 为什么TCP还需要MSS
如果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报文目的:
客户端触发超时重传机制 重传SYN报文
客户端收到后 需要给服务端发送ACK确认额报文
服务端也会触发超时重传机制 重传SYN-ACK
第三次握手丢失了 会发生什么?
客户端收到服务端的SYN-ACK报文后 会给服务端回一个ACK报文 也就是第三次握手
此时客户端状态进入到ESTABLISH状态
服务端会重传SYN-ACK报文 直到收到第三次握手 或者达到最大重传次数
ACK报文时不会有重传的 当ACK丢失了 就由对方重传对应的报文
什么是SYN攻击 如何避免SYN攻击
TCP 连接建立 需要三次握手 假设攻击者短时间伪造不同地址的SYN报文
服务端每接收到一个SYN报文 就进入SYN_RCVD状态 但服务器发送出去的ACK+SYN报文 无法得到未知IP主机的ACK应答
占满服务端的半连接队列
使得服务器不能为正常用户服务
修改Linux内核参数 控制队列大小 队列满处理
当服务端接受到客户端的SYN报文时 将其加入到内核的SYN队列中
接着发送SYN + ACK给客户端 等待客户端回应ACK报文
服务端接收到ACK报文后 从SYN队列移除到Accept队列
应用调用socket接口 从队列取出连接
应用程序过慢: accept队列占满
SYN攻击: SYN队列满
解决方案
tcp_syncookies
net.ipv4.tcp_syncookies=1
TCP的四次握手过程和状态变迁
TCP断开连接方式是通过四次挥手方式
FIN
报文 之后客户端进入FIN_WAIT_1
状态CLOSED_WAIT
状态FIN_WAIT_2
TIME_WAIT
状态CLOSED
状态 至此服务器已经完成连接的关闭2MSL
一段时间后 自动进入CLOSED
状态 至此客户端也完成连接的关闭四次挥手:每个方向都需要由一个FIN和一个ACK
主动关闭连接的 才有 TIME_WAIT
状态
为什么挥手要四次
第一次挥手丢失?
超时重传 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报文通知源主机
TTL的值一般是64 Linux将MSL设置成30s
Linux认为数据报文经过64个路由器的时间不会超过30s 如果超过说明报文消失在网络中了
为何等待2MSL 一来一回等待二倍的时间
2MSL时长 至少允许报文丢失一次
2MSL -> 从客户端接受FIN到发送ACK开始计时的
为什么需要TIME_WAIT状态
主动发起关闭连接的一方才有TIME_WAIT状态
TIME_WAIT过多的危害
内存占用
端口资源占用
net.ipv4.ip_local_port_range
端口可以通过参数设置
客户端端口 65536 被占满无法创建新的连接
服务端 一个四元组变送TCP连接 TCP连接过多 占用资源
优化TIME_WAIT
已经建立连接 客户端抽风了怎么办
保活机制
:
每隔一段时间 发送一个探测报文 连续没有响应 TCP连接死亡 错误信息通知
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
保活时间
检测间隔
无响应最多次数
对端程序正常工作 TCP保活时间充重置
崩溃重启 RST报文
对端程序不可达 死亡
时间有点小长 ->心跳机制 (eureka中也有!)
指定超时时间 如果在超时时间没有发送新的请求 回调函数 结束连接
已经建立连接 客户端进程崩溃
服务端FIN报文 四次挥手
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是accept队列 队列长度 = min(backlog, somaxconn)。
accept 发生在三次握手的哪一步?
[外链图片转存中…(img-w4JI1WOj-1649216290877)]
connect
调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;accept
阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。
客户端调用 close 了,连接是断开的流程是什么?
close
,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;EOF
到接收缓冲区中,应用程序可以通过 read
调用来感知这个 FIN 包。这个 EOF
会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;EOF
,于是也调用 close
关闭它的套接字,这会使得服务端发出一个 FIN 包,之后处于 LAST_ACK 状态;2MSL
时间之后,也进入 CLOSE 状态;