当前位置: 首页 > article >正文

Linux内核传输层UDP源码分析

一、用户数据包协议(UDP)

1.UDP数据报头

        UDP 提供面向消息的不可靠传输,但没有拥塞控制功能。很多协议都使用 UDP,如用于 IP 网络传输音频和视频的实时传输协议 (Real-time Transport Protocol,RTP),此类型容许一定的数据包丢弃。UDP 报头长 8 字节,具体内核源码如下:

2.UDP初始化操作

        定义对象udp_protocol(net_protocol对象)并使用方法inet_add_protocol()来添加它,具体源码如下:

 1. udp_protocol 结构体分析(协议注册)

static struct net_protocol udp_protocol = {.early_demux = udp_v4_early_demux,    // 早期解复用函数,处理初始阶段的数据包.early_demux_handler = udp_v4_early_demux, // 早期解复用处理器.handler = udp_rcv,                    // UDP 数据包接收处理函数,处理完整的 UDP 数据接收.err_handler = udp_err,                // 错误处理函数,处理 UDP 传输中的错误.no_policy = 1,                        // 标记是否忽略策略检查.netns_ok = 1                          // 标记是否支持网络命名空间
};
  • 作用:这是内核中 UDP 协议的注册实现。通过定义 net_protocol 类型的 udp_protocol,向内核网络子系统注册 UDP 协议的处理函数。内核通过 inet_add_protocol() 方法将其加入协议处理链,使内核能够识别和处理 UDP 数据包。

2.inet_init_net 初始化(端口范围等配置)

  • 作用:初始化网络命名空间内的 IPv4 相关参数。例如设置本地端口分配范围(ip_local_ports),限制程序动态申请端口的范围;配置 ping 套接字的用户组权限(ping_group_range),控制哪些用户组可创建 ping 套接字。

 3.发送UDP数据包udp_sendmsg(...)

        从UDP用户空间套接字中发送数据,可以使用系统调用send()、sendto()、sendmsg()和write()。这些系统调用最终都会由内核中的方法udp_sendmsg()来处理。以下是这个函数的流程图:

1. 函数参数

  • struct sock *sk:指向套接字的指针,该套接字表示当前进行 UDP 数据发送操作所使用的套接字对象,其中包含了套接字的各种状态信息和配置。
  • struct msghdr *msg:指向 msghdr 结构体的指针,这个结构体包含了要发送的数据以及目标地址等信息,例如数据缓冲区、目标地址结构体等。
  • size_t len:表示要发送的数据的长度。

2. 函数功能

udp_sendmsg 函数是 Linux 内核中用于通过 UDP 套接字发送数据的核心函数。它会对传入的消息进行一系列的检查和处理,包括验证目标地址、处理控制消息、查找路由表等操作,最终将数据封装成 UDP 数据包并发送出去。如果在发送过程中遇到错误,会返回相应的错误码;如果发送成功,则返回实际发送的数据长度。

3. 重要部分及流程讲解

3.1 基本参数检查和初始化
if (len > 0xFFFF)return -EMSGSIZE;if (msg->msg_flags & MSG_OOB)return -EOPNOTSUPP;getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
  • 首先检查要发送的数据长度是否超过了 UDP 数据包的最大长度(0xFFFF),如果超过则返回 EMSGSIZE 错误。
  • 接着检查消息标志中是否包含 MSG_OOB(带外数据),由于 UDP 不支持带外数据,所以如果包含则返回 EOPNOTSUPP 错误。
  • 根据套接字是否为 UDP-Lite 类型,选择不同的数据获取函数。
3.2 处理已 corked 的套接字
fl4 = &inet->cork.fl.u.ip4;
if (up->pending) {lock_sock(sk);if (likely(up->pending)) {if (unlikely(up->pending != AF_INET)) {release_sock(sk);return -EINVAL;}goto do_append_data;}release_sock(sk);
}
  • 检查套接字是否已经处于 corked 状态(即有未发送的数据包)。如果是,则加锁并进一步检查状态是否正确,若不正确则返回 EINVAL 错误,若正确则跳转到 do_append_data 标签处继续处理。
3.3 获取并验证目标地址
if (usin) {if (msg->msg_namelen < sizeof(*usin))return -EINVAL;if (usin->sin_family != AF_INET) {if (usin->sin_family != AF_UNSPEC)return -EAFNOSUPPORT;}daddr = usin->sin_addr.s_addr;dport = usin->sin_port;if (dport == 0)return -EINVAL;
} else {if (sk->sk_state != TCP_ESTABLISHED)return -EDESTADDRREQ;daddr = inet->inet_daddr;dport = inet->inet_dport;connected = 1;
}
  • 如果 msg 中包含目标地址信息,则检查地址长度和地址族是否正确,获取目标 IP 地址和端口号,并验证端口号是否有效。
  • 如果 msg 中不包含目标地址信息,则检查套接字是否已经连接,如果未连接则返回 EDESTADDRREQ 错误,否则使用套接字中保存的目标地址和端口号,并标记为已连接。
3.4 处理控制消息
if (msg->msg_controllen) {err = udp_cmsg_send(sk, msg, &ipc.gso_size);if (err > 0)err = ip_cmsg_send(sk, msg, &ipc, sk->sk_family == AF_INET6);if (unlikely(err < 0)) {kfree(ipc.opt);return err;}if (ipc.opt)free = 1;connected = 0;
}
3.5 查找路由表
if (connected)rt = (struct rtable *)sk_dst_check(sk, 0);if (!rt) {struct net *net = sock_net(sk);__u8 flow_flags = inet_sk_flowi_flags(sk);fl4 = &fl4_stack;flowi4_init_output(fl4, ipc.oif, ipc.sockc.mark, tos,RT_SCOPE_UNIVERSE, sk->sk_protocol,flow_flags,faddr, saddr, dport, inet->inet_sport,sk->sk_uid);security_sk_classify_flow(sk, flowi4_to_flowi(fl4));rt = ip_route_output_flow(net, fl4, sk);if (IS_ERR(rt)) {err = PTR_ERR(rt);rt = NULL;if (err == -ENETUNREACH)IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);goto out;}err = -EACCES;if ((rt->rt_flags & RTCF_BROADCAST) &&!sock_flag(sk, SOCK_BROADCAST))goto out;if (connected)sk_dst_set(sk, dst_clone(&rt->dst));
}
  • 如果套接字已经连接,则检查缓存的路由信息是否可用。
  • 如果没有可用的路由信息,则初始化 flowi4 结构体,进行安全分类,并调用 ip_route_output_flow 函数查找路由表。如果查找失败,则根据错误码进行相应处理,如增加统计信息并跳转到 out 标签处。
  • 如果路由表中包含广播标志,但套接字不允许广播,则返回 EACCES 错误。
3.6 发送数据
if (!corkreq) {struct inet_cork cork;skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,sizeof(struct udphdr), &ipc, &rt,&cork, msg->msg_flags);err = PTR_ERR(skb);if (!IS_ERR_OR_NULL(skb))err = udp_send_skb(skb, fl4, &cork);goto out;
}lock_sock(sk);
if (unlikely(up->pending)) {release_sock(sk);net_dbg_ratelimited("socket already corked\n");err = -EINVAL;goto out;
}
fl4 = &inet->cork.fl.u.ip4;
fl4->daddr = daddr;
fl4->saddr = saddr;
fl4->fl4_dport = dport;
fl4->fl4_sport = inet->inet_sport;
up->pending = AF_INET;do_append_data:
up->len += ulen;
err = ip_append_data(sk, fl4, getfrag, msg, ulen,sizeof(struct udphdr), &ipc, &rt,corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
if (err)udp_flush_pending_frames(sk);
else if (!corkreq)err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))up->pending = 0;
release_sock(sk);
  • 如果不需要 cork 操作(即 corkreq 为 false),则调用 ip_make_skb 函数创建一个 skb(套接字缓冲区),并调用 udp_send_skb 函数发送数据。
  • 如果需要 cork 操作,则加锁并检查套接字状态,更新相关信息,然后调用 ip_append_data 函数将数据追加到缓冲区中。如果追加过程中出现错误,则调用 udp_flush_pending_frames 函数清空缓冲区;如果不需要 cork 操作,则调用 udp_push_pending_frames 函数将缓冲区中的数据发送出去。
3.7 清理资源并返回结果
out:
ip_rt_put(rt);
out_free:
if (free)kfree(ipc.opt);
if (!err)return len;
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {UDP_INC_STATS(sock_net(sk),UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;
  • 释放路由表资源和控制消息相关的内存。
  • 如果没有错误发生,则返回实际发送的数据长度;如果出现错误,则根据错误码进行相应的统计更新并返回错误码。
如果  msg 中包含控制消息,则调用  udp_cmsg_send 和  ip_cmsg_send 函数处理这些控制消息。如果处理过程中出现错误,则释放相关资源并返回错误码。

4.接收来自网络层的UDP数据包udp_rcv(...)

        方法udp_rcv()是负责接收来自网络层的UDP数据包的主要处理程序,函数流程如下:

1. 函数参数和功能

函数参数
  • struct sk_buff *skb:指向套接字缓冲区的指针,其中包含接收到的 UDP 数据包。
  • struct udp_table *udptable:指向 UDP 表的指针,该表用于存储 UDP 套接字的相关信息。
  • int proto:协议类型,通常为 IPPROTO_UDP
函数功能

udp_rcv 函数是一个简单的包装函数,它直接调用 __udp4_lib_rcv 函数来处理接收到的 UDP 数据包。__udp4_lib_rcv 函数是处理 UDP 数据包接收的核心函数,它会对数据包进行一系列的验证和处理,包括检查数据包长度、校验和,查找对应的套接字,将数据包传递给相应的套接字进行处理,或者在找不到合适套接字时发送 ICMP 错误消息。

2. 重要部分及流程讲解

2.1 基本参数检查和初始化
if (!pskb_may_pull(skb, sizeof(struct udphdr)))goto drop;		/* No space for header. */uh   = udp_hdr(skb);
ulen = ntohs(uh->len);
saddr = ip_hdr(skb)->saddr;
daddr = ip_hdr(skb)->daddr;
  • 首先检查套接字缓冲区中是否有足够的空间来存储 UDP 头部,如果没有则跳转到 drop 标签处丢弃数据包。
  • 提取 UDP 头部信息,包括 UDP 数据包长度 ulen,源 IP 地址 saddr 和目的 IP 地址 daddr
2.2 数据包长度验证
if (ulen > skb->len)goto short_packet;if (proto == IPPROTO_UDP) {/* UDP validates ulen. */if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))goto short_packet;uh = udp_hdr(skb);
}
  • 检查 UDP 头部中记录的数据包长度 ulen 是否超过了实际接收到的数据包长度 skb->len,如果超过则跳转到 short_packet 标签处处理。
  • 如果协议类型为 IPPROTO_UDP,还需要检查 ulen 是否小于 UDP 头部长度,或者调用 pskb_trim_rcsum 函数对数据包进行裁剪和校验和更新,如果出现问题则跳转到 short_packet 标签处。
2.3 校验和初始化
if (udp4_csum_init(skb, uh, proto))goto csum_error;

调用 udp4_csum_init 函数对 UDP 数据包的校验和进行初始化,如果初始化失败则跳转到 csum_error 标签处处理。

2.4 查找套接字并处理
sk = skb_steal_sock(skb);
if (sk) {struct dst_entry *dst = skb_dst(skb);int ret;if (unlikely(sk->sk_rx_dst != dst))udp_sk_rx_dst_set(sk, dst);ret = udp_unicast_rcv_skb(sk, skb, uh);sock_put(sk);return ret;
}
  • 尝试从套接字缓冲区中获取关联的套接字 sk
  • 如果获取到了套接字,检查接收目标地址是否与套接字中的记录一致,如果不一致则更新。
  • 调用 udp_unicast_rcv_skb 函数将数据包传递给该套接字进行处理,并返回处理结果。
2.5 处理广播或多播数据包
if (rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))return __udp4_lib_mcast_deliver(net, skb, uh,saddr, daddr, udptable, proto);

如果路由表项中包含广播或多播标志,则调用 __udp4_lib_mcast_deliver 函数处理广播或多播数据包。

2.6 再次查找套接字
sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
if (sk)return udp_unicast_rcv_skb(sk, skb, uh);

如果之前没有找到关联的套接字,再次调用 __udp4_lib_lookup_skb 函数根据源端口和目的端口在 UDP 表中查找合适的套接字。如果找到则将数据包传递给该套接字进行处理并返回结果,

这个过程通常包含以下步骤:

  • 把接收到的数据添加到套接字的接收缓冲区。
  • 若应用程序正在阻塞等待数据,就会唤醒该应用程序,让其从接收缓冲区读取数据。
  • 若应用程序采用的是非阻塞模式,就会在后续的轮询或事件通知中得知有新数据到达。
2.7 策略检查和校验和检查
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))goto drop;
nf_reset_ct(skb);/* No socket. Drop packet silently, if checksum is wrong */
if (udp_lib_checksum_complete(skb))goto csum_error;
  • 重置连接跟踪信息。
  • 调用 udp_lib_checksum_complete 函数检查 UDP 数据包的校验和,如果校验和错误则跳转到 csum_error 标签处处理。
2.8 发送 ICMP 错误消息
__UDP_INC_STATS(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);/** Hmm.  We got an UDP packet to a port to which we* don't wanna listen.  Ignore it.*/
kfree_skb(skb);
return 0;

如果没有找到合适的套接字,增加相应的统计信息,调用 icmp_send 函数发送 ICMP 目的不可达(端口不可达)消息,然后释放套接字缓冲区并返回 0。

2.9 错误处理
short_packet:net_dbg_ratelimited("UDP%s: short packet: From %pI4:%u %d/%d to %pI4:%u\n",proto == IPPROTO_UDPLITE ? "Lite" : "",&saddr, ntohs(uh->source),ulen, skb->len,&daddr, ntohs(uh->dest));goto drop;csum_error:/** RFC1122: OK.  Discards the bad packet silently (as far as* the network is concerned, anyway) as per 4.1.3.4 (MUST).*/net_dbg_ratelimited("UDP%s: bad checksum. From %pI4:%u to %pI4:%u ulen %d\n",proto == IPPROTO_UDPLITE ? "Lite" : "",&saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),ulen);__UDP_INC_STATS(net, UDP_MIB_CSUMERRORS, proto == IPPROTO_UDPLITE);
drop:__UDP_INC_STATS(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);kfree_skb(skb);return 0;
  • short_packet 标签处:记录数据包长度过短的调试信息,然后跳转到 drop 标签处。
  • csum_error 标签处:记录校验和错误的调试信息,增加校验和错误的统计信息,然后跳转到 drop 标签处。
  • drop 标签处:增加接收错误的统计信息,释放套接字缓冲区并返回 0。
调用  xfrm4_policy_check 函数进行安全策略检查,如果检查不通过则跳转到  drop 标签处丢弃数据包。

5.UDP使用流程 

1. 用户层调用(sendto)

  • 用户通过 socket () 创建 UDP 套接字,调用 sendto () 发送数据报
  • 参数包含目标 IP (47.95.193.211)、端口 (默认可能未指定,需填充)
  • 内核进入 inet_sendmsg () 处理,分配 skb_buff(创建位置:传输层)

2. 传输层处理(UDP 层)

  • 创建 sk_buff 数据结构(skb->dev = 网络设备指针)
  • 填充 UDP 头(源端口 8888,目标端口待确认)
  • 计算校验和(可选,由 net.core.udp_checksum 内核参数控制)
  • skb->protocol = htons (ETH_P_IP),标识上层协议

3. 网络层处理(IP 层)

  • 调用 ip_queue_xmit () 进行路由查找
  • 路由表查询:
    • 使用 FIB 表(fib_rules)查找最佳路由
    • 确定输出设备(eth0/wlan0 等)
    • 下一跳地址(可能是网关地址,若目标不在同一子网)
  • 填充 IP 头:
    • 源 IP (192.168.186.138),目标 IP (47.95.193.211)
    • TTL、协议号 (17)、校验和
  • 检查是否需要分片(根据 MTU)

4. 链路层处理

  • 调用 dev_queue_xmit () 进入链路层
  • ARP 表查询:
    • 使用 neigh_table 结构查找 arp_cache
    • 若下一跳是网关,查询网关 MAC
    • 若目标在同一子网,直接查目标 MAC
    • 若 ARP 缓存缺失,触发 ARP 请求
  • 填充链路层头(以太网为例):
    • 源 MAC(本地网卡 MAC)
    • 目标 MAC(下一跳或目标 MAC)
    • 类型字段 0x0800(IP 协议)

5. 物理层传输

  • sk_buff 通过 net_device_ops->ndo_start_xmit () 发送
  • 物理层将二进制数据转换为电信号 / 光信号传输

接收流程简要说明:

  1. 物理层接收信号并转为二进制数据
  2. 链路层剥离帧头,检查 CRC 校验
  3. IP 层校验和验证,路由表查找输入接口
  4. UDP 层校验和验证,端口号匹配
  5. 用户层通过 recvfrom () 接收数据

关键数据结构说明:

  • sk_buff:贯穿各层,包含协议头、数据负载、设备指针等
  • fib_rules:路由规则表,用于确定输出路径
  • arp_cache:基于 neigh_table 的邻居缓存,存储 IP-MAC 映射
  • net_device:网络设备结构体,包含发送 / 接收函数指针

相关文章:

Linux内核传输层UDP源码分析

一、用户数据包协议&#xff08;UDP&#xff09; 1.UDP数据报头 UDP 提供面向消息的不可靠传输&#xff0c;但没有拥塞控制功能。很多协议都使用 UDP&#xff0c;如用于 IP 网络传输音频和视频的实时传输协议 (Real-time Transport Protocol&#xff0c;RTP)&#xff0c;此类型…...

FPGA学习(二)——实现LED流水灯

FPGA学习(二)——实现LED流水灯 目录 FPGA学习(二)——实现LED流水灯一、DE2-115时钟源二、控制6个LED灯实现流水灯1、核心逻辑2、代码实现3、引脚配置4、实现效果 三、模块化代码1、分频模块2、复位暂停模块3、顶层模块 四、总结 一、DE2-115时钟源 DE2-115板子包含一个50MHz…...

E1-最远距离(stl使用)

题目描述 给定一个数组&#xff0c;请你找出数组中相同元素之间的最远距离。若数组中不存在相同元素&#xff0c;则输出 null。 输入描述 输入一个数组&#xff0c;数组长度不超过 10000。格式请见用例。 输出描述 输出数组中相同元素的最远距离。 用例 输入 [3, 2, 3,…...

Linux如何在设备树中表示和引用设备信息

DTS基本知识 dts 硬件的相应信息都会写在.dts为后缀的文件中&#xff0c;每一款硬件可以单独写一份xxxx.dts&#xff0c;一般在Linux源码中存在大量的dts文件&#xff0c;对于arm架构可以在arch/arm/boot/dts找到相应的dts&#xff0c;一个dts文件对应一个ARM的machie。 dtsi 值…...

Matlab 汽车振动多自由度非线性悬挂系统和参数研究

1、内容简介 略 Matlab 169-汽车振动多自由度非线性悬挂系统和参数研究 可以交流、咨询、答疑 2、内容说明 略 第二章 汽车模型建立 2.1 汽车悬架系统概述 2.1.1 悬架系统的结构和功能 2.1.2 悬架分类 2.2 四分之一车辆模型 对于车辆动力学&#xff0c;一般都是研究其悬…...

Maven核心包:maven-resolver-api

在阅读 nexus-pubic 开源项目过程中&#xff0c;使用了大量的核心组件进行轻量化集成。它的这种构建方式&#xff0c;在阅读过程中不得不感概&#xff0c;节省成本从构建项目的方式上就遥遥领先了。但是 maven核心包&#xff0c;依然使用前几年的aether-spi&#xff0c;却没有更…...

生活中的可靠性小案例11:窗户把手断裂

窗户把手又断了&#xff0c;之前也断过一次&#xff0c;使用次数并没有特别多。上方的图是正常的把手状态&#xff0c;断的形状如下方图所示。 这种悬臂梁结构&#xff0c;没有一个良好的圆角过渡&#xff0c;导致应力集中。窗户的开关&#xff0c;对应的是把手的推拉&#xff…...

[oeasy]python074_ai辅助编程_水果程序_fruits_apple_banana_加法_python之禅

074_ai辅助编程_水果程序_fruits_加法 回忆上次内容 上次直接从模块中导入变量、函数 from my_file import pi 导入my_file.pi 并作为 pi 使用 from my_file import pi as my_pi 导入变量 并 重命名 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; …...

【图论】并查集的学习和使用

目录 并查集是什么&#xff1f; 举个例子 组成 父亲数组&#xff1a; find函数&#xff1a; union函数&#xff1a; 代码实现&#xff1a; fa[] 初始化code: find code&#xff1a; 递归实现: 非递归实现: union code : 画图模拟&#xff1a; 路径压缩&#xff1a…...

欢乐力扣:反转链表

文章目录 1、题目描述2、思路 1、题目描述 反转链表。  给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 2、思路 借助cur指针和pre双指针来调整链表的前后指向。 # Definition for singly-linked list. # class ListNode: # def __i…...

1.8PageTable

页表的作用 虚拟地址空间映射&#xff1a;页表记录了进程的虚拟页号到物理页号的映射关系。每个进程都有自己的页表&#xff0c;操作系统为每个进程维护一个独立的页表。内存管理&#xff1a;页表用于实现虚拟内存管理&#xff0c;支持进程的虚拟地址空间和物理地址空间之间的…...

什么是大带宽服务器

什么是大带宽服务器&#xff1f; 在深入探讨大带宽之前&#xff0c;让我们先明确带宽的概念。带宽与我们日常所说的宽带有所不同&#xff0c;宽带是运营商为满足家庭或商业上网需求所提供的服务&#xff0c;而带宽则特指数据的传输速度&#xff0c;尤其是上行速度。大带宽服务…...

【TCP】三次挥手,四次挥手详解--UDP和TCP协议详解

活动发起人小虚竹 想对你说&#xff1a; 这是一个以写作博客为目的的创作活动&#xff0c;旨在鼓励大学生博主们挖掘自己的创作潜能&#xff0c;展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴&#xff0c;那么&#xff0c;快来参加吧&#xff01…...

Compose 实践与探索十二 —— 附带效应

1、SideEffect Google 官方文档对 side effect 有两种翻译&#xff0c;简体中文翻译为附带效应&#xff0c;繁体中文翻译为副作用。这两个翻译我们用哪个都行&#xff0c;关键是如何理解它的含义。 1.1 什么是副作用 我们在日常生活中听到的副作用大多是医学领域中的&#x…...

Kubernetes 控制平面详解 —— 探秘 API Server、Controller Manager、Scheduler 与 etcd

文章目录 Kubernetes 控制平面详解 —— 探秘 API Server、Controller Manager、Scheduler 与 etcd控制平面概述API Server角色与职责工作原理 etcd角色与职责工作原理 Scheduler角色与职责工作原理 Controller Manager角色与职责工作原理 总结 Kubernetes 控制平面详解 —— 探…...

SSM基础专项复习4——Maven项目管理工具(1)

系列文章 1、SSM基础专项复习1——SSM项目整合-CSDN博客 2、SSM基础专项复习2——Spring 框架&#xff08;1&#xff09;-CSDN博客 3、SSM基础专项复习3——Spring框架&#xff08;2&#xff09;-CSDN博客 文章目录 系列文章 1. Maven 的概念 1.1. 什么是 Maven 1.2. 什…...

使用c#进行串口通信

一、串口通信协议 1.串口通信协议简介 串口通信&#xff08;serial communication&#xff09;是一种设备间非常常用的串行通信方式&#xff0c;大部分电子设备都支持&#xff0c;电子工程师再调试设备时也经常使用该通信方式输出调试信息。讲到某一种通信协议&#xff0c;离…...

Web开发-PHP应用鉴别修复AI算法流量检测PHP.INI通用过滤内置函数

知识点&#xff1a; 1、安全开发-原生PHP-PHP.INI安全 2、安全开发-原生PHP-全局文件&单函数 3、安全开发-原生PHP-流量检测&AI算法 一、演示案例-WEB开发-修复方案-PHP.INI配置 文章参考&#xff1a; https://www.yisu.com/ask/28100386.html https://blog.csdn.net/…...

蓝桥模拟+真题讲解

今天谁一篇文章哈 &#xff01; 由于本篇文章有些的题目只有图片&#xff0c;因此还望各位见谅。 目录 第一题 题目解析 代码原理 代码编写 填空技巧---巧用python 第二题 题目解析 ​编辑 填空技巧---巧用python 第三题 题目链接 题目解析 必备知识 解题技巧 …...

C语言【数据结构】:时间复杂度和空间复杂度.详解

引言 详细介绍什么是时间复杂度和空间复杂度。 前言&#xff1a;为什么要学习时间复杂度和空间复杂度 算法在编写成可执行程序后&#xff0c;运行时需要耗费时间资源和空间(内存)资源。因此衡量一个算法的好坏&#xff0c;一般是从时间和空间两个维度来衡量的&#xff0c;即时…...

大模型的参数数量与学习的知识数量之间

大模型的参数数量与学习的知识数量之间 大模型的参数数量与学习的知识数量之间呈现非线性、条件依赖的复杂关系,其本质是**「表达能力」与「知识编码效率」的动态博弈**。以下从五个维度拆解核心逻辑: 一、参数是知识的「载体容量」,但非唯一决定因素 理论上限:参数数量决…...

基于Python的selenium入门超详细教程(第2章)--单元测试框架unittest

学习路线 自动化测试介绍及学习路线-CSDN博客 ​自动化测试之Web自动化&#xff08;基于pythonselenium&#xff09;-CSDN博客 基于Python的selenium入门超详细教程(第1章)--WebDriver API篇-CSDN博客 目录 前言&#xff1a; 一、单元测试 1. 单元测试的定义 2. 单元测…...

日志、类加载器、XML(配置文件)

目录 一、日志1.日志技术的概述2.日志技术的体系a. Logback 3.日志的级别 二、类加载器1.概述2.类加载时机3.类加载过程3.类加载器的分类4.常用方法 三、XML&#xff08;配置文件&#xff09;1.概述2.XML的基本语法3.XML的文档约束a.DTD约束b.schema约束 4.XML文档解析a.Dom4jb…...

Flutter中的const和final的区别

目录 一、核心区别对比表 二、初始化机制深度解析 1. const 的编译期特性 2. final 的运行时特性 三、内存管理差异 1. const 的内存优化 2. final 的独立内存 四、集合类型的本质区别 1. const 集合的完全不可变性 2. final 集合的引用不可变性 五、在 Flutter 中的…...

DAY34 贪心算法Ⅲ

134. 加油站 - 力扣&#xff08;LeetCode&#xff09; 这种环路问题要记一下。 class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum0;int totalSum0;int start0;for(int i0;i<gas.size();i){curSumga…...

AI大白话(一):5分钟了解AI到底是什么?

&#x1f31f;引言&#xff1a; 在这个信息爆炸的时代&#xff0c;“人工智能”、“AI”、“机器学习”、"深度学习"等词汇频繁出现在我们的生活中。 从手机里的语音助手&#xff0c;到网购平台的个性化推荐&#xff0c;再到最近大火的AI绘画和ChatGPT&#xff0c;人…...

(七)Spring Boot学习——Redis使用

有部分内容是常用的&#xff0c;为了避免每次都查询数据库&#xff0c;将部分数据存入Redis。 一、 下载并安装 Redis Windows 版的 Redis 官方已不再维护&#xff0c;你可以使用 微软提供的 Redis for Windows 版本 或者 使用 WSL&#xff08;Windows Subsystem for Linux&a…...

蓝桥与力扣刷题(蓝桥 字符统计)

题目&#xff1a;给定一个只包含大写字母的字符出 S, 请你输出其中出现次数最多的字符。如果有多个字母均出现了最多次, 按字母表顺序依次输出所有这些字母。 输入格式 一个只包含大写字母的字等串 S. 输出格式 若干个大写字母&#xff0c;代表答案。 样例输入 BABBACAC样…...

AtCoder Beginner Contest 397(ABCDE)

目录 A - Thermometer 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; B - Ticket Gate Log 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; C - Variety Split Easy 翻译&#xff1a; 思路&#xff1a; 实现&#xff1a; D - Cubes 翻译&#xff1a…...

Profinet转Profinet以创新网关模块为核心搭建西门子和欧姆龙PLC稳定通讯架构案例​

你是否有听过PROFINET主站与PROFINET主站之间需要做数据通讯有需求&#xff1f; 例如西门子1500与霍尼韦尔DCS系统两个主站之间的通讯。应用于PROFINET为主站设备还有欧姆龙、基恩士、罗克韦尔、施耐德、GE、ABB等品牌的PLC或DCS、FCS等平台。在生产或智能领域有通讯需求。两头…...