
UDP 传输层传输层负责数据能够从发送端传输接收端.再谈端口号端口号(Port)标识了⼀个主机上进行通信的不同的应用程序所以TCP/IP协议中,用源IP,源端口号,目的IP,目的端口号,协议号是哪一种协议这样⼀个五元组来标识⼀个 唯一的通信(可以通过netstat-n查看);UDP协议端格式含义字段大小作用源端口号16bit (2 字节)发送方端口目的端口号16bit (2 字节)接收方端口最重要交给上层哪个程序UDP 长度16bit (2 字节)整个 UDP 报文总长度头部 8 字节 数据UDP 校验和16bit (2 字节)校验数据是否损坏可校验分离怎么把 UDP 报文区分开报头和正文UDP 头部有16 位 UDP 长度记录整个 UDP 报文8 字节头部 数据总字节数。读到了长度后只需再减去8bit的报头那剩下的就是正文部分了UDP 为什么叫用户数据报”数据报 (datagram)自带边界、独立完整的一包数据。每个报文独立。用户应用层用户直接发送一份完整数据内核不拆分、不合并。 你 sendto 给多少数据就封装成一个 UDP 数据报发出去。struct udphdr 结构体内核里面描述 UDP 头部的 C 结构体struct udphdr { __u16 source; //源端口 __u16 dest; //目的端口 __u16 len; //udp总长度 __u16 check; //校验和 };注意这个结构体只在本机操作系统内核内存内部使用。 内核内部同一台机器大小端、对齐规则一致直接操作结构体成员没问题。不能直接把这个结构体二进制原样发到网络上跨机器传输必须转成网络大端字节流序列化处理大小端。UDP 的特点UDP 传输的过程类似于寄信。无连接知道对端的 IP 和端口号就直接进行传输不需要建立连接不可靠没有确认机制没有重传机制如果因为网络故障该段无法发到对方UDP 协议层也不会给应用层返回任何错误信息面向数据报不能够灵活的控制读写数据的次数和数量UDP 缓冲区• UDP没有真正意义上的发送缓冲区.调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;• UDP具有接收缓冲区.但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序⼀ 致;如果缓冲区满了,再到达的UDP数据就会被丢弃;怎么理解1.UDP没有真正意义上的发送缓冲区调用sendto()直接把数据拷贝交给内核。内核简单封装 UDP 头部直接交给网络层发出去。 sendto 调用成功只代表数据拷贝到内核不代表对方收到。 没有缓存起来等待重传的区域所以不需要发送缓冲区。 TCP 才有发送缓冲区用来存数据做超时重传就是说传数据的时候如果失败了因为不会删除缓冲区里的数据这样就可以重传了呀。UDP 没有重传机制所以不需要。udp不可靠的原因之一2.不保证顺序网络上 UDP 报文会乱序。缓冲区里报文顺序不一定和发送顺序一样。先发的包可能后到。缓冲区满 → 新报文直接丢弃接收缓冲区存满了再有新 UDP 报文到达内核直接丢掉不会通知应用层。应用程序完全不知道丢包了不可靠UDP 使用注意事项我们注意到UDP 协议首部中有一个 16 位的最大长度。也就是说一个 UDP 能传输的数据最大长度是 64K (包含 UDP 首部).然而 64K 在当今的互联网环境下是一个非常小的数字.如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包多次发送并在接收端手动拼装基于 UDP 的应用层协议NFS: 网络文件系统TFTP: 简单文件传输协议DHCP: 动态主机配置协议BOOTP: 启动协议 (用于无盘设备启动)DNS: 域名解析协议报文的理解在OS内部存在大量的报文要管理这些报文酒就得通过先描述再组织的方式sk_buff 内核缓冲区Linux 网络核心sk_buff是 Linux 内核用来存放一个网络报文的结构体。 网卡收到每一个数据包内核都会生成一个struct sk_buff。所有 IP/UDP/TCP 报文在内核都靠它管理。sk_buff 关键指针图中标出来 head /data/tail /endhead这块内存的起始地址end这块内存的结束地址head‑end 是整个申请出来的内存总区间。data当前协议头部的起始位置可以前后移动。tail当前有效数据的末尾位置。拆包、封包本质移动 data、tail 这两个指针不是拷贝内存拆包向下封装从应用层往下发 一开始data指向应用数据。 封装 UDP 头data指针往前挪 留出位置填写 UDP 头部 封装 IP 头data再往前挪填写 IP 头部 封装以太网头继续往前挪指针。封包过程就与其相反向上收包不断后移data剥掉一层一层头部。向下发包不断前移data留出空间填充各层协议头部。sk_buff 链表OS 内核里面会有很多收到的数据包每个包一个 sk_buff用链表串起来。UDP 接收缓冲区本质就是一条 sk_buff 链表每一个节点存完整一个 UDP 报文。 当你recvfrom取出链表中一个 sk_buff拷贝数据给用户态然后释放这个 sk_buff。如果应用层正在解析报文会不会影响 OS 从网络中读取报文为什么不会。内核接收报文放在内核的 sk_buff 队列内核缓冲区。应用层调用recvfrom/recv只是把内核 sk_buff 里面的数据拷贝一份到用户内存。用户层拿着拷贝出来的数据慢慢解析不影响内核继续接收新报文放入 sk_buff 链表。 内核和应用层内存隔离。TCP协议段格式16 位源端口号16 位目的端口号32 位序号 seq本报文段数据第一个字节的编号可靠传输、重排序靠它。32 位确认号 ack期望收到对方下一个字节序号确认收到。4 位首部长度数据偏移单位4 字节 取值范围5‑155 ×4 20 字节无选项的情况TCP 最小头部 20 字节最大 15 ×4 60 字节头部最大 60 字节。靠这个字段内核知道头部到哪里结束后面哪里开始是应用数据。保留 6 位 6 个标志位URG、ACK、PSH、RST、SYN、FIN(具体含义后面说16 位窗口大小滑动窗口流量控制。告诉对方我方接收缓冲区还能收多少字节。16 位校验和校验头部 数据16 位紧急指针URG 标志生效才有用标记紧急数据位置。选项部分0~40 字节。MSS、窗口扩大因子、时间戳等。补齐到 4 字节对齐。后面应用层数据载荷怎么没有整个 TCP 报文总长度字段UDP 头部有udp‑len记录整个报文长度。TCP 头部没有总长度字段因为TCP 是字节流协议报文边界不重要每一个报文之间边界不明确。报头和有效载荷怎么分离靠4 位首部长度数据偏移头部字节数 首部长度 ×4data 指针向后移动这么多字节就跨过 TCP 头部到达应用数据。TCP通信过程可靠性本质 确认应答机制客户端一次可以向服务端发送多个报文服务端再做应答不用发一次应答一次提高效率画图箭头传递的内容如果单单只是应答的话就只有报头如果带有数据有效载荷就是一个完整的报文怎么理解 TCP 的可靠性可以保证历史消息的可靠性已经收到应答的报文100% 确认对方收到。最新发出去的报文还没有收到应答可靠性无法保证可能丢包、延迟。TCP不能做到 100% 实时可靠只有收到应答的数据才代表可靠交付。TCP 最重要可靠性策略确认应答机制ACK确认应答机制ACK发送方发送数据。接收方收到数据返回应答发送方收到应答代表这部分数据对方已经成功收到这份历史数据可靠。没有收到应答就不确定对方是否收到。TCP 序号 (seq) 确认号 (ack)、捎带应答1.序号 seq32 位TCP将每个字节的数据都进行了编号即为序号.后面再进一步理解。seq当前报文数据第一个字节的编号。作用①解决乱序TCP 根据序号 seq 把乱序到达的报文重新排序整理成有序字节放进缓冲区②去重③实现确认应答。2. 确认号 ack32 位核心ack 期望收到对方下一个字节的序号含义确认号之前所有字节全部已经完整收到。举例 对方发来报文seq1000携带 200 字节数据字节 1000‑1199 接收方回复ack 1200意思1000~1199 全部收到下一次希望收到序号 1200 开始的数捎带应答接收方不一定专门发一个纯 ACK 报文。即除了单单发送应答报头表示成功收到了这条报文外还可以发送数据有效载荷呀这时就可以把 ACK 确认号直接填在自己要发送的数据 TCP 头部里面跟着业务数据一起发出去这叫捎带应答。这样ACK 确认可以跟随自己的数据报文一起发送不用单独发确认报文。为什么 TCP 报文要有 seq 和 ack 两个字段TCP 是全双工双方都可以同时发数据。seq我发给你的数据的字节编号ack我对你发给我的数据的确认一条报文既要标记「我发出去的数据序号 seq」又要标记「我确认收到了你多少数据 ack」所以两个字段都必须存在再谈TCP流量控制接收方有接收缓冲区大小有限。 如果发送方疯狂发数据包接收缓冲区满了新的数据装不下 → 只能丢弃报文发送方白发网络浪费低效。问题发送端怎么知道对方接收缓冲区还剩多少空间TCP 引入窗口window也就是接收窗口 rwnd (receive window)。流量控制原理滑动窗口靠 ACK 报文中的窗口字段接收方每次回复 ACK 的时候会把自己接收缓冲区剩余可用空间填到 TCP 头部的Window窗口字段告诉发送方。rwnd 接收缓冲区剩余字节数。发送方严格遵守不能一次性发送超过 rwnd 字节的数据。发送方的发送窗口大小就等于对方通告过来的 rwnd。 这就是流量控制控制发送速率适配接收方处理能力防止接收缓冲区溢出。TCP 标志位为什么要有标志位TCP 报文有多种类型建立连接、断开连接、普通数据、应答、重置连接、紧急数据。 接收方拿到报文需要知道这是什么类型报文做不同处理。 于是 TCP 头部用多个 1bit 标志位位段 bit‑field来标记报文类型struct tcphdr:6 个核心 TCP 标志位每个占 1bit1 置位代表生效标志名字作用SYN同步建立连接三次握手。FIN结束断开连接四次挥手。ACK确认应答ack_seq字段有效即表明是一个应答报文绝大多数报文 ACK1RST重置异常断开连接拒绝连接报错复位PSH推送接收方立刻把缓冲区数据上交应用层不要缓存URG紧急urg_ptr紧急指针有效传输紧急数据SYN和FIN三次握手第一次握手客户端 → 服务端SYN1申请建立连接第二次握手服务端 → 客户端SYN1,ACK1服务端同意连接第三次握手客户端 → 服务端ACK1四次挥手第一次挥手客户端 → 服务端FIN1客户端我这边不再发送新数据关闭发送方向但还可以接收对方数据。第二次挥手服务端 → 客户端ACK1确认收到 FIN。此时服务端还可以继续向客户端发送剩下的数据。这一步只是应答服务端自己的发送通道还没关闭所以不能和 FIN 合并因此需要四次。3.第三次挥手服务端 → 客户端FIN1,ACK1服务端把自己剩余数据全部发送完毕后发送 FIN代表服务端也不再发送数据。4.第四次挥手客户端 → 服务端ACK1确认服务端 FIN。RST建立连接一定会成功吗不一定。网络丢包、端口没监听、双方对连接状态认知不一致就会出现RSTReset重置报文直接暴力断开连接对应浏览器报错ERR_CONNECTION_RESET连接已重置。图中的场景三次握手异常 —— 双方连接状态认知不一致对客户端来说在1位置处发送了ACK报文后就代表3次握手已经完成而服务端要在2位置处收到了这个ACK才算3次握手成功即双方对连接状态的认知不一样所以如果在1位置处客户端只要发送了ACK后就会认为已经成功建立了连接向服务端发送数据但如果发送的ACK丢失了服务端在2位置处没有收到那么等到服务端收到客户端发送的数据报文后发现根本就没有这个连接记录。服务端不知道这个 ACK 属于谁直接回复RST 报文重置。RST 常见触发场景1访问一个没有进程监听的端口2迟到的旧报文到达连接已经消失3通信中途异常一方进程崩溃退出4报文序号不合法5防火墙拦截核心上面所有这些问题都可以进行重置来解决URG 紧急标志 紧急指针 urg_ptrURG 标志位 1代表紧急指针 (urg_ptr) 字段有效。urg_ptr16 位紧急指针当前报文的有效载荷中特定偏移量处有紧急数据指向紧急数据的最后一个字节。TCP 是字节流默认按顺序处理接收缓冲区数据紧急数据可以跳过普通排队优先交给应用程序处理不用等前面全部普通数据读完。工作过程发送方URG1填写urg_ptr经典紧急数据通常 1 字节一般用作状态信号中断信号比如 ctrlc 取消上传不是传输大块业务数据。接收方看到URG1根据seq urg_ptr定位到紧急数据末尾先处理。⚠️重点紧急指针指向紧急数据的最后一字节不是起始位置例子取消上传场景客户端正在上传大文件想立刻中断本次传输。 不需要把前面一大堆上传数据全部读完直接发1 字节紧急数据当作中断命令接收方优先拿到这个紧急字节执行取消上传丢弃剩余普通数据流。再理解序列号确认号TCP不给报文编号给每一个字节编号。。我们可以把TCP发送缓冲区当成一个巨大的字节数组char send_buf[])缓冲区里每一字节都分配唯一序列号 (seq)。序列号就是数组下标每个字节对应一个序列号。seq这个报文中第一个字节的编号。eg字节内容abcdef序列号100110021003100410051006现在把abc放在第一个报文发送报文 seq 1001载荷abc代表这个报文携带字节1001、1002、1003。报文只是 “打包箱子”箱子本身没有编号箱子里面的每一个字节才有编号。ACK 确认号 ack_seq 怎么理解呢对方回复ack_seq 1004含义1004 之前所有字节1001、1002、1003我全部收到了下一个我想要字节编号 1004。不是确认报文是确认字节编号超时重传机制先来理解一下什么时候要超时重传什么是丢包ACK 报文ACK或者应答都是同一回事只有 TCP 头部没有载荷两种情况A 发出去的数据报文在网络丢了B 根本没收到。---数据丢了B 已经成功收到数据并且返回了 ACK1001但是ACK 应答报文在路上丢了。---ACK丢了所以丢包分为数据丢包和ACK丢包从网络设备角度两种都是网络丢包事件从 TCP 发送方A的角度丢包 我发出去的数据报文B 没有收到。在A的角度只有图1的那种情况数据没有到对方才算丢包图2对A来说不算丢包数据已经成功到B了所以对A来说没有收到ACK不意味着丢包即不意味着我的数据没有到B也有可能是数据已经到了但发生图2的情况但对网络来说没有收到ACK就是丢包了不管哪一种情况都是丢包超时重传设置一个超时计时器RTO重传超时时间规则发送数据的时候启动计时器如果计时器到期之前收到对应 ACK计时器撤销万事大吉。如果收不到 ACK 计时器超时发送方就判定报文丢失重传这份数据1‑1000“判定报文丢失”不是说 TCP 确定 “数据一定丢了”。 是时间窗口已经耗尽继续等没有意义只能假定丢失执行重传兜底。问题来了如果B 早就收到 1‑1000 字节。现在 A 又重传一遍 1‑1000B 收到重复数据。解决方案依靠序列号去重。B 根据序列号识别1‑1000 已经接收过直接丢弃重复数据但是依然再次回复 ACK1001。超时时间 RTO 应该设置多长为什么不能写死固定值如果设置太小马上就判定超时了明明再等一会就可以传回来了却定位超时导致大量不必要的重传浪费网络。如果设置太大真丢包的时候要等很久才重传传输延迟高。TCP 为了保证无论在任何环境下都能比较高性能的通信因此会动态计算这个最大超时时间.Linux 中 (BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制每次判定超时重发的超时时间都是 500ms 的整数倍.如果重发一次之后仍然得不到应答等待 2*500ms 后再进行重传.如果仍然得不到应答等待 4*500ms 进行重传。依次类推以指数形式递增.累计到一定的重传次数TCP 认为网络或者对端主机出现异常强制关闭连接.