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

深入理解Linux网络随笔(一):内核是如何接收网络包的(下篇)

3、接收网络数据

3.1.1硬中断处理

数据帧从网线到达网卡时候,首先到达网卡的接收队列,网卡会在初始化时分配给自己的RingBuffer中寻找可用内存位置,寻找成功后将数据帧DMA到网卡关联的内存里,DMA操作完成后,网卡会向CPU发起一个硬中断,通知CPU有数据到达。

在这里插入图片描述

启动网卡到硬中断注册处理函数调用流程ign_open-->igb_request_irq-->igb_request_msix-->igb_msix_ring

static irqreturn_t igb_msix_ring(int irq, void *data)
{struct igb_q_vector *q_vector = data;/* Write the ITR value calculated from the previous interrupt. *///记录硬件中断频率igb_write_itr(q_vector);//调度NAPI机制napi_schedule(&q_vector->napi);return IRQ_HANDLED;
}

napi_schedule将q_vector关联的NAPI结构添加于调度队列,函数调用关系napi_schedule-->__napi_schedule-->____napi_schedule

static inline void ____napi_schedule(struct softnet_data *sd,struct napi_struct *napi)
{......list_add_tail(&napi->poll_list, &sd->poll_list);__raise_softirq_irqoff(NET_RX_SOFTIRQ);
}

基于软中断的NAPI处理调用list_add_tail修改Per-CPU变量的softnet_datapoll_list,将驱动napi_struct传入的poll_list添加于软中断的poll_list,触发NET_RX_SOFTIRQ类型软中断。

void __raise_softirq_irqoff(unsigned int nr)
{//禁中断lockdep_assert_irqs_disabled();//追踪软中断trace_softirq_raise(nr);//触发or_softirq_pending(1UL << nr);
}
#define or_softirq_pending(x)  (S390_lowcore.softirq_pending |= (x))

通过or操作符将软中断nr对应的位设置为1,调用or_softirq_pending将该标志位添加于软中断挂起队列,触发软中断。

3.1.2软中断处理

前文分析软中断处理通过ksfortirq内核线程处理,会调用两个函数ksoftirqd_should_runrun_ksoftirqd,均调用local_softirq_pending进行处理。

在这里插入图片描述

#define local_softirq_pending() (S390_lowcore.softirq_pending)
static int ksoftirqd_should_run(unsigned int cpu)
{return local_softirq_pending();
}
static void run_ksoftirqd(unsigned int cpu)
{ksoftirqd_run_begin();if (local_softirq_pending()) {/** We can safely run softirq on inline stack, as we are not deep* in the task stack here.*/__do_softirq();ksoftirqd_run_end();cond_resched();return;}ksoftirqd_run_end();
}

硬中断处理是由硬件中断服务例程(ISR)触发的,调用local_softirq_pending标记软中断挂起状态,真正的中断处理由ksfortirq内核线程处理,函数调用逻辑run_ksoftirqd-->__do_softirq

asmlinkage __visible void __softirq_entry __do_softirq(void)
{.....// 获取当前 CPU 上待处理的软中断类型的掩码pending = local_softirq_pending();// 遍历所有待处理的软中断while ((softirq_bit = ffs(pending))) {unsigned int vec_nr;int prev_count;// 将指针 'h' 移动到当前待处理软中断类型的处理函数h += softirq_bit - 1;// 计算当前软中断处理函数在软中断处理数组中的索引vec_nr = h - softirq_vec;  // 获取当前任务的预占用计数prev_count = preempt_count();// 统计当前软中断类型的处理次数kstat_incr_softirqs_this_cpu(vec_nr);// 调用 trace 函数跟踪软中断的进入trace_softirq_entry(vec_nr);// 执行软中断的处理函数h->action(h);// 调用 trace 函数跟踪软中断的退出trace_softirq_exit(vec_nr);......// 处理下一个软中断类型h++;// 右移 pending 位图,检查下一个待处理的软中断pending >>= softirq_bit;}
}

__do_softirq根据传入的软中断类型处理所有挂起的软中断,通过h->action(h)执行具体的软中断处理函数。硬中断中的设置软中断标记,和ksoftirqd中的判断是否有软中断到达,都是基于smp_processor_id()的。只要硬中断在哪个CPU上被响应,那么软中断也是在这个CPU上处理的,针对Linux软中断消耗集中一个核现象,方法:调整硬中断CPU亲和性,硬中断打散于不同核上。

设备初始化时调用open_softirq(NET_RX_SOFTIRQ, net_rx_action),将网络接收软中断 NET_RX_SOFTIRQ 绑定到 net_rx_action 处理函数,收到软中断类型NET_RX_SOFTIRQ会调用net_rx_action接收软中断,处理网络数据包。

static __latent_entropy void net_rx_action(struct softirq_action *h)
{struct softnet_data *sd = this_cpu_ptr(&softnet_data);  // 获取当前 CPU 的软中断数据unsigned long time_limit = jiffies +usecs_to_jiffies(READ_ONCE(netdev_budget_usecs));  // 计算软中断的超时时间int budget = READ_ONCE(netdev_budget);  // 获取软中断的处理预算(每次允许处理的最大数据包数量)LIST_HEAD(list);  // 创建链表 list,用于存储要处理的 napi 结构体LIST_HEAD(repoll);  // 创建链表 repoll,用于存储需要重新投递的 napi 结构体local_irq_disable();  // 关闭CPU硬中断list_splice_init(&sd->poll_list, &list);  // 将当前 CPU 上的 poll_list 中的元素移动到 list 中local_irq_enable();  // 重新启用本地中断for (;;) {struct napi_struct *n;skb_defer_free_flush(sd);  // 清理延迟释放的数据包if (list_empty(&list)) {  // 如果没有要处理的 napi 结构体if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))  // 如果没有需要处理的 RPS IPI 和 repoll 列表为空goto end;  // 结束处理break;  // 如果有需要的工作,继续处理}n = list_first_entry(&list, struct napi_struct, poll_list);  // 获取待处理的第一个 napi 结构体budget -= napi_poll(n, &repoll);  // 调用 napi_poll 处理数据包,更新剩余预算/* 如果软中断窗口已耗尽,则退出处理* 允许最多运行 2 个 jiffies,这会允许平均延迟为 1.5/HZ*/if (unlikely(budget <= 0 ||time_after_eq(jiffies, time_limit))) {sd->time_squeeze++;  // 记录时间压缩(即软中断处理超时)break;  // 退出循环}}local_irq_disable();  // 禁用本地中断// 将 repoll 和 list 的元素合并到 sd->poll_list 中list_splice_tail_init(&sd->poll_list, &list);list_splice_tail(&repoll, &list);list_splice(&list, &sd->poll_list);// 如果 poll_list 中仍有元素,重新唤起软中断if (!list_empty(&sd->poll_list))__raise_softirq_irqoff(NET_RX_SOFTIRQ);net_rps_action_and_irq_enable(sd);  // 处理 RPS(Receive Packet Steering)和启用中断
end:;
}

遍历所有待处理的网络接收软中断,核心获取当前CPU软中断数据softnet_datalist_first_entry遍历poll_list,调用 napi_poll 处理数据包,上文中分析了NAPI机制的poll函数是igb_poll

static int igb_poll(struct napi_struct *napi, int budget)
{
.....//TX发送队列if (q_vector->tx.ring)clean_complete = igb_clean_tx_irq(q_vector, budget);//RX接收队列if (q_vector->rx.ring) {int cleaned = igb_clean_rx_irq(q_vector, budget);// 累计本次轮询处理的数据包数量work_done += cleaned;if (cleaned >= budget)clean_complete = false;}
......return work_done;
}

igb_polligb 网卡驱动中 NAPI 轮询的核心函数,负责清理发送和接收队列的中断。核心处理逻辑igb_clean_rx_irqigb_clean_tx_irq

static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget)
{......rx_desc = IGB_RX_DESC(rx_ring, rx_ring->next_to_clean);  // 获取当前的接收描述符size = le16_to_cpu(rx_desc->wb.upper.length);  // 获取数据包的大小rx_buffer = igb_get_rx_buffer(rx_ring, size, &rx_buf_pgcnt);  // 获取接收缓冲区pktbuf = page_address(rx_buffer->page) + rx_buffer->page_offset;  // 获取数据包的缓冲区地址if (!skb) {unsigned char *hard_start = pktbuf - igb_rx_offset(rx_ring);  // 获取数据包的起始地址unsigned int offset = pkt_offset + igb_rx_offset(rx_ring);  // 计算数据包的偏移xdp_prepare_buff(&xdp, hard_start, offset, size, true);  // 为 XDP 准备数据包xdp_buff_clear_frags_flag(&xdp);  // 清除 XDP 的分段标志skb = igb_run_xdp(adapter, rx_ring, &xdp);  // 运行 XDP 处理,构建 skb
}if (IS_ERR(skb)) {unsigned int xdp_res = -PTR_ERR(skb);if (xdp_res & (IGB_XDP_TX | IGB_XDP_REDIR)) {  // 如果是 XDP 传输或重定向xdp_xmit |= xdp_res;igb_rx_buffer_flip(rx_ring, rx_buffer, size);  // 切换缓冲区} else {rx_buffer->pagecnt_bias++;  // 增加页面计数偏移}total_packets++;  // 增加数据包计数total_bytes += size;  // 增加字节数
}else if (skb) {igb_add_rx_frag(rx_ring, rx_buffer, skb, size);  // 将接收到的数据包添加到 skb 中
}napi_gro_receive(&q_vector->napi, skb);  // 将 skb 传递给 NAPI 进行进一步处理igb_put_rx_buffer(rx_ring, rx_buffer, rx_buf_pgcnt);  // 释放接收缓冲区if (cleaned_count)igb_alloc_rx_buffers(rx_ring, cleaned_count);......
}

igb_clean_tx_irq核心将数据帧从RingBuffer中摘下,igb_alloc_rx_buffers重新申请新的skb再重新挂起,NAPI机制下一步处理调用napi_gro_receive

gro_result_t napi_gro_receive(struct napi_struct *napi, struct sk_buff *skb)
{......ret = napi_skb_finish(napi, skb, dev_gro_receive(napi, skb));......return ret;
}
EXPORT_SYMBOL(napi_gro_receive);

napi_gro_receive用于网卡GRO特性,合并多个小的数据包(通常是同一流的 TCP 数据包)为一个较大的数据包,从而减少协议栈的处理开销。调用napi_skb_finish完成GRO处理。

static gro_result_t napi_skb_finish(struct napi_struct *napi,struct sk_buff *skb,gro_result_t ret)
{switch (ret) {case GRO_NORMAL:gro_normal_one(napi, skb, 1);break;case GRO_MERGED_FREE:if (NAPI_GRO_CB(skb)->free == NAPI_GRO_FREE_STOLEN_HEAD)napi_skb_free_stolen_head(skb);else if (skb->fclone != SKB_FCLONE_UNAVAILABLE)__kfree_skb(skb);else__kfree_skb_defer(skb);break;case GRO_HELD:case GRO_MERGED:case GRO_CONSUMED:break;}return ret;
}

正常数据包处理GRO_NORMAL,需要合并的数据包处理GRO_MERGED_FREE,根据不同的方式选择不同的释放方式,函数调用逻辑gro_normal_one-->gro_normal_list-->netif_receive_skb_list_internal-->__netif_receive_skb_list-->__netif_receive_skb_core,数据包发送于协议栈。

3.1.3网络协议栈处理
static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,struct packet_type **ppt_prev)
{......// 遍历全局的协议类型链表并传递 skb,tcpdump入口list_for_each_entry_rcu(ptype, &ptype_all, list) {if (pt_prev)ret = deliver_skb(skb, pt_prev, orig_dev);pt_prev = ptype;}type = skb->protocol;// 如果没有精确匹配,处理协议类型if (likely(!deliver_exact)) {deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,&ptype_base[ntohs(type) & PTYPE_HASH_MASK]);
}// 传递到设备特定协议链表deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,&orig_dev->ptype_specific);// 如果 skb 的设备不是原始设备,进行协议处理if (unlikely(skb->dev != orig_dev)) {deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,&skb->dev->ptype_specific);}  
}

ptype_all 是一个全局链表,包含了所有已注册的协议类型及其处理回调,在 __netif_receive_skb_core 函数中,首先会遍历这个链表,依次处理每个协议类型,并调用与协议相关的处理函数,ptype_base 是一个基于协议类型的哈希表,它包含了协议类型(如 IPv4、IPv6、TCP 等)对应的特定处理函数,当数据包的协议类型与哈希表中的某个条目匹配时,数据包会被传递到该处理函数。例如,ip_rcv 的地址通常是保存在 ptype_base 哈希表中的。

static inline int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev,struct net_device *orig_dev)
{......//调用协议处理函数 (pt_prev->func) 并将 skb 传递给它return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}
static struct packet_type ip_packet_type __read_mostly = {.type = cpu_to_be16(ETH_P_IP),.func = ip_rcv,
};

deliver_skb 将接收到的 skb传递给协议处理函数。从 packet_type 结构体中获取 func 字段,并将 skb 数据包传递给该函数进行处理。这里的 pt_prev->func 是一个协议回调函数,指向处理该协议类型数据包的函数(例如,对于 IPv4 数据包,func 指向 ip_rcv 函数)。

3.1.4IP层处理

数据包经过协议栈处理后会被传递到IP层进行处理,收包方向IP层入口函数ip_rcv

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,struct net_device *orig_dev)
{struct net *net = dev_net(dev);skb = ip_rcv_core(skb, net);if (skb == NULL)return NET_RX_DROP;return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,net, NULL, skb, dev, NULL,ip_rcv_finish);
}

接收到的数据包会经过ip_rcv_core进行基本处理,例如对数据包进行有效性检查、协议解析等,处理成功会触发IPV4数据包Netfilter钩子链,在 NF_INET_PRE_ROUTING 钩子处插入数据包处理,NF_INET_PRE_ROUTING 是所有接收数据包到达的第一个 hook 触发点,在路由判断之前执行,对应的回调函数ip_rcv_finish

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{struct net_device *dev = skb->dev;int ret;/* if ingress device is enslaved to an L3 master device pass the* skb to its handler for processing*/skb = l3mdev_ip_rcv(skb);if (!skb)return NET_RX_SUCCESS;ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);if (ret != NET_RX_DROP)ret = dst_input(skb);return ret;
}

ip_rcv_finish负责接收和处理通过 IPv4 协议栈传输的网络数据包,核心的IP数据包处理函数调用dst_input传递数据包。

static inline int dst_input(struct sk_buff *skb)
{return INDIRECT_CALL_INET(skb_dst(skb)->input,ip6_input, ip_local_deliver, skb);
}

基于路由类型skb_dst(skb)选择对应的处理函数,通过INDIRECT_CALL_INET宏选择IPV4/IPV6协议数据包处理函数,IPV4选择调用ip_local_deliver

int ip_local_deliver(struct sk_buff *skb)
{/**	Reassemble IP fragments.*/struct net *net = dev_net(skb->dev);if (ip_is_fragment(ip_hdr(skb))) {if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))return 0;}return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,net, NULL, skb, skb->dev, NULL,ip_local_deliver_finish);
}
EXPORT_SYMBOL(ip_local_deliver);

ip_local_deliver处理本地IPV4数据包,接收一个网络数据包skb,调用ip_is_fragment检查是否需要进行IP分片重组,对不分片/已重组的数据包调用NF_HOOKNF_INET_LOCAL_IN处理本地接收到的数据包(并不是经过路由转发的数据包),对应的处理函数ip_local_deliver_finish

参考资料:《深入理解Linux网络》

相关文章:

深入理解Linux网络随笔(一):内核是如何接收网络包的(下篇)

3、接收网络数据 3.1.1硬中断处理 数据帧从网线到达网卡时候&#xff0c;首先到达网卡的接收队列&#xff0c;网卡会在初始化时分配给自己的RingBuffer中寻找可用内存位置&#xff0c;寻找成功后将数据帧DMA到网卡关联的内存里&#xff0c;DMA操作完成后&#xff0c;网卡会向…...

《只狼》运行时提示“mfc140u.dll文件缺失”是什么原因?要怎么解决?

《只狼》运行时提示“mfc140u.dll文件缺失”是什么原因&#xff1f;要怎么解决&#xff1f; 宝子们&#xff0c;是不是在玩《只狼》的时候&#xff0c;突然弹出一个提示&#xff1a;“找不到mfc140u.dll文件”&#xff1f;这可真是让人着急上火&#xff01;别慌&#xff0c;今…...

SSM开发(十二) mybatis的动态SQL

目录 一、为什么需要动态SQL? Mybatis 动态 sql 是做什么的? 二、多种动态 SQL 元素 三、示例 1、model定义 2、数据库定义 3、UserMapper接口及UserMapper.xml内容定义 if标签 choose/when/otherwise 标签 foreach标签 trim 标签 四、动态SQL注意 一、为什么需…...

基于LVS负载均衡练习

对比 LVS 负载均衡群集的 NAT 模式和 DR 模式&#xff0c;比较其各自的优势。 NAT模式&#xff0c;全称是网络地址转换模式。NAT模式下&#xff0c;负载均衡器&#xff08;Director&#xff09;会修改请求和响应的IP地址。客户端的请求先到达Director&#xff0c;Director将请…...

FreeRTOS低功耗总结

前言 Cortex-M核的MCU一般支持以下三种低功耗方式&#xff1a; ● 睡眠(Sleep)模式 ● 停止(Stop)模式 ● 待机(Standby)模式 睡眠模式 进入睡眠模式有两种指令&#xff1a;WFI(等待中断)和WFE(等待事件)&#xff0c; WFI进入睡眠模式后&#xff0c;任意中断都可唤醒。 WFE进…...

【IC】AI处理器核心--第二部分 用于处理 DNN 的硬件设计

第 II 部分 用于处理 DNN 的硬件设计 第 3 章 关键指标和设计目标 在过去的几年里&#xff0c;对 DNN 的高效处理进行了大量研究。因此&#xff0c;讨论在比较和评估不同设计和拟议技术的优缺点时应考虑的关键指标非常重要&#xff0c;这些指标应纳入设计考虑中。虽然效率通常…...

React历代主要更新

一、React 16之前更新 React Fiber是16版本之后的一种更新机制&#xff0c;使用链表取代了树&#xff0c;是一种fiber数据结构&#xff0c;其有三个指针&#xff0c;分别指向了父节点、子节点、兄弟节点&#xff0c;当中断的时候会记录下当前的节点&#xff0c;然后继续更新&a…...

常用查找算法整理(顺序查找、二分查找、哈希查找、二叉排序树查找、平衡二叉树查找、红黑树查找、B树和B+树查找、分块查找)

常用的查找算法&#xff1a; 顺序查找&#xff1a;最简单的查找算法&#xff0c;适用于无序或数据量小的情况&#xff0c;逐个元素比较查找目标值。二分查找&#xff1a;要求数据有序&#xff0c;通过不断比较中间元素与目标值&#xff0c;将查找范围缩小一半&#xff0c;效率…...

Linux性能分析工具Trace使用

Linux Trace是⼀种⽤于抓取和分析系统运⾏时信息的⼯具。允许开发⼈员跟踪和分析系统的各种活动,以便深⼊了解系统的性能、⾏为和故障。下⾯是关于Linux Trace数据抓取的说明: 1. 数据抓取范围:Linux Trace可以抓取各种级别的数据,包括系统级别、进程级别和内核级别的数据。…...

2024华为OD机试真题-简单的自动曝光(C++)-E卷B卷-100分

2024华为OD机试最新E卷题库-(C卷+D卷+E卷)-(JAVA、Python、C++) 目录 题目描述 输入 输出 备注 示例1 示例2 解题思路 考点 代码 c++ 题目描述 一个图像有n个像素点,存储在一个长度为n的数组img里,每个像素点的取值范围[0,255]的正整数。 请你给图像每个像素点值…...

【python】向Jira测试计划下,附件中增加html测试报告

【python】连接Jira获取token以及jira对象 # 往 jira 测试计划下面&#xff0c;上传测试结果html def put_jira_file(plain_id):# 配置连接jiraconn ConnJira()jira conn.jira_login()[2]path jira.issue(O45- plain_id)attachments_dir os.path.abspath(..) \\test_API…...

STM32自学记录(九)

STM32自学记录 文章目录 STM32自学记录前言一、DMA杂记二、实验1.学习视频2.复现代码 总结 前言 DMA 一、DMA杂记 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输&#xff0c;无须CPU干预&…...

【C++】C++-教师信息管理系统(含源码+数据文件)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 【C】C教师信息管理系统&#xff08;含源码&#x…...

Java Swing-5.jar 使用 jpackage 打包成 windows 可安装应用(exe,msi,免安装版exe)

环境 jdk17 (jdk14 以后自带将jar 打安装包工具 jpackage&#xff0c;版本从1.8调整到17) Maven&#xff1a;3.2.5 效果 对比 exe4j :免费版在启动的时候总是先弹出一个弹框&#xff0c;告诉用户你在用他们的免费版Launch4j:无法把jre环境打到exe文件中&#xff0c;用户需要单独…...

ADC入门准备(十):信号与系统知识回顾

4.7系统函数零极点分布决定时域特性 4.7.1 H(s)极点分布与h(t)的对应图解 4.7.2 H(s)、E(s&#xff09;极点分布与自由响应、强迫响应特征的对应 4.8 H(s)零极点分布决定频域特性 4.8.1 s平面几何分析法 4.8.2 高通滤波器的频率特性 4.8.3 低通滤波器的频率特性 4.9 二阶谐振系…...

wx060基于springboot+vue+uniapp的宿舍报修系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…...

Spring Boot 配置 Mybatis 读写分离

JPA 的读写分离配置不能应用在 Mybatis 上, 所以 Mybatis 要单独处理 为了不影响原有代码, 使用了增加拦截器的方式, 在拦截器里根据 SQL 的 CRUD 来路由到不同的数据源 需要单独增加Mybatis的配置 Configuration public class MyBatisConfig {Beanpublic SqlSessionFactory…...

CCF-GESP 等级考试 2024年9月认证C++二级真题解析

2024年9月真题 一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 正确答案&#xff1a;A 考察知识点&#xff1a;计算机存储 解析&#xff1a;磁心存储元件是早期计算机中用于存储数据的部件&#xff0c;它和现代计算机中的内存功能类似&#xff0c;都是用于临时…...

第二天:工具的使用

每天上午9点左右更新一到两篇文章到专栏《Python爬虫训练营》中&#xff0c;对于爬虫有兴趣的伙伴可以订阅专栏一起学习&#xff0c;完全免费。 键盘为桨&#xff0c;代码作帆。这趟为期30天左右的Python爬虫特训即将启航&#xff0c;每日解锁新海域&#xff1a;从Requests库的…...

HarmonyOS:使用List实现分组列表(包含粘性标题)

一、支持分组列表 在列表中支持数据的分组展示&#xff0c;可以使列表显示结构清晰&#xff0c;查找方便&#xff0c;从而提高使用效率。分组列表在实际应用中十分常见&#xff0c;如下图所示联系人列表。 联系人分组列表 在List组件中使用ListItemGroup对项目进行分组&#…...

Django5的新特征

Django是一个用Python编写的高级Web框架&#xff0c;它的目标是让开发人员能够快速高效地构建复杂的Web应用程序。自从2008年首次发布以来&#xff0c;Django已经成为开源Web框架中的佼佼者&#xff0c;被广泛应用于各种规模的项目中。Django 提供了一套强大且全面的工具&#…...

web自动化笔记(二)

文章目录 一、参数化测试1.pytest命令2.实现参数化测试3.填写地址测试4.生成Allure测试报告5.关键字驱动 二、案例1.实现后台登录1.1登录1.2.处理验证码1.3.封装识别验证码函数 2.通过cookie保持登录2.1给页面添加cookie2.2获取页面的cookie2.3自动化获取cookie 三、excel进行数…...

青少年编程与数学 02-009 Django 5 Web 编程 12课题、表单处理

青少年编程与数学 02-009 Django 5 Web 编程 12课题、表单处理 一、表单1. 表单类的定义示例&#xff1a;普通表单示例&#xff1a;模型表单 2. 字段类型3. 验证4. 渲染5. 表单处理示例&#xff1a;视图中的表单处理6. 自定义表单 二、验证1. 字段级验证示例2. 表单级验证示例3…...

JVM类加载和垃圾回收(详细)

文章目录 JVM介绍JDK/JRE/JVM的关系 内存结构堆程序计数器虚拟机栈本地方法栈本地内存 类文件字节码文件结构 类加载类的生命周期加载类加载器双亲委派模型 链接初始化类卸载 垃圾回收堆空间的基本结构内存分配和回收原则死亡对象判断方法垃圾收集算法垃圾收集器 JVM 介绍 JD…...

秘密信息嵌入到RGB通道的方式:分段嵌or完整嵌入各通道

目录 1. 将秘密信息分为三部分的理由 &#xff08;1&#xff09;均匀分布负载 &#xff08;2&#xff09;提高鲁棒性 &#xff08;3&#xff09;容量分配 2. 不将秘密信息分为三部分的情况 &#xff08;1&#xff09;嵌入容量 &#xff08;2&#xff09;视觉质量 &#…...

基于Flask的影视剧热度数据可视化分析系统的设计与实现

【FLask】基于Flask的影视剧热度数据可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 随着互联网技术的飞速发展&#xff0c;影视剧行业的数据量呈爆炸性增长&#x…...

Docker Desktop如何恢复出厂设置

在测试dify、ragfow等几个模型过程中&#xff0c;各种拉镜像建容器&#xff0c;导致错误提示“AssertionError(Can t access Redis. Please check the Redis status.)”&#xff0c;两个模型都无法使用&#xff0c;如何清空重建&#xff1f;请参照下面操作&#xff1a; 1、Win…...

Go语言协程Goroutine高级用法(一)

什么协程 在Go语言中&#xff0c;协程就是一种轻量的线程&#xff0c;是并发编程的单元&#xff0c;由Go来管理&#xff0c;所以在GO层面的协程会更加的轻量、高效、开销更小&#xff0c;并且更容易实现并发编程。 轻量级线程 Go语言中协程&#xff08;线程&#xff09;与传…...

Android Studio:键值对存储sharedPreferences

一、了解 SharedPreferences SharedPreferences是Android的一个轻量级存储工具&#xff0c;它采用的存储结构是Key-Value的键值对方式&#xff0c;类似于Java的Properties&#xff0c;二者都是把Key-Value的键值对保存在配置文件中。不同的是&#xff0c;Properties的文件内容形…...

网络安全-攻击路径

以下是互联网场景下常见的攻击路径分类及详细说明&#xff0c;以分层结构呈现&#xff1a; 一、网络层攻击路径 DDoS攻击 原理&#xff1a;通过僵尸网络发起海量请求淹没目标服务器示例&#xff1a;SYN Flood攻击、HTTP洪泛攻击影响&#xff1a;服务不可用&#xff0c;带宽资源…...