Linux下 传输层协议UDP详解(含底层源码解析)

欢迎来到我的频道!【点击跳转专栏】

本文所有代码已托管至码云:【点此转跳】

文章目录

  • [1. 传输层](#1. 传输层)
  • [2. 再谈端⼝号](#2. 再谈端⼝号)
    • [2.1 端⼝号范围划分](#2.1 端⼝号范围划分)
    • [2.2 认识知名端⼝号](#2.2 认识知名端⼝号)
    • [2.3 两个问题](#2.3 两个问题)
  • [3. UDP协议](#3. UDP协议)
    • [3.1 UDP协议端格式](#3.1 UDP协议端格式)
    • [3.2 几个结论和问题(重点!封装和解包本质)](#3.2 几个结论和问题(重点!封装和解包本质))
    • [3.3 【结合源码解析】当我们收到一个报文的时候,我们是如何做到以文件管理的原理,读到数据到应用层的?(超级大重点!一定要读)](#3.3 【结合源码解析】当我们收到一个报文的时候,我们是如何做到以文件管理的原理,读到数据到应用层的?(超级大重点!一定要读))
    • [3.4 UDP的特点](#3.4 UDP的特点)
    • [3.5 ⾯向数据报](#3.5 ⾯向数据报)
    • [3.6 既然不可靠,为什么还要有UDP](#3.6 既然不可靠,为什么还要有UDP)
    • [3.7 UDP的缓冲区](#3.7 UDP的缓冲区)
    • [3.8 UDP使⽤注意事项](#3.8 UDP使⽤注意事项)
    • [3.9 补充:基于UDP的应⽤层协议](#3.9 补充:基于UDP的应⽤层协议)

1. 传输层

负责数据能够从发送端传输接收端。

2. 再谈端⼝号

端⼝号(Port)标识了⼀个主机上进⾏通信的不同的应⽤程序

在TCP/IP协议中, ⽤ "源IP", "源端⼝号", "⽬的IP", "⽬的端⼝号", "协议号" 这样⼀个五元组来标识⼀个通信(可以通过netstat -n查看);

2.1 端⼝号范围划分

  • 0‑1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
  • 1024‑65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.

2.2 认识知名端⼝号

有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:

  • ssh服务器,使用22端口
  • ftp服务器,使用21端口
  • telnet服务器,使用23端口
  • http服务器,使用80端口
  • https服务器,使用443

执行下面的命令,可以看到知名端口号

复制代码
1  cat /etc/services

我们自己写一个程序使用端口号时,要避开这些知名端口号.

2.3 两个问题

  1. 一个进程可以绑定多个端口号吗?

可以!

  1. 一个端口号可以绑定多个进程吗?

不可以!

3. UDP协议

3.1 UDP协议端格式

  • 16位UDP⻓度, 表⽰整个数据报(UDP⾸部+UDP数据)的最⼤⻓度;
  • 如果校验和出错, 就会直接丢弃;

3.2 几个结论和问题(重点!封装和解包本质)

  1. 为什么我们写socket的时候,port是16位的? 因为UDP协议规定!
  2. 报头和有效载荷如何分离?

UDP是采用固定长度的报头(8字节)!其中有一个固定位置是16位UDP长度!因为我们有它的长度,所以我们就能很轻松的将报头和正文内容进行分离!

  1. 那么有效载荷是怎么交付给上层的呢?

因为有16位目的端口号,就能得到交付给哪个端口的进程!

  1. 那么UDP是否存在粘包问题?

UDP叫用户数据报协议,长度固定好,收一个报文得一个报文!!所以不存在粘包问题!

  1. 我们是如何理解UDP协议的报头的呢?

OS内核是C语言写的,协议本质是一个结构体!在OS内部,双方是能互传结构体变量!(天然可以做反序列化)

如图:

  1. 所以我们该如何理UDP解封装过程?

当我们需要发送数据了,在OS内部会给我们开辟一段缓冲区,会把上层数据拷贝下来,并将指针向前移动UPD报头的位置,然后以访问结构体的方式通过指针将UDP属性部分填充进去!

封装的本质 就是对结构体的拷贝!

  1. 报文如何在协议层从一层到另一层

从底层到上层,你就是在做回调;从上层传下层,你就是调用下层函数,本质都是传参的方式,进行协议栈式的流通!

3.3 【结合源码解析】当我们收到一个报文的时候,我们是如何做到以文件管理的原理,读到数据到应用层的?(超级大重点!一定要读)

首先,OS内部,可能会同时存在多个报文! 那么就不可避免的需要对报文进行管理!

OS,报文的在内核表达的本质是一个 struct sk_buff结构体!所谓管理,就是将收到的报文以某种数据结构(假设是链表)进行管理!

  • head: 一般指向数据的最开头。
  • end: 指向结尾边界,后面有些属性字段也要被管理(不用管)
  • data: 在应用层指向应用层数据开头,到传输层移动到TCP协议头开头,到网络层,移动到IP协议头,依次类推!(而这就是封装的本质,即不断入栈的形式,对指针进行移动!解包就是反过来!)
  • tail: 指向应用层数据结尾!

结论 :报文贯穿协议栈,封装和解包的过程,最核心的其实就是移动指针就可以了!


知道报文数据的存储结构体,接着将视线移动到进程内部:

  • 进程,OS会创建对应的task_struct结构体,同时会会创建一个files_struct,里面有个struct file* fd_arrary[]的数组负责该进程所创建的文件,以下标fd的方式进行管理!
  • 当创建对应套接字的时候,就会创建对应的struct file结构体,但是怎么标识 套接字的特殊型 ,于是会创建一个struct socket的结构体,通过file内部的万能指针void* private_data指向对应的struct socket,至此就完成了文件到套接字的转换

接着把视角放到socket内部:

  • socket被private_data指向,同时socket内部还有个 struct file * file回指struct file!
  • 由于一切皆文件,当我们通过read、recv等接口进行读写的时候,进程回被阻塞,进程就会把 PCB 列入 wait_queue_head_t wait中呆着!当数据有了,就会再次把进程唤醒!
  • 同时 还有个结构叫 struct sock * sk指向的就是套接字内部的属性了!

将视线继续往下看:

  • 在struct sock中有两个属性:
  • struct sk_buff_head就是对应的 缓冲队列 这一块我写过 为了文章完整性 我把关键内容截取下来了:
  • 当然光有sock还不够,上层它还套了层struct inet_sock:

里面第一个成员就是struct sock,这里面的daddr、rcv_saddr、dport等就是目的地址、原地址、端口号等,即 套接字的绑定信息,就是填到这里的!!
我们真实在内核创建套接字,并持有信息的结构体其实是 inet_sock!而不是struct sock!,只是其第一个成员是 struct scok!


那么怎么区分是UDP还是TCP呢?

其实利用多态的思想 本质上就是在struct inet_sock 上再套上一层 struct udp_sock

里面第一个属性就是struct inet_sock inet!;未来换成TCP也是同理! 而里面的struct sock就是最原始的基类(网络传送接收的数据就是通过该基类管理的)!后面不管是套上什么样的子类,都可以利用多态思想 通过指针的强转 ,然后利用文件的wirte、read接口读取写入 网络缓冲区里面的东西!!至此 文件和套接字网络就关联起来了!!

3.4 UDP的特点

UDP传输的过程类似于寄信

  • ⽆连接: 知道对端的IP和端⼝号就直接进⾏传输, 不需要建⽴连接;
  • 不可靠: 没有确认机制, 没有重传机制; 如果因为⽹络故障该段⽆法发到对⽅, UDP协议层也不会给应⽤层返回任何错误信息(UDP本身不可靠,但不排除上层代码维护可靠性)
  • ⾯向数据报: 不能够灵活的控制读写数据的次数和数量

3.5 ⾯向数据报

应⽤层交给UDP多⻓的报⽂, UDP原样发送, 既不会拆分, 也不会合并

⽤UDP传输100个字节的数据:

如果发送端调⽤⼀次sendto, 发送100个字节, 那么接收端也必须调⽤对应的⼀次recvfrom, 接收100个字节; ⽽不能循环调⽤10次recvfrom, 每次接收10个字节

3.6 既然不可靠,为什么还要有UDP

所谓的不可靠,不是数据传不过去,而是丢包了,我们不关心!!现阶段网络环境已经很少丢包了!

所谓的不可靠,不代表 不可用!!

可靠,意味着需要做更多工作,就相对更慢,而不可靠,就意味着相对快一点,使用更简单,意味着在不重要的场合可以更简单更快速进行传输!

3.7 UDP的缓冲区

虽然UDP也是全双工!!

  • 但是 UDP没有真正意义上的 发送缓冲区. 调⽤sendto会直接交给内核, 由内核将数据传给⽹络层协议进⾏后续的传输动作( 因为UDP是不可靠的,直接发出去就行,不存在TCP那种如果传输失败,需要缓冲区数据作为备份再次发送!);
  • UDP具有接收缓冲区. 但是 这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序⼀致 ; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;

3.8 UDP使⽤注意事项

我们注意到, UDP协议⾸部中有⼀个16位的最⼤⻓度. 也就是说⼀个UDP能传输的数据最⼤⻓度是64K(包含UDP⾸部)!

然⽽64K在当今的互联⽹环境下, 是⼀个⾮常⼩的数字.

如果我们需要传输的数据超过64K, 就需要在应⽤层⼿动的分包, 多次发送, 并在接收端⼿动拼装

3.9 补充:基于UDP的应⽤层协议

  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

当然,也包括你自己写UDP程序时自定义的应用层协议!

类似动不动10w+的直播视频,都是UDP的,因为一个一个建立连接,服务器成本太高了!

相关推荐
阿钱真强道2 小时前
12 嵌入式操作系统 | UDP 服务器编程
服务器·网络协议·udp
青梅味猪大肠2 小时前
【操作系统-24】双标志先检查
linux·运维·服务器
xiaoye-duck2 小时前
《Linux 网络编程》深入理解五种 IO 模型:从 IO 本质到非阻塞 IO 实战
linux·网络
程序员-Benothing2 小时前
Linux内核与模块管理:uname、lsmod、modprobe、sysct
linux·运维·数据库
微三云生态系统架构师-彭丹2 小时前
智慧物业工单智能派单算法:技能匹配与位置负载均衡设计
运维·负载均衡·物业管理·调度算法·工单系统·智能派单·智慧物业
嵌入式小能手2 小时前
飞凌嵌入式ElfBoard-Shell编程应用实例-使用WiFi拨号上网
linux·服务器·网络
..Dauntless..2 小时前
【Linux】环境变量
linux·运维·服务器
FED_AF2 小时前
Linux运维“邪修”功法之正则表达式
linux·运维·正则表达式
极光通讯3 小时前
服务器CPU现货选型:Intel Xeon Scalable vs AMD EPYC平台适配指南
运维·服务器