-
TCP定义
-
TCP 是一个可靠的, 面向连接的, 基于字节流的, 全双工协议
-
面向连接的: 先建立逻辑连接, 再发送数据
-
可靠的: 超时重传, 流量控制, 拥塞控制
-
基于字节流: 没有固定报文边界
-
全双工: 发送接收并行
-
-
TCP/IP分层
-
具体分层
-
应用层: HTTP, FTP, SMTP, DNS, POP3
-
传输层: TCP, UDP; 信息单元为段
-
网络互联层(网络层): IP, ICMP, ARP
-
网络访问层(链路层): IEEE 802.11(无线); 信息单元为帧
-
-
最大传输单元(MTU)与最大段大小(MSS)
-
链路层传输的帧有大小限制, 帧的数据部分的最大长度称为最大传输单元(MTU); IP协议会按照MTU切分数据报, 每个IP数据报头部有个表示"分片偏移"的字段, 用于表示该分段在原始数据报中的位置
-
TCP为避免被IP协议切分, 会主动自己切割, 即最大段大小(MSS) = MTU-IP头大小-TCP头大小, TCP有一个socket选项TCP_MAXSEG可以设置本次链接的MSS
-
以太网数据包负载为1500字节, TCP负载一般为1450字节
-
-
一个TCP连接可由四元组唯一确定: 本地IP地址, 本地端口, 远程IP地址, 远程端口
-
-
TCP头部
-
由"源端口 + 目标端口 + 序列号 + ack确认号 + TCP标记位 + 窗口大小"构成
-
端口号
-
有端口, 没有IP是因为网间传递不归传输层管理. 传输层用端口号区分同一主机上不同程序, 80为HTTP, 443为HTTPS, 22为ssh
-
熟知端口号0-1023, 已登记端口号1024-49151, 临时端口号49152-65535
-
socket的SO_REUSEADDR选项, 允许端口重用
-
-
ack确认号: 1) 告知对方下一个期望接收的序列号 2) 小于此确认号的报文均已接收
-
TCP flags有8位, 这些标记可以组合使用(置为1), 常见的组合有: SYN+ACK, FIN+ACK; SYN: 建立连接, ACK: 接收确认, RST: 强制断开连接, FIN: 断开连接, PSH: 不缓存
-
RST用来异常的关闭连接
-
发送RST时, 不必等缓冲区的数据都发送出去, 而是直接丢弃, 连接进入CLOSED状态
-
接收RST后, 不需要ACK确认, 连接立即进入CLOSED状态
-
RST出现情况: 端口未监听/服务端关闭/服务端奔溃时, 收到客户端连接, 会返回RST(Connection Reset或者Connection refused)
-
Broken pipe与Connection reset by peer错误很常见, 出现前提都是连接已关闭. 其中Broken pipe出现时机是: 在一个 RST的套接字继续写数据, 就会出现Broken pipe
-
-
-
窗口大小, 16位 (最大允许2^16字节, 即64KB)
-
TCP状态
-
三次握手时: closed状态(客户端/服务器), listen状态(服务器), syn_sent状态(客户端), syn_rcvd状态(服务器), established状态(客户端/服务器)
-
四次挥手时: established状态(客户端/服务器), fin_wait1状态(客户端), close_wait状态(服务器), fin_wait2状态(客户端), last_ack状态(服务器), time_wait状态(客户端), closed状态(客户端/服务器)
-
-
-
TCP三次握手
-
三次握手目的是建立连接,并交换初始序列号, 最大段大小, 窗口大小
-
为什么SYN报文不携带数据却要消耗一个序列号?
凡是消耗序列号的TCP报文段, 一定需要对端确认, 如果未收到对端确认, 会不停重传. TCP中不占用序列号的报文, 是不需要确认的, 比如ACK包
-
为什么要三次握手, 而不是两次
防止是客户端先前的请求, 导致服务器建立了连接, 故需要客户端再次确认.
-
time_wait状态过多
-
表现
客户端无法建立新的链接
-
原因
客户端短时间内建立了大量短连接, 连接断开后, TCP正在经历time_wait状态
-
解决
-
短连接变为长连接
-
参数配置, SO_REUSEADDR??
-
-
-
-
TCP四次挥手
-
一般都是客户端主动挥手, 但服务器奔溃, 重启时, 也会主动发起挥手. 重新启动服务器会存在"Address already in use"的情况, 原因是服务器进入了TIME_WAIT状态, 需要保留连接2MSL(最大报文生存时间, 1个MSL大约2分钟). 客户端为什么不存在这种情况, 因为客户端端口一般都是临时的, 每次很难重合
-
为啥要有TIME_WAIT状态(挥手后, 客户端为何要等一段时间再关闭)
-
确保旧连接时的报文在链路中不存在
-
避免最后一个ACK, 被动关闭方没收到, 导致重发FIN时无人回应, 从而保证TCP全双工的连接可靠终止
-
-
为什么TIME_WAIT状态是两个MSL
-
一个MSL确保最后一个ACK能到达对端
-
一个MSL确保对端没收到ACK而重传的FIN能到达
-
-
-
TCP滑动窗口 (发送端到接收端的流量控制)
-
TCP会把要发送的数据放入发送缓冲区, 接收到的数据放入接收缓冲区, 应用程序会不停读取接收缓冲区的内容进行处理
-
为控制发送端的流量速率, 接收端会告知发送端自己的接收窗口, 也就是接收缓冲区中空闲的部分. 发送端滑动窗口机制允许在收到确认前, 在发送窗口的限制下发送多个包
-
接收窗口: 接收端概念, 接收缓冲区中空闲的部分, ACK包里会带着自己接收窗口的大小, 对端会根据该大小调整发送策略
发送窗口: 发送端概念, 一端在某时刻能拥有最大未确认数据包大小, 包括已用窗口+可用窗口, 发送窗口需要小于接收窗口
-
已用窗口: 发送端概念, 已发送但未收到确认的数据包
-
可用窗口: 发送端概念, 未发送但接收端有空间接收的数据包
-
-
发送窗口变为0了怎么办: 零窗口探测包 (就是ACK包, seq为当前连接seq-1), 发送方不断向接收端发送该ACK包
利用零窗口探测机制的攻击, 从服务器下载大文件, 接收一部分后接收窗口置为0, 服务器开始长达十几分钟的零窗口探测机制, 多个连接并发压榨服务器资源
-
-
TCP拥塞处理 (整个网络的流量控制)
-
拥塞窗口与滑动窗口都是控制发送端速率, 滑动窗口保证接收端能力, 拥塞窗口保证网络能力
-
拥塞窗口: 收到对端ACK之前, 自己还能发送的MSS段数
-
拥塞窗口的大小在发送端本地内存中, 不会被交换
-
发送端真正发送窗口的大小(某刻最大未确认数据包大小) = min(发送端自己拥塞窗口的大小, 接收端反馈的接收窗口的大小)
-
慢启动: 拥塞窗口初始值很小, 收到一个ACK后, 窗口值增加1, 经过一个RTT后, 窗口值变为原来的两倍.
-
RTT (往返时延), 发送端发送数据, 到发送端收到对端确认(对端立即确认)所经历的时间
-
拥塞窗口初始值为系统中的变量initcwnd, 为10, 大约16KB. (90%的HTTP请求为16KB, 即10个MSS. 因此慢启动对于大文件传输会有时延)
-
-
快速重传/选择确认
-
传统的重传需要等几百毫秒, 为了减少等待, 引入了"快速重传"与"选择确认"机制
-
快速重传: 收到一个未按序到达的数据段时, 接收端回复上次的ACK, 当发送端收到3个重复ACK时, 立即重传, 而不必等到重传超时器超时再重传. (上次的ACK即等于最大的连续包号)
ack表示这之前的包已经收到了, 四个包: (1: 1001), (1001: 2001), (2001, 3001), (3001, 4001); 第二个未收到, 即使第三/四个收到了, 服务端也只能回复1001, 表示seq为1001之前的数据包已经都收到了, 当服务器收到客户端重传的第二个包后, 立即回复ACK为4001, 表示seq为4001之前的数据包均已收到.
-
选择确认(SACK): 如何让发送端知道需要重传哪个包. ACK 1001, SACK=1:1001 2001:5001, 表示接收端收到最大连续包号为1001, 额外[1:1001] [2001:5001]的包也已收到. 发送端明白只要重传[1001:2000]即可
-
-
快速恢复, 发送端收到三次重复ACK时, 进入快速恢复, 即网络轻度拥塞. 拥塞阈值降为拥塞窗口的一半, 拥塞窗口设置为拥塞阈值
-
-
TCP延迟确认
-
收到数据包后暂时没有数据给对端, 可以等一段时间再确认(Linux上是40ms). 有数据要给对端, ACK一并发出. 再次收到对端数据, 则合并ACK. 没有数据要发送但已过一段时间, 也发送ACK, 避免对端认为丢包.
-
除了收到乱序包立即ACK, 其余均延迟确认
-
-
TCP半关闭/半连接/半打开
-
TCP半关闭
向对端发送FIN, 对端也回应了ACK, 但对端没有回应FIN给我, 此时我就处于半关闭状态, 可以接收数据, 但不能发送数据
-
TCP半连接
服务器发送SYN+ACK, 客户端还未回应ACK, 服务器处于半连接
-
TCP半打开
-
已建立连接, 但一方突然断开(断电, 断网), 另一方仍会维持连接的建立. 如果一直没有通信, 该问题会无法察觉
-
解决方法是TCP提供了keepalive机制, 该机制在2小时没有数据包交互后会发送keepalive探测包, 但这个等待时间太长, 遂一般应用都未开启, 而是自己实现心跳机制.
-
keep-alive与keepalive区别: HTTP的keep-alive是保持长连接, 即多个HTTP可依次使用一个TCP, TCP的keepalive用于心跳检测
-
-
-
IP头部
-
IP报文头中有一个存活时间字段TTL, 表示一个IP报文最大可经过路由数, 每经过一个路由器, TTL减1, 当TTL减到0, 该IP报文会被丢弃
-
源IP地址, 目标IP地址
-
偏移量: IP将根据各分片的偏移量重组包
-
协议类型: TCP/UDP
-
混淆概念: MSL(TCP报文最大生存时间), Linux中一般为30秒, MSL比TTL的时间还会大些
-
-
MAC头部
-
源MAC地址, 目标MAC地址. MAC地址用处就是就是将包送达下个路由器
-
以太类型: IP, ARP
-
-
MAC
-
ARP协议: 广播这个IP是谁的, 目标设备会返回MAC地址
-
将包发送到接收方的整体过程是由IP控制的(由路由器操控), 而将包转发到下一个路由器的过程是由以太网控制的(由交换机操控). 整个通讯过程中数据包的IP头部不会变, 而MAC头部会变
-
路由器相当于是网卡, 有IP地址和MAC地址, 交换机则没有IP地址和MAC地址. 路由器只接受数据包MAC头部中接收方MAC地址为自己的包, 交换机会接收所有的包
-
路由器内部有路由表, 路由表有目标地址, 网关. 路由器收到包后, 检查该包MAC头部中接收方MAC地址是否为自己, 是自己则丢弃MAC头部, 并在路由表中根据该包的接收方IP地址按序检查表中目标地址, 从而找到网关, 如果找到的网关为空, 则该包已到达最终设备, 否则该网关就是下一个路由器的IP地址, 之后通过ARP协议获得网关对应的MAC地址, 封装为新的MAC头部
-
交换机内部有MAC地址与网线端口映射表, 收到包后, 将MAC头部中发送方MAC地址与接收端口更新到映射表中, 之后通过查表, 找到目标MAC地址相应的端口转发出去
-