网络-内核是如何与用户进程交互

1、socket的直接创建

在这里插入图片描述

net/socket.cSYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{...retval = sock_create(family, type, protocol, &sock);...
}int sock_create(int family, int type, int protocol, struct socket **res)
{return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
}int __sock_create(struct net *net, int family, int type, int protocol,struct socket **res, int kern)
{int err;struct socket *sock;const struct net_proto_family *pf;...//分配socket对象sock = sock_alloc();...//获取对应协议簇的操作表pf = rcu_dereference(net_families[family]);...//调用协议簇的创建函数,对于AF_INET对应的是inet_create,这个函数在inet_init函数中被初始化err = pf->create(net, sock, protocol, kern);...
}
EXPORT_SYMBOL(__sock_create);

socket在内核中是怎么创建的?
sock_create->__sock_create->inet_create
在__sock_create里首先调用sock_alloc来分配一个struct sock内核对象,接着获取协议簇的操作函数表,调用其create方法。对于AF_INET协议簇来说,执行到的是inet_create函数。

static int inet_create(struct net *net, struct socket *sock, int protocol,int kern)
{struct sock *sk;            // 指向 sock 结构体,表示套接字struct inet_protosw *answer; // 指向 inet_protosw 结构体,表示协议开关struct inet_sock *inet;     // 指向 inet_sock 结构体,表示 IPv4 套接字struct proto *answer_prot;  // 指向 proto 结构体,表示协议操作unsigned char answer_flags;  // 协议开关标志int try_loading_module = 0;  // 尝试加载模块的次数int err;                    // 用于存储函数返回值/** 检查协议号是否在有效范围内。* 如果不在,返回 -EINVAL 错误。*/if (protocol < 0 || protocol >= IPPROTO_MAX)return -EINVAL;/** 初始化套接字状态为未连接。*/sock->state = SS_UNCONNECTED;/** 查找请求的类型/协议对。* 首先尝试在 RCU 读锁保护下查找协议。*/
lookup_protocol:err = -ESOCKTNOSUPPORT; // 设置错误为“不支持的套接字类型”rcu_read_lock();        // 锁定 RCU 读锁/** 遍历协议开关列表,查找匹配的协议。* 如果找到匹配的协议,跳出循环。*/list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {if (protocol == answer->protocol) {if (protocol != IPPROTO_IP)break;} else {if (IPPROTO_IP == protocol) {protocol = answer->protocol;break;}if (IPPROTO_IP == answer->protocol)break;}err = -EPROTONOSUPPORT; // 设置错误为“不支持的协议”}/** 如果没有找到匹配的协议,尝试加载相应的模块。* 如果模块加载后,再次尝试查找协议。*/if (unlikely(err)) {if (try_loading_module < 2) {rcu_read_unlock(); // 解锁 RCU 读锁if (++try_loading_module == 1)request_module("net-pf-%d-proto-%d-type-%d",PF_INET, protocol, sock->type);elserequest_module("net-pf-%d-proto-%d",PF_INET, protocol);goto lookup_protocol; // 重新查找协议} else {goto out_rcu_unlock; // 如果模块加载失败,跳转到错误处理}}/** 检查是否允许非内核进程创建原始套接字。* 如果不允许,返回 -EPERM 错误。*/err = -EPERM;if (sock->type == SOCK_RAW && !kern &&!ns_capable(net->user_ns, CAP_NET_RAW))goto out_rcu_unlock;/** 设置套接字的操作函数和协议。*/sock->ops = answer->ops;//获得tcp_portanswer_prot = answer->prot;answer_flags = answer->flags;rcu_read_unlock(); // 解锁 RCU 读锁/** 分配套接字内存。* 如果分配失败,返回 -ENOBUFS 错误。*/err = -ENOBUFS;sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);if (!sk)goto out;/** 初始化套接字。*/err = 0;if (INET_PROTOSW_REUSE & answer_flags)sk->sk_reuse = SK_CAN_REUSE; // 设置套接字重用标志inet = inet_sk(sk); // 获取 IPv4 套接字结构体inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0; // 设置是否为控制套接字// 初始化其他 IPv4 特定字段inet->nodefrag = 0;if (SOCK_RAW == sock->type) {inet->inet_num = protocol;if (IPPROTO_RAW == protocol)inet->hdrincl = 1;}if (net->ipv4.sysctl_ip_no_pmtu_disc)inet->pmtudisc = IP_PMTUDISC_DONT;elseinet->pmtudisc = IP_PMTUDISC_WANT;inet->inet_id = 0;// 关联套接字和协议sock_init_data(sock, sk);sk->sk_destruct	   = inet_sock_destruct;sk->sk_protocol	   = protocol;sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;inet->uc_ttl	= -1;inet->mc_loop	= 1;inet->mc_ttl	= 1;inet->mc_all	= 1;inet->mc_index	= 0;inet->mc_list	= NULL;inet->rcv_tos	= 0;sk_refcnt_debug_inc(sk); // 增加套接字引用计数// 如果设置了特定协议号,添加到协议哈希链if (inet->inet_num) {err = sk->sk_prot->hash(sk);if (err) {sk_common_release(sk);goto out;}}// 如果协议需要,调用协议的初始化函数if (sk->sk_prot->init) {err = sk->sk_prot->init(sk);if (err) {sk_common_release(sk);goto out;}}// 对于非内核进程,运行 BPF cgroup 套接字挂钩程序if (!kern) {err = BPF_CGROUP_RUN_PROG_INET_SOCK(sk);if (err) {sk_common_release(sk);goto out;}}
out:return err; // 返回结果
out_rcu_unlock:rcu_read_unlock(); // 解锁 RCU 读锁goto out; // 跳转到函数出口
}

在inet_create中根据类型SOCK_STREAM查找到对于TCP定义的操作方法实现集合inet_stream_ops和tcp_port,并把它们分别设置到socket->ops和sock->sk_port上。
在这里插入图片描述

void sock_init_data(struct socket *sock, struct sock *sk)
{/* 初始化套接字通用字段 */sk_init_common(sk);.../* 设置套接字的接收和发送缓冲区大小 */sk->sk_rcvbuf = sysctl_rmem_default; // 接收缓冲区默认大小sk->sk_sndbuf = sysctl_wmem_default; // 发送缓冲区默认大小.../* 设置套接字状态为 TCP_CLOSE(关闭状态) */sk->sk_state = TCP_CLOSE;/* 将套接字与 socket 结构体关联 */sk_set_socket(sk, sock);/* 设置套接字的回调函数 *///当套接字的状态发生变化时,这个回调函数被调用。例如,套接字从监听状态变为已连接状态,或者从已连接状态变为关闭状态。sk->sk_state_change = sock_def_wakeup;//当套接字接收队列中有数据可读时,这个回调函数被调用。它通知套接字数据已经准备好,可以被用户空间读取。sk->sk_data_ready = sock_def_readable;//当套接字的发送队列有足够的空间来接受新的数据时,这个回调函数被调用。它通知套接字发送缓冲区不再满,可以发送更多数据。sk->sk_write_space = sock_def_write_space;//当套接字遇到错误时,这个回调函数被调用。它负责向用户空间报告错误,例如连接重置、数据传输错误等。sk->sk_error_report = sock_def_error_report;//当套接字被销毁时,这个回调函数被调用。它负责执行清理操作,释放套接字占用的资源。sk->sk_destruct = sock_def_destruct;/* 设置套接字的接收和发送超时时间 */sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT;sk->sk_sndtimeo = MAX_SCHEDULE_TIMEOUT;.../* 设置套接字的时间戳 */sk->sk_stamp = SK_DEFAULT_STAMP;}
EXPORT_SYMBOL(sock_init_data);

2、内核IO和用户进程协作之阻塞方式

同步阻塞IO总体流程如下:
在这里插入图片描述

2.1 等待接收消息

recv函数通过strace命令跟踪,可以看到recv会执行recvform调用。
在这里插入图片描述
下面从源代码看看recvfrom是怎么把自己阻塞掉的。

SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,unsigned int, flags, struct sockaddr __user *, addr,int __user *, addr_len)
{...err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);if (unlikely(err))return err;// 根据用户传入的fd找到socket对象sock = sockfd_lookup_light(fd, &err, &fput_needed);...err = sock_recvmsg(sock, &msg, flags);...
}

接下来调用顺序为:
sock_recvmsg==>sock_recvmsg_nosec==>inet_recvmsg==>tcp_recvmsg

int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags)
{int err = security_socket_recvmsg(sock, msg, msg_data_left(msg), flags);return err ?: sock_recvmsg_nosec(sock, msg, flags);
}static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg,int flags)
{return sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);
}int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,int flags)
{struct sock *sk = sock->sk;int addr_len = 0;int err;sock_rps_record_flow(sk);err = sk->sk_prot->recvmsg(sk, msg, size, flags & MSG_DONTWAIT,flags & ~MSG_DONTWAIT, &addr_len);if (err >= 0)msg->msg_namelen = addr_len;return err;
}
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,int flags, int *addr_len)
{...do {...//遍历接收队列接收数据skb_queue_walk(&sk->sk_receive_queue, skb) {...//数据接收完成则返回if (copied >= target && !sk->sk_backlog.tail)break;if (copied >= target) {/* Do not sleep, just process backlog. */release_sock(sk);lock_sock(sk);} else {//没有收到足够的数据,启用sk_wait_data阻塞当前进程,等待数据到来的通知sk_wait_data(sk, &timeo, last);}...} while (len > 0);}
EXPORT_SYMBOL(tcp_recvmsg);

这里可以看到,skb_queue_Walk在访问sock对象下的接收队列,如果没有收到数据或者收到的不够多,那么调用sk_wait_data将当前进程阻塞。
在这里插入图片描述

int sk_wait_data(struct sock *sk, long *timeo, const struct sk_buff *skb)
{//当前进程关联到所定义的等待队列项上,并设置唤醒回调函数DEFINE_WAIT_FUNC(wait, woken_wake_function);int rc;//添加等待队列项到sock的等待队列中add_wait_queue(sk_sleep(sk), &wait);sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk);//通过调用wait_woken让出CPU,然后进入睡眠rc = sk_wait_event(sk, timeo, skb_peek_tail(&sk->sk_receive_queue) != skb, &wait);sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk);remove_wait_queue(sk_sleep(sk), &wait);return rc;
}#define sk_wait_event(__sk, __timeo, __condition, __wait)		\({	int __rc;						\release_sock(__sk);					\__rc = __condition;					\if (!__rc) {						\*(__timeo) = wait_woken(__wait,			\TASK_INTERRUPTIBLE,	\*(__timeo));		\}							\sched_annotate_sleep();					\lock_sock(__sk);					\__rc = __condition;					\__rc;							\})

在这里插入图片描述
当内核收完数据产生就绪事件的时候,会通过回调查找socket等待队列项,进而可以找到回调函数和在等待该socket就绪事件的进程。

2.2 软中断模块

网络包从网卡后怎么接收再交给软中断处理的,这篇文章有描述:添加链接描述
下面从tcp_v4_rcv的源码开始看,总体接收流程如下:
在这里插入图片描述
软中断里收到数据之后,发现是TCP包就会执行tcp_v4_rcv函数。如果是ESTABLISH状态下的数据包,则最终会把数据解析出来放到对应socket的接收队列中,然后调用sk_data_ready来唤醒用户进程。

int tcp_v4_rcv(struct sk_buff *skb)
{...th = (const struct tcphdr *)skb->data;	//获取tcp headeriph = ip_hdr(skb);	//获取ip header...
lookup://根据数据包header中的IP、端口信息查找到对应的socketsk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, &refcounted);	if (!sk)goto no_tcp_socket;process:if (sk->sk_state == TCP_TIME_WAIT)goto do_time_wait;if (sk->sk_state == TCP_NEW_SYN_RECV) {struct request_sock *req = inet_reqsk(sk);struct sock *nsk;sk = req->rsk_listener;if (unlikely(tcp_v4_inbound_md5_hash(sk, skb))) {sk_drops_add(sk, skb);reqsk_put(req);goto discard_it;}if (unlikely(sk->sk_state != TCP_LISTEN)) {inet_csk_reqsk_queue_drop_and_put(sk, req);goto lookup;}/* We own a reference on the listener, increase it again* as we might lose it too soon.*/sock_hold(sk);refcounted = true;nsk = tcp_check_req(sk, skb, req, false);if (!nsk) {reqsk_put(req);goto discard_and_relse;}if (nsk == sk) {reqsk_put(req);} else if (tcp_child_process(sk, nsk, skb)) {tcp_v4_send_reset(nsk, skb);goto discard_and_relse;} else {sock_put(sk);return 0;}}if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) {__NET_INC_STATS(net, LINUX_MIB_TCPMINTTLDROP);goto discard_and_relse;}if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))goto discard_and_relse;if (tcp_v4_inbound_md5_hash(sk, skb))goto discard_and_relse;nf_reset(skb);if (tcp_filter(sk, skb))goto discard_and_relse;th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);skb->dev = NULL;if (sk->sk_state == TCP_LISTEN) {ret = tcp_v4_do_rcv(sk, skb);goto put_and_return;}//socket未被用户锁定if (!sock_owned_by_user(sk)) {if (!tcp_prequeue(sk, skb))ret = tcp_v4_do_rcv(sk, skb);} else if (tcp_add_backlog(sk, skb)) {goto discard_and_relse;}
}

在tcp_v4_do_rcv中,首先找到对应skb对应的socket,然后进入tcp_v4_do_rcv。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{struct sock *rsk;if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */struct dst_entry *dst = sk->sk_rx_dst;sock_rps_save_rxhash(sk, skb);sk_mark_napi_id(sk, skb);if (dst) {if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif ||!dst->ops->check(dst, 0)) {dst_release(dst);sk->sk_rx_dst = NULL;}}//执行连接状态下的数据处理tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len);return 0;}//其他非ESTABLISH状态的数据包处理...
}

在tcp_v4_do_rcv中会调用tcp_rcv_established,在tcp_rcv_established中会调用tcp_queue_rcv把数据添加到接收队列末尾,然后调用sock的sk_data_ready函数指针,这个指针在sock初始化的时候已经被设置成了sock_def_readable,所以会调用到sock_def_readable。

void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{struct tcp_sock *tp = tcp_sk(sk); // 获取 TCP 套接字的特定结构体skb_mstamp_get(&tp->tcp_mstamp); // 获取当前时间戳if (unlikely(!sk->sk_rx_dst)) // 检查接收目的地是否已设置inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb); // 设置接收目的地/** 头部预测。* 代码大致遵循 Van Jacobson 的 "30 instruction TCP receive"。*/tp->rx_opt.saw_tstamp = 0; // 初始化接收选项/* 检查 TCP 头部的有效性 */if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&TCP_SKB_CB(skb)->seq == tp->rcv_nxt &&!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) {int tcp_header_len = tp->tcp_header_len;if (len <= tcp_header_len) {...if (!eaten) {.../* 大量数据传输:接收方 */eaten = tcp_queue_rcv(sk, skb, tcp_header_len, &fragstolen); // 将数据包排入接收队列}tcp_event_data_recv(sk, skb); // 触发数据接收事件if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {/* 处理 ACK */tcp_ack(sk, skb, FLAG_DATA);tcp_data_snd_check(sk); // 检查发送数据if (!inet_csk_ack_scheduled(sk))goto no_ack;}__tcp_ack_snd_check(sk, 0); // 检查 ACK 发送
no_ack:if (eaten)kfree_skb_partial(skb, fragstolen); // 释放部分数据包sk->sk_data_ready(sk); // 通知套接字有数据可读return;}}...
}
EXPORT_SYMBOL(tcp_rcv_established);

在这里插入图片描述

static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen,bool *fragstolen)
{int eaten;struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue);__skb_pull(skb, hdrlen);eaten = (tail &&tcp_try_coalesce(sk, tail, skb, fragstolen)) ? 1 : 0;tcp_rcv_nxt_update(tcp_sk(sk), TCP_SKB_CB(skb)->end_seq);//将接收到的数据添加到尾部if (!eaten) {__skb_queue_tail(&sk->sk_receive_queue, skb);skb_set_owner_r(skb, sk);}return eaten;
}
static void sock_def_readable(struct sock *sk)
{struct socket_wq *wq;rcu_read_lock();wq = rcu_dereference(sk->sk_wq);if (skwq_has_sleeper(wq))//唤醒等待队列的进程wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |POLLRDNORM | POLLRDBAND);sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);rcu_read_unlock();
}#define wake_up_interruptible_sync_poll(x, m)				\__wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m))//nr_exclusive被宏定义成了1,是为了防止惊群
void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{unsigned long flags;int wake_flags = 1; /* XXX WF_SYNC */if (unlikely(!q))return;if (unlikely(nr_exclusive != 1))wake_flags = 0;spin_lock_irqsave(&q->lock, flags);__wake_up_common(q, mode, nr_exclusive, wake_flags, key);spin_unlock_irqrestore(&q->lock, flags);
}static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, int wake_flags, void *key)
{wait_queue_t *curr, *next;list_for_each_entry_safe(curr, next, &q->task_list, task_list) {unsigned flags = curr->flags;//调用进程加入等待队列时注册的回调函数if (curr->func(curr, mode, wake_flags, key) &&(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)break;}
}

在这里插入图片描述
__wake_up_common实现唤醒,该函数调用的传入参数nr_exclusive写死了为1,这里是指即使有多个进程都阻塞在同一个socket上,也只唤醒一个进程。其作用是为了避免“惊群”。在recv注册等待队列项的时候,内核把curr->func设置成了woken_wake_function;在woken_wake_function中调用了woken_wake_function,最终调用到了try_to_wake_up,调用try_to_wake_up传入的参数curr->private就是当时因为等待而被阻塞的进程任务。当这个函数执行完的时候,在socket上等待而被阻塞的进程就被推入可运行队列了。

int woken_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
{wait->flags |= WQ_FLAG_WOKEN;return default_wake_function(wait, mode, sync, key);
}int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,void *key)
{return try_to_wake_up(curr->private, mode, wake_flags);
}

同步阻塞整体流程图如下:
在这里插入图片描述

文章内容参考:《深入理解Linux网络》
Linux版本用的是4.12,书中用的是3.10

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1539714.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

英飞凌PSoC4000T的GPIO中断示例工程

关于PSoC4000T的初步介绍见:英飞凌MCU第五代高性能CAPSENSE技术PSoC4000T_psoc 4000t-CSDN博客 下面这个工程,在modustoolbox中可编译、下载到开发板、debug调试。 编译时会用到mtb_shared这个库: 已经pdl这个periperal driver library库:

imo云办公室 Imo_DownLoadUI.php 任意文件下载漏洞复现

0x01 漏洞描述&#xff1a; imo云办公室由上海易睦网络科技有限公司于2007年创立&#xff0c;总部位于上海&#xff0c;imo云办公室管理运营企业即时通讯平台imo&#xff0c;包括对imo的在线支持&#xff0c;故障处理&#xff0c;客户服务等&#xff0c;对imo进行持续研发&…

利用echarts 显示图片信息

当前有个需求,需要对其进行相关统计,这里我们采用jquery3.6 与echarts.js 做相关图表,不解释,直接上代码吧 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"wid…

IM项目-----文件管理子服务

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、搭建思想二、服务器搭建1.继承fileService类&#xff0c;重写业务方法服务器类设计建造者类 前言 文件管理子服务&#xff0c;主要用于管理用户的头像&…

两台虚拟机之分布式部署

Apache2 和 PHP 安装 在虚拟机1上执行以下步骤: 更新系统包列表: sudo apt update安装 Apache2: sudo apt install apache2 -y安装 PHP 及其扩展: sudo apt install php libapache2-mod-php php-mysql配置Apache和PHP sudo nano /etc/apache2/mods-enabled/dir.conf#…

Netty笔记10-Netty参数调优

文章目录 一、CONNECT_TIMEOUT_MILLISCONNECT_TIMEOUT_MILLIS设置为1秒超时CONNECT_TIMEOUT_MILLIS设置为5秒超时注意事项 二、SO_BACKLOG代码示例注意事项 三、ulimit -n(文件描述符)设置文件描述符限制在注意事项 四、TCP_NODELAY使用 TCP_NODELAY 的场景注意事项 五、SO_SND…

【Git原理与使用】版本管理与分支管理(1)

目录 一、基本操作 1、初识Git 2、Git安装[Linux-centos] 3、Git安装[ Linnx-ubuntu] 4、创建git本地仓库 5、配置Git 6、认识工作区、暂存区、版本库 7、添加文件 8、查看历史提交记录 9、查看.git文件目录结构 10、查看版本库对象的内容 11、小结&#xff08;在本地的.git仓库…

VS code EXPLORER 中不显示指定文件及文件夹设置(如.pyc, __pycache__, .vscode 文件)

VS code EXPLORER 中不显示指定文件及文件夹设置 引言正文方法1打开方式1打开方式2 方法2 引言 VS code 号称地表最强轻量级编译器&#xff0c;其最大的优势在于用户可以根据自己的需求下载适合自己的 extension。从而定制个性化的编译器。然而&#xff0c;本人今天遇到了一个…

鹰眼降尘监测

鹰眼系统检测原理根据应用领域的不同而有所差异。以下是朗观视觉小编整理的几个主要应用领域及其检测原理的概述&#xff1a; 1. 体检领域 在体检领域&#xff0c;鹰眼通常指的是一种全身扫描仪器&#xff0c;如法国鹰眼检测全身扫描系统。该系统根据生理反馈信号的单向导通&a…

基于ssm的快餐店点餐系统设计与实现

需要项目源码请联系我&#xff0c;目前有各类成品 毕设 javaweb ssh ssm springboot等等项目框架&#xff0c;源码丰富。 专业团队&#xff0c;咨询就送开题报告&#xff0c;活动限时免费&#xff0c;有需要的朋友可以来留言咨询。 一、摘要 进入二十一世纪以来&#xff0c;计…

深度学习自编码器 - 收缩自编码器(CAE)篇

序言 在深度学习的浪潮中&#xff0c;收缩自编码器&#xff08; Compressive Autoencoder, CAE \text{Compressive Autoencoder, CAE} Compressive Autoencoder, CAE&#xff09;作为自编码器的一种高级形式&#xff0c;正逐步崭露头角。收缩自编码器在保留自编码器核心功能—…

QT For Android开发-打开PPT文件

一、前言 需求&#xff1a; Qt开发Android程序过程中&#xff0c;点击按钮就打开一个PPT文件。 Qt在Windows上要打开PPT文件或者其他文件很容易。可以使用QDesktopServices打开文件&#xff0c;非常方便。QDesktopServices提供了静态接口调用系统级别的功能。 这里用的QDesk…

高效开发,从暗藏玄机的文件系统开始

4G-Cat.1模组的文件系统关乎数据传输速度、存储效率&#xff0c;以及数据安全性等等诸多因素&#xff0c;在应用开发中极为重要。 本期&#xff0c;我们来学习合宙Air201的实用示例——文件系统的使用 Air201文件系统的使用 合宙Air201资产定位模组——是一个集成超低功耗4G通…

深入探索迭代器模式的原理与应用

&#x1f3af; 设计模式专栏&#xff0c;持续更新中 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 迭代器模式 &#x1f4bb; 迭代器模式 (Iterator Pattern) 是一种行为设计模…

saltstack高级用法

一、saltstack的高级用法 一、job管理 1、job简介 Jid&#xff1a;job id&#xff0c;格式为%Y%m%d%H%M%S%fmaster在下发指令消息时&#xff0c;会附带上产生的Jid&#xff0c;minion在接收到指令开始执行时&#xff0c;会在本地的cachedir&#xff08;默认是/var/cache/salt/…

力扣 2824.统计和小于目标的下标对数目

文章目录 题目介绍解法 题目介绍 解法 题目相当于从数组中选两个数&#xff0c;我们只关心这两个数的和是否小于 target&#xff0c;由于 abba&#xff0c;无论如何排列数组元素&#xff0c;都不会影响加法的结果&#xff0c;所以排序不影响结果的数量。 排序后&#xff1a; …

CDH Hive集群的create/drop慢问题,在200s 多一点处理分析

现象&#xff1a; CREATE TABLE test911 (SN String,PN_CODE String); Total time spent in this metastore function was greater than 1000ms : createTable_(Table, )200091 Hive集群的 create/drop 操作时间基本都稳定在 200s 多一点。 分析&#xff1a; HMS会实时向Sentr…

实战OpenCV之图像阈值处理

基础入门 图像阈值处理是一种二值化技术&#xff0c;它基于预设的阈值&#xff0c;可以将图像中的像素分为两大类&#xff1a;一大类是背景&#xff0c;另一大类是前景或目标对象。这个过程涉及将图像中的每个像素值与阈值进行比较&#xff0c;并根据比较结果决定保留原始值还是…

jmeter得到的文档数据处理

通过前面jmeter得到的输出文档&#xff0c;这里是txt文档&#xff0c;里面包含了很多条数据&#xff0c;每条数据的结构如下&#xff1a; 【request】 uuid&#xff1a;xxxxxxx timestamp&#xff1a;xxxxxxxx No.x question&#xff1a;xxxxxxx 【response】 code&#…

防火墙配置变更管理

在任何组织中&#xff0c;当涉及到网络安全时&#xff0c;频繁地更换防火墙是必要的&#xff0c;实施简化的防火墙更改管理策略模板可以减少管理时间&#xff0c;还可以减少每次变更引入新的安全性或合规性问题的可能性。典型的防火墙变更管理流程将包括以下步骤&#xff1a; …