
hello~ 很高兴见到大家! 这次带来的是Linux网络中关于网络基础这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?个 人 主 页: 默|笙文章目录一、UDP1.1 认识知名服务器端口号1.2 UDP 协议报文格式1.3 UDP特点总结1.4 理解报文封装过程1.5 理解报文和报文的读取代码HTTP代码一、UDP应用层往下就进入了位于操作系统内部的传输层和网络层。其中传输层最有名的两个协议就是 TCP 和 UDP 协议网络层最核心的则是 IP 协议。1.1 认识知名服务器端口号0 ~ 1023知名端口号Well-known Ports像 SSH(22)、HTTP(80)、HTTPS(443) 这些广为使用的应用层协议它们的端口号都是固定的。1024 ~ 65535一般由普通程序或客户端使用。可以细分为两段1024 ~ 49151注册端口分配给普通的服务端程序。比如你写的后端代码如 8080或者 MySQL3306、Redis6379等。49152 ~ 65535动态/私有端口操作系统真正“动态分配”给客户端程序的端口号。当你用浏览器打开网页时操作系统会从这个范围里随机挑一个空闲的端口比如 51234给你的浏览器使用。有一些服务器提供的服务是非常常用的人们约定这些服务会绑定一个固定的端口号被称为“知名端口”比如SSH 服务器使用 22 端口用于安全的远程登录FTP 服务器使用 21 端口用于文件传输Telnet 服务器使用 23 端口早期的明文远程登录现已被淘汰HTTP 服务器使用 80 端口明文 Web 服务HTTPS 服务器使用 443 端口加密 Web 服务DNS使用 53 端口域名解析一个进程可以 bind 多个端口号吗可以的。 一个进程可以创建多个 Socket并分别绑定不同的端口号例如一个 Web 服务器进程同时监听 80 端口和 443 端口。但是默认情况下一个端口号只能被一个进程进行绑定。 如果第二个进程尝试绑定同一个端口操作系统会报错比如 Address already in use。1.2 UDP 协议报文格式UDP 报文由源端口号、目的端口号、UDP长度以及UDP校验和组成其中UDP长度指的是报头有效载荷总体的大小。校验和则是用来检测数据在传输过程中有没有发生损坏。这四个字段每个占 2 个字节16位所以报头固定是 8 字节。UDP 协议报文的报头长度是固定的为8字节而报文总体长度又被记载在报头里面这样就能够知道有效载荷长度从而将报头和有效载荷进行分离。这些有效载荷应该给谁–16位目的端口号交给这个目的端口号对应的进程就好。为什么我们socket的时候端口号得是16位的是因为这是UDP和TCP协议明确规定的。16 位二进制能表示的最大数字是 2 的 16 次方也就是 65536。这就是为什么网络端口号的范围只能是 0 ~ 65535。因为UDP长度是16位的也就是说一个UDP报文可以传输的有效载荷是有限的最大约64KB。如果要传输的内容太长就得分成好几个报文进行发送或者底层IP协议自动分片接收方还得一一拼接。UDP 报文不存在数据粘包问题因为读取UDP报文时必须得读取一整个报文用户数据报协议就是面向报文的这个过程很像寄快递或者邮件寄和收都是一整个。这就是 UDP 的“面向报文”特性。你调用一次 sendto对方就必须调用一次 recvfrom 来接收。边界非常清晰就像拿刀子切好的蛋糕一块就是一块。而 TCP 像水龙头水流出来是连在一起的你得自己找分隔符这就是 TCP 为什么有粘包问题。structudphdr{__be16 source;// 源端口号__be16 dest;// 目的端口号__be16 len;// UDP长度__sum16 check;// 校验和};UDP 报头的本质其实就是一个约定好的协议结构体由于UDP协议是在操作系统内部的操作系统大部分是由C语言写的所以这个协议结构体也是C语言写的。1.3 UDP特点总结无连接不需要像 TCP 那样通过“三次握手”建立连接只要知道目标的 IP 和端口号就可以直接进行数据传输。UDP 在发送数据前不会在双方之间建立任何“状态”或“通道”。它的开销极小只有 8 字节报头没有建立和断开连接的延迟非常适合对实时性要求极高的场景如直播、DNS 查询。当然因为没有连接它也就无法保证发送顺序和到达顺序一致乱序。不可靠UDP 不会告诉你此次传输是否成功。它没有确认机制也没有重传机制如果因为网络拥塞等原因导致数据没有送达目标UDP 也不会向应用层返回任何报错信息。在计算机网络中UDP 被称为“尽最大努力交付Best-effort delivery”。无论数据包是在半路丢了还是因为校验和错误被接收方内核静默丢弃发送方都无从知晓。如果应用层比如你写代码需要保证数据绝对不丢必须自己在应用层实现一套“确认重传”的逻辑比如 QUIC 协议就是这样做的。面向用户数据报UDP 是“面向报文”的它保留了应用层传递消息的边界。应用层交给 UDP 多大的数据UDP 就原样封装发送接收方必须一次性读取一整个数据报不能像 TCP 那样“流水式”地分批读。如果接收方的缓冲区太小装不下整个报文多余的数据会被直接截断丢弃如果缓冲区很大一次也只能读到一个完整的报文不会把两次发送的数据拼在一起。正因为它严格绑定了“一收一发”的边界所以读写起来不如 TCP字节流那样灵活。DP 没有真正意义上的发送缓冲区它调用 sendto 之后数据会直接拷贝给系统内核然后由内核交给网络层。它有接收缓冲区但无法保证发送顺序和到达顺序一致可以这样理解几个人虽然起点和终点相同但因为选择的代步工具不同、路况不同到达的顺序自然会和出发顺序有所不同。而且如果接收缓冲区满了后续到达的报文就会被直接丢弃。UDP 的 Socket 既能读也能写这种特性在网络通信中被称为全双工Full-Duplex。也就是说发送数据和接收数据使用的是两条独立的通道不需要像对讲机那样“说完一句切一下开关才能听”。1.4 理解报文封装过程报文的封装过程开辟缓冲区当应用层想要发送数据时操作系统内核会先分配一块足够大的连续内存缓冲区并预留出足够的“头部空间Headroom”。拷贝数据把用户真正要发的数据比如你写的 HTTP 报文拷贝到这块内存的靠右的位置。此时内存左边全是空的。指针左移当数据要往下传给传输层时内核不会重新分配内存去拷贝数据而是把指向数据的指针向左移动比如移动 8 个字节对应 UDP 报头的大小。强转填充*把左移后的指针强转成 UDP 协议的结构体指针 (struct udphdr)直接在这 8 个字节里填入源端口、目的端口、长度、校验和。瞬间一个 UDP 报文就封装好了继续下探传给网络层IP时指针继续左移 20 字节IPv4 报头强转填充 IP 地址传给链路层以太网时指针再左移填入 MAC 地址。1.5 理解报文和报文的读取如何理解报文报文宏观视角报文就是网络中传输的独立数据块。微观视角报文就是一段连续的内存。在底层C语言中它就是一个“结构体头 有效载荷”。系统中肯定不只存在一个报文而是很多个那么操作系统应该如何管理这些报文先描述再组织。先描述形成结构体sk_buff再组织起来。在早期的 Linux 系统版本如 2.6 版本中sk_buff 结构体的核心成员变量主要就是 4 个指针head、data、tail、end。其中data 指针就是封装过程中用来向左移动并填充协议头字段的核心指针。从 head 到 end 就是一整个系统为报文开辟的缓冲区其中头空间headroom和尾空间tailroom通常都是空余的。当我们收到一个报文时应用层之所以能通过文件管理比如调用 read 或 recvfrom读取到数据是因为底层的配合首先struct file 结构体里面的 private_data 指针指向了此文件的 struct socket 结构体socket 结构体里面不仅有一个 file 指针回指 struct file实现互相指它本身还有一个 struct sock* 类型的基类指针。当我们创建的是 UDP 协议时这个 struct sock* 实际上指向的是更庞大的 udp_sock 结构体。这就像 C 语言中用“基类指针指向子类对象”。因为 udp_sock 的内存布局开头就是 struct sock所以只要拿到底层的 sock 指针直接通过指针强转 (struct udp_sock *)sk就能随意访问 udp_sock 里特有的成员变量了。这样内核就能通过文件管理系统层层剥茧最终找到数据。在 struct sock 结构体里面维护着接收队列sk_receive_queue和发送队列sk_write_queue这两个队列里面挂着的就是一长串的 sk_buff用来管理报文的结构体真正的网络数据就藏在 sk_buff 所指向的内存里。struct socket 是内核中的基础套接字抽象层。调用 socket() 时内核会创建 struct socket并关联具体的协议控制块UDP 对应 udp_sockTCP 对应 tcp_sock。这些具体结构体内部都内嵌了通用的 struct sock因此可以通过 struct sock * 这样的基类指针统一访问并通过函数指针表调用具体协议的实现需要访问协议特有字段时再强转回具体类型即可。从结构层次看UDP 为 udp_sock - inet_sock - sockTCP 为 tcp_sock - inet_connection_sock - inet_sock - sock。也就是说TCP 比 UDP 多一层 inet_connection_sock而不是多一层 inet_sock因为两者都包含 inet_sock。Linux 内核正是用这种“内嵌结构体 函数指针表”的方式在 C 语言中模拟继承与多态。今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~让我们共同努力, 一起走下去!