【Linux内核深度解析】TCP协议栈之tcp_recvmsg
tcp_recvmsg
是 Linux 内核中用于处理 TCP 套接字接收数据的核心函数。它的主要任务是从接收队列中读取数据并将其复制到用户空间。
函数原型
int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len);
参数说明
iocb
: IO 控制块,包含与 IO 操作相关的信息。sk
: 指向struct sock
的指针,表示当前套接字。msg
: 指向struct msghdr
的指针,包含接收数据的缓冲区信息。len
: 要接收的最大字节数。nonblock
: 非阻塞标志。flags
: 控制接收行为的标志。addr_len
: 传出参数,用于返回地址长度。
处理流程
锁定套接字
通过调用 lock_sock(sk) 来确保对套接字的独占访问,防止其他线程同时访问。
/** 通过 tcp_sk 宏将传入的 struct sock *sk 转换为 struct tcp_sock *tp 类型,获取与该套接字相关的 TCP 结构体。** 这使得后续的操作能够直接访问 TCP 特有的字段 **/struct tcp_sock *tp = tcp_sk(sk);int copied = 0; //用于记录已经成功复制到用户缓冲区的字节数u32 peek_seq; //用于存储在使用 MSG_PEEK 标志时的序列号u32 *seq; //指向当前序列号的指针,用于跟踪已复制的数据unsigned long used; //用于记录从接收缓冲区中提取的数据长度int err;int target; /* Read at least this many bytes(期望读取的最小字节数) */long timeo; //用于存储接收超时时间。struct task_struct *user_recv = NULL; //指向正在接收数据的用户任务结构体,初始化为 NULLint copied_early = 0; //标记是否在早期阶段就复制了数据struct sk_buff *skb; //指向当前处理的套接字缓冲区(socket buffer)u32 urg_hole = 0; //用于处理紧急数据时可能出现的数据缺口lock_sock(sk);
状态检查
err = -ENOTCONN;if (sk->sk_state == TCP_LISTEN)goto out;
// sk_rcvtimeo 通常表示套接字在接收数据时的超时时间,单位为毫秒。
static inline long sock_rcvtimeo(const struct sock *sk, int noblock)
{return noblock ? 0 : sk->sk_rcvtimeo;
}
检查套接字状态,如果处于监听状态(TCP_LISTEN
),则返回错误,因为在此状态下不能接收数据。
这一设计的目的主要是为了处理不同的网络通信场景,确保套接字在接收数据时的灵活性和效率。
区分阻塞与非阻塞模式:
- 阻塞模式: 在这种模式下,如果没有数据可读,调用
tcp_recvmsg
的进程会被挂起,直到有数据到达或发生错误。在这种情况下,超时设置用于防止进程无限期等待。 - 非阻塞模式: 如果套接字处于非阻塞模式,函数会立即返回,如果没有数据可读,则返回
-EAGAIN
或-EWOULDBLOCK
。在这种情况下,超时设置用于控制函数在没有数据时的返回行为。
阻塞模式下默认超时设置为sk_rcvtimeo
,如何设置这个参数呢?
在默认情况下,TCP 套接字的接收超时通常设定为几秒钟(例如 180 秒),但具体值可以通过系统配置进行调整。
默认值: 在许多 Linux 系统中,TCP 套接字的默认接收超时为 180 秒(即 180000 毫秒),但可以根据具体需求进行修改。
sk_rcvtimeo
的值通常在创建套接字时被初始化,具体可以在以下几个地方进行设置:
- 套接字创建: 在调用 socket() 函数创建套接字时,内核会为该套接字分配默认的超时时间。
- 系统配置文件: 超时时间也可以通过系统配置文件(如
/proc/sys/net/ipv4/tcp_rmem
和/proc/sys/net/ipv4/tcp_wmem)
进行调整。这些文件定义了 TCP 套接字的缓冲区大小和超时行为。
应用程序可以通过调用 setsockopt()
函数显式地设置套接字的接收超时。例如,可以使用 SO_RCVTIMEO
选项来修改 sk_rcvtimeo
的值。
超时设置
timeo = sock_rcvtimeo(sk, nonblock);
设置接收超时时间,根据传入的非阻塞标志来确定。
紧急数据处理
检查传入的标志 flags 是否包含 MSG_OOB
。如果包含,函数将跳转到专门处理紧急数据的部分 (recv_urg
)
if (flags & MSG_OOB) goto recv_urg;
...
recv_urg:err = tcp_recv_urg(sk, msg, len, flags);goto out;
为什么需要这个处理流程呢?
- 紧急数据的特性
紧急数据: 在 TCP 协议中,紧急数据(也称为“带外数据”)是一种特殊的数据流,它允许应用程序在正常数据流中插入优先级更高的数据。这通常用于需要立即处理的信息,如中断信号或重要通知。
独立处理: 紧急数据的处理与常规数据流不同,因此需要单独的逻辑来确保它们能够被及时和正确地接收。- 应用场景
实时通信: 在某些实时应用(如 VoIP 或在线游戏)中,可能会使用紧急数据来传递关键控制信息或状态更新,这些信息需要优先处理。
终端控制: 在终端应用程序中,紧急数据常用于发送控制字符(如 Ctrl+C),这些字符需要立即响应,以便用户能够及时中断或控制正在运行的程序。
网络协议: 一些网络协议可能会利用紧急数据来指示特定事件或状态变化,例如在 SSH 或 Telnet 等协议中。
/** Handle reading urgent data. BSD has very simple semantics for* this, no blocking and very strange errors 8)*/static int tcp_recv_urg(struct sock *sk, struct msghdr *msg, int len, int flags)
{struct tcp_sock *tp = tcp_sk(sk);/* No URG data to read. */if (sock_flag(sk, SOCK_URGINLINE) || !tp->urg_data ||tp->urg_data == TCP_URG_READ)return -EINVAL; /* Yes this is right ! */if (sk->sk_state == TCP_CLOSE && !sock_flag(sk, SOCK_DONE))return -ENOTCONN;if (tp->urg_data & TCP_URG_VALID) {int err = 0;char c = tp->urg_data;if (!(flags & MSG_PEEK))tp->urg_data = TCP_URG_READ;/* Read urgent data. */msg->msg_flags |= MSG_OOB;if (len > 0) {if (!(flags & MSG_TRUNC))err = memcpy_toiovec(msg->msg_iov, &c, 1);len = 1;} elsemsg->msg_flags |= MSG_TRUNC;return err ? -EFAULT : len;}if (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))return 0;/* Fixed the recv(..., MSG_OOB) behaviour. BSD docs and* the available implementations agree in this case:* this call should never block, independent of the* blocking state of the socket.* Mike <pall@rz.uni-karlsruhe.de>*/return -EAGAIN;
}
上面的代码做了什么👆?
在 recv_urg 部分,内核会执行以下操作:
- 读取紧急数据: 从接收缓冲区中提取紧急数据,并将其复制到用户提供的缓冲区。
- 更新状态: 更新 TCP 套接字状态,以反映已处理的紧急数据,并确保后续的数据接收逻辑能够正确识别当前的数据流状态。
- 信号处理: 如果在处理过程中有信号(如 SIGURG)到达,函数会相应地进行处理,以确保应用程序可以及时响应外部事件。
这一设计确保了关键控制信息能够被优先处理,提高了实时应用和网络协议的响应能力。对于需要快速响应用户输入或特定事件的应用场景,这种机制是至关重要的。
主要职责
- 数据接收:
tcp_recvmsg
从 TCP 接收队列中读取数据,并将其复制到用户提供的缓冲区。它确保数据的顺序性和完整性,处理 TCP 协议的特性,如流量控制和拥塞控制。 - 队列管理: 函数会检查多个接收队列,包括:
- 接收队列 (receive queue): 存放已接收但未被读取的数据。
- 预处理队列 (prequeue): 暂存于用户进程上下文中等待处理的数据。
- 后备队列 (backlog): 用于存放在套接字被锁定时到达的数据包。
- 阻塞与非阻塞模式: 根据传入的参数,函数可以在没有数据可读时选择阻塞等待或立即返回。
- 错误处理: 在数据接收过程中,tcp_recvmsg 还会处理各种错误情况,并返回相应的错误码。
工作流程
-
函数首先锁定与套接字相关的控制块,以防止并发访问。
-
检查接收队列是否有数据可用。如果没有且处于阻塞模式,则函数会挂起等待数据到达。
-
一旦有数据可用,函数会遍历接收队列,找到合适的序列号进行读取,并将数据复制到用户缓冲区。
处理特殊标志(如MSG_PEEK
)以决定如何处理数据。
如何处理预备队列和后备队列中的数据
- 预备队列 (prequeue): 当接收的数据包到达时,如果套接字未被用户进程锁定,数据包会被放入预备队列。这些数据包随后会在用户进程上下文中进行处理。
- 后备队列 (backlog): 如果套接字被用户进程锁定,新的数据包将被放入后备队列。此时,任何试图锁定该套接字的线程将被排入等待队列,直到套接字被解锁。
设计预备队列(prequeue)和后备队列(backlog)在 Linux 内核 TCP 实现中是为了优化数据接收的效率和可靠性。
当多个数据包同时到达时,预备队列允许内核在用户进程未锁定套接字的情况下临时存储这些数据包,从而避免数据丢失。这样可以在用户进程准备好读取数据时,快速将数据从预备队列转移到用户空间。
/** This routine copies from a sock struct into the user buffer.** Technical note: in 2.3 we work on _locked_ socket, so that* tricks with *seq access order and skb->users are not required.* Probably, code can be easily improved even more.*/int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,size_t len, int nonblock, int flags, int *addr_len)
{struct tcp_sock *tp = tcp_sk(sk);int copied = 0;u32 peek_seq;u32 *seq;unsigned long used;int err;int target; /* Read at least this many bytes */long timeo;struct task_struct *user_recv = NULL;int copied_early = 0;struct sk_buff *skb;u32 urg_hole = 0;lock_sock(sk);err = -ENOTCONN;if (sk->sk_state == TCP_LISTEN)goto out;timeo = sock_rcvtimeo(sk, nonblock);/* Urgent data needs to be handled specially. */if (flags & MSG_OOB)goto recv_urg;seq = &tp->copied_seq;if (flags & MSG_PEEK) {peek_seq = tp->copied_seq;seq = &peek_seq;}target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);#ifdef CONFIG_NET_DMAtp->ucopy.dma_chan = NULL;preempt_disable();skb = skb_peek_tail(&sk->sk_receive_queue);{int available = 0;if (skb)available = TCP_SKB_CB(skb)->seq + skb->len - (*seq);if ((available < target) &&(len > sysctl_tcp_dma_copybreak) && !(flags & MSG_PEEK) &&!sysctl_tcp_low_latency &&dma_find_channel(DMA_MEMCPY)) {preempt_enable_no_resched();tp->ucopy.pinned_list =dma_pin_iovec_pages(msg->msg_iov, len);} else {preempt_enable_no_resched();}}
#endifdo {u32 offset;/* Are we at urgent data? Stop if we have read anything or have SIGURG pending. */if (tp->urg_data && tp->urg_seq == *seq) {if (copied)break;if (signal_pending(current)) {copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;break;}}/* Next get a buffer. */skb_queue_walk(&sk->sk_receive_queue, skb) {/* Now that we have two receive queues this* shouldn't happen.*/if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),"recvmsg bug: copied %X seq %X rcvnxt %X fl %X\n",*seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt,flags))break;offset = *seq - TCP_SKB_CB(skb)->seq;if (tcp_hdr(skb)->syn)offset--;if (offset < skb->len)goto found_ok_skb;if (tcp_hdr(skb)->fin)goto found_fin_ok;WARN(!(flags & MSG_PEEK),"recvmsg bug 2: copied %X seq %X rcvnxt %X fl %X\n",*seq, TCP_SKB_CB(skb)->seq, tp->rcv_nxt, flags);}/* Well, if we have backlog, try to process it now yet. */if (copied >= target && !sk->sk_backlog.tail)break;if (copied) {if (sk->sk_err ||sk->sk_state == TCP_CLOSE ||(sk->sk_shutdown & RCV_SHUTDOWN) ||!timeo ||signal_pending(current))break;} else {if (sock_flag(sk, SOCK_DONE))break;if (sk->sk_err) {copied = sock_error(sk);break;}if (sk->sk_shutdown & RCV_SHUTDOWN)break;if (sk->sk_state == TCP_CLOSE) {if (!sock_flag(sk, SOCK_DONE)) {/* This occurs when user tries to read* from never connected socket.*/copied = -ENOTCONN;break;}break;}if (!timeo) {copied = -EAGAIN;break;}if (signal_pending(current)) {copied = sock_intr_errno(timeo);break;}}tcp_cleanup_rbuf(sk, copied);if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {/* Install new reader */if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {user_recv = current;tp->ucopy.task = user_recv;tp->ucopy.iov = msg->msg_iov;}tp->ucopy.len = len;WARN_ON(tp->copied_seq != tp->rcv_nxt &&!(flags & (MSG_PEEK | MSG_TRUNC)));/* Ugly... If prequeue is not empty, we have to* process it before releasing socket, otherwise* order will be broken at second iteration.* More elegant solution is required!!!** Look: we have the following (pseudo)queues:** 1. packets in flight* 2. backlog* 3. prequeue* 4. receive_queue** Each queue can be processed only if the next ones* are empty. At this point we have empty receive_queue.* But prequeue _can_ be not empty after 2nd iteration,* when we jumped to start of loop because backlog* processing added something to receive_queue.* We cannot release_sock(), because backlog contains* packets arrived _after_ prequeued ones.** Shortly, algorithm is clear --- to process all* the queues in order. We could make it more directly,* requeueing packets from backlog to prequeue, if* is not empty. It is more elegant, but eats cycles,* unfortunately.*/if (!skb_queue_empty(&tp->ucopy.prequeue))goto do_prequeue;/* __ Set realtime policy in scheduler __ */}#ifdef CONFIG_NET_DMAif (tp->ucopy.dma_chan)dma_async_memcpy_issue_pending(tp->ucopy.dma_chan);
#endifif (copied >= target) {/* Do not sleep, just process backlog. */release_sock(sk);lock_sock(sk);} elsesk_wait_data(sk, &timeo);#ifdef CONFIG_NET_DMAtcp_service_net_dma(sk, false); /* Don't block */tp->ucopy.wakeup = 0;
#endifif (user_recv) {int chunk;/* __ Restore normal policy in scheduler __ */if ((chunk = len - tp->ucopy.len) != 0) {NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);len -= chunk;copied += chunk;}if (tp->rcv_nxt == tp->copied_seq &&!skb_queue_empty(&tp->ucopy.prequeue)) {
do_prequeue:tcp_prequeue_process(sk);if ((chunk = len - tp->ucopy.len) != 0) {NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);len -= chunk;copied += chunk;}}}if ((flags & MSG_PEEK) &&(peek_seq - copied - urg_hole != tp->copied_seq)) {if (net_ratelimit())printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\n",current->comm, task_pid_nr(current));peek_seq = tp->copied_seq;}continue;found_ok_skb:/* Ok so how much can we use? */used = skb->len - offset;if (len < used)used = len;/* Do we have urgent data here? */if (tp->urg_data) {u32 urg_offset = tp->urg_seq - *seq;if (urg_offset < used) {if (!urg_offset) {if (!sock_flag(sk, SOCK_URGINLINE)) {++*seq;urg_hole++;offset++;used--;if (!used)goto skip_copy;}} elseused = urg_offset;}}if (!(flags & MSG_TRUNC)) {
#ifdef CONFIG_NET_DMAif (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)tp->ucopy.dma_chan = dma_find_channel(DMA_MEMCPY);if (tp->ucopy.dma_chan) {tp->ucopy.dma_cookie = dma_skb_copy_datagram_iovec(tp->ucopy.dma_chan, skb, offset,msg->msg_iov, used,tp->ucopy.pinned_list);if (tp->ucopy.dma_cookie < 0) {printk(KERN_ALERT "dma_cookie < 0\n");/* Exception. Bailout! */if (!copied)copied = -EFAULT;break;}dma_async_memcpy_issue_pending(tp->ucopy.dma_chan);if ((offset + used) == skb->len)copied_early = 1;} else
#endif{err = skb_copy_datagram_iovec(skb, offset,msg->msg_iov, used);if (err) {/* Exception. Bailout! */if (!copied)copied = -EFAULT;break;}}}*seq += used;copied += used;len -= used;tcp_rcv_space_adjust(sk);skip_copy:if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {tp->urg_data = 0;tcp_fast_path_check(sk);}if (used + offset < skb->len)continue;if (tcp_hdr(skb)->fin)goto found_fin_ok;if (!(flags & MSG_PEEK)) {sk_eat_skb(sk, skb, copied_early);copied_early = 0;}continue;found_fin_ok:/* Process the FIN. */++*seq;if (!(flags & MSG_PEEK)) {sk_eat_skb(sk, skb, copied_early);copied_early = 0;}break;} while (len > 0);if (user_recv) {if (!skb_queue_empty(&tp->ucopy.prequeue)) {int chunk;tp->ucopy.len = copied > 0 ? len : 0;tcp_prequeue_process(sk);if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);len -= chunk;copied += chunk;}}tp->ucopy.task = NULL;tp->ucopy.len = 0;}#ifdef CONFIG_NET_DMAtcp_service_net_dma(sk, true); /* Wait for queue to drain */tp->ucopy.dma_chan = NULL;if (tp->ucopy.pinned_list) {dma_unpin_iovec_pages(tp->ucopy.pinned_list);tp->ucopy.pinned_list = NULL;}
#endif/* According to UNIX98, msg_name/msg_namelen are ignored* on connected socket. I was just happy when found this 8) --ANK*//* Clean up data we have read: This will do ACK frames. */tcp_cleanup_rbuf(sk, copied);release_sock(sk);return copied;out:release_sock(sk);return err;recv_urg:err = tcp_recv_urg(sk, msg, len, flags);goto out;
}
EXPORT_SYMBOL(tcp_recvmsg);
相关文章:
【Linux内核深度解析】TCP协议栈之tcp_recvmsg
tcp_recvmsg 是 Linux 内核中用于处理 TCP 套接字接收数据的核心函数。它的主要任务是从接收队列中读取数据并将其复制到用户空间。 函数原型 int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_le…...
android-studio-4.2下载 、启动
下载 分享一个国内的android studio网站,可以下载SDK和一些Android studio开发工具 https://www.androiddevtools.cn/ 启动 JAVA_HOME/app/zulu17.48.15-ca-jdk17.0.10-linux_x64/ /app5/android-studio-home/android-studio-ide-201.6568795-linux-4.2C1/bin/s…...
Excel——宏教程(2)
Excel——宏教程(2) 一)、处理单元格 1、直接赋值与引用 将变量、常量值直接赋给单元格、或将单元格的值直接赋给变量、常量,这是在excel中最简单的单元格赋值及引用方法。 如下例将工作表"Sheet1"A1单元格的值赋给Integer变量I,并将I1的值…...
React Native 全栈开发实战班 - 性能与调试之打包与发布
在完成 React Native 应用的开发与性能优化后,下一步就是将应用打包并发布到各大应用市场,如 Apple App Store 和 Google Play Store。本章节将详细介绍 React Native 应用的打包与发布流程,包括 Android 和 iOS 平台的打包步骤、签名配置、发…...
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
最近在做一个与激光器用串口进行通讯的程序文档中要求将频率参数以3个字节的方式进行发送。这里记录一下过程。以便以后再有类似问题时可以快速解决。 /// <summary>/// 设置频率/// </summary>/// <param name"sender"></param>/// <par…...
Win11下载和配置VSCode(详细讲解)
配置VSCode需要的工具: 一、MinGW-w64 二、Visual Studio Code 一、MinGW-w64下载 1、下载 MinGW官网地址: Downloads - MinGW-w64 直链下载: 下载 mingw-w64-install.exe (MinGW-w64 - 适用于 32 位和 64 位 Windows&#…...
基于Multisim的多路智力竞赛抢答器设计与仿真
(1)设计一个8路智力竞赛抢答器,主持人可控制系统的清零和抢答的开始,控制电路可实现最快抢答选手按键抢答的判别和锁定功能,并禁止后续其他选手抢答。 (2)抢答选手确定后给出一声音响的提示和选手编号的显示,抢答选手的编号显示保持到系统被清零为止。 …...
Three.js 闪电效果
闪电shader const shader new THREE.ShaderMaterial({uniforms: {iTime: this.iTime,color: { value: new THREE.Color("#D2F8FE") },},vertexShader: /* glsl */ varying vec2 vUv;varying float normalizeY;void main() {// vUv (uv * 2. - 2.) * vec2(0.3,2.);…...
高效序列化工具(1)-----Protobuf
目录 1.Protobuf Protobuf 的特点 工作原理 Protobuf 与 JSON、XML 的对比 2.protobuf语法 1.数据类型 2.消息 3.枚举 4.嵌套消息 5.重复字段 6.默认值 7.其他类型 1.oneof类型 2.any类型 8.文件组织 3.protobuf命令 1.常见命令 选项: 1. --proto_…...
湛江市社保卡申领指南:手机获取电子照片回执单号
在湛江市,社保卡的申领流程已经实现了数字化,为市民带来了极大的便利。特别是通过手机获取数码照片回执单号,这一环节更是简化了申领过程。今天,我们将详细介绍如何不去照相馆,利用手机来获取数码照片回执单号…...
HTML5实现剪刀石头布小游戏(附源码)
文章目录 1.设计来源1.1 主界面1.2 皮肤风格1.2 游戏中界面 2.效果和源码源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/143798520 HTM…...
TypeScript之常见类型
常见类型(Everyday Types) 本章我们会讲解 JavaScript 中最常见的一些类型,以及对应的描述方式。注意本章内容并不详尽,后续的章节会讲解更多命名和使用类型的方式。 类型可以出现在很多地方,不仅仅是在类型注解 (type annotations)中。我们不仅要学习类型本身,也要学习…...
MacOS java多版本安装与管理-sdkman
安装sdkman curl -s "https://get.sdkman.io" | bashsource "$HOME/.sdkman/bin/sdkman-init.sh"sdk version正常出现sdkman版本号就安装成功了 # 安装java # 安装java8 sdk install java 8.0.412.fx-zulu建议和上述一样安装 fx-zulu 的jdk,…...
NLP论文速读(多伦多大学)|利用人类偏好校准来调整机器翻译的元指标
论文速读|MetaMetrics-MT: Tuning Meta-Metrics for Machine Translation via Human Preference Calibration 论文信息: 简介: 本文的背景是机器翻译(MT)任务的评估。在机器翻译领域,由于不同场景和语言对的需求差异&a…...
20241121 android中树结构列表(使用recyclerView实现)
1、adapter-item的布局 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"wrap_content&…...
达索系统亮相第三十一届中国汽车工程学会年会暨展览会
伴随着改革开放以及中国入世WTO,三十多年来,中国汽车产销已经成为世界最大的单一市场而独占鳌头。近十年来,另辟蹊径的中国汽车产业人在新能源汽车赛道上引领了一波又一波令全球惊艳的创新成就,成为最为靓丽的新出口三大件的头牌。…...
Python网络爬虫实践案例:爬取猫眼电影Top100
以下是一个Python网络爬虫的实践案例,该案例将演示如何使用Python爬取猫眼电影Top100的电影名称、主演和上映时间等信息,并将这些信息保存到TXT文件中。此案例使用了requests库来发送HTTP请求,使用re库进行正则表达式匹配,并包含详…...
ROSSERIAL与Arduino IDE交叉开发(UBUNTU环境,包含ESP32、arduino nano)
ROSSERIAL与Arduino IDE交叉开发 一、简介二、安装1、Ubuntu下的Arduino IDE安装 **针对ESP32报错问题原因溯源和修改**三、运行结点 一、简介 这个教程展示在ubuntu环境下如何利用Arduino IDE配合rosserial开发机器人部件。通过Arduino IDErosserial实现arduino/esp32开发板通…...
爬虫开发工具与环境搭建——使用Postman和浏览器开发者工具
第三节:使用Postman和浏览器开发者工具 在网络爬虫开发过程中,我们经常需要对HTTP请求进行测试、分析和调试。Postman和浏览器开发者工具(特别是Network面板和Console面板)是两种最常用的工具,能够帮助开发者有效地捕…...
ceph 18.2.4二次开发,docker镜像制作
编译环境要求 #需要ubuntu 22.04版本 参考https://docs.ceph.com/en/reef/start/os-recommendations/ #磁盘空间最好大于200GB #内存如果小于100GB 会有OOM的情况发生,需要重跑 目前遇到内存占用最高为92GB替换阿里云ubuntu 22.04源 将下面内容写入/etc/apt/sources.list 文件…...
游戏引擎学习第19天
介绍 这段内容描述了开发者在进行游戏开发时,对于音频同步和平台层的理解和调整的过程。以下是更详细的复述: 开发者表达了他希望今天继续进行的工作内容。他提到,昨天他讲解了一些关于音频的内容,今天他想稍微深入讲解一下他正…...
简单实现vue2响应式原理
vue2 在实现响应式时,是根据 object.defineProperty() 这个实现的,vue3 是通过 Proxy 对象实现,但是实现思路是差不多的,响应式其实就是让 函数和数据产生关联,在我们对数据进行修改的时候,可以执行相关的副…...
TypeScript 中扩展现有模块的用法
declare module 是 TypeScript 中用于扩展现有模块的特性。它允许开发者在已有模块的基础上,添加新的功能(比如扩展接口、添加类型声明等)。通过 declare module,可以将额外的声明合并到原模块中。以下是用法详解: 用…...