linux内核网络分层概述
在开发应用时,我们使用 socket 实现网络数据的收发。以tcp为例,server端通过 socket, bind, listen来创建服务端,然后通过 accept接收客户端连接;客户端通过 socket和 connect系统调用来创建客户端。用于数据收发的系统调用包括 send, recv, sendto, recvfrom等。除了上述系统调用之外,另外还有多路复用技术 select,poll, epoll,也常常在网络应用中使用。
// tcp 服务端int fd = socket(AF_INET, SOCK_STREAM, 0);bind(listen_fd,(struct sockaddr *)&server_addr, sizeof(server_addr))}listen(listen_fd, 5);int accetp_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client);// tcp 客户端sock_fd = socket(AF_INET, SOCK_STREAM, 0);connect(sock_fd, (struct sockaddr *)&addr_serv,sizeof(struct sockaddr));
在做应用开发时,使用上述系统调用比较简单和简洁。对于 linux 网络来说,这些系统调用只是冰山一角,在 linux 网络收发包的过程中,报文要经过协议栈,网卡驱动以及网卡硬件本身的处理。
1网络分层
如下是 linux 收发包示意图:
上图中包括的环节有网卡,网卡驱动,网络层,传输层,socket 层,应用层。收包时报文的流动方向是从下到上,发包时反之。其中网络应用工作在用户态;传输层、网络层以及网卡驱动工作在内核态;socket 层是 linux 提供的系统调用,是用户态和内核态的桥梁。应用层、socket、传输层网络层是软件部分,网卡是硬件部分,网卡驱动是软件与硬件的桥梁。
1.1网卡
dma
dma即直接内存访问,直接内存访问的意思是硬件来访问内存,数据读写过程中不需要cpu参与。在收包方向,网卡收到包之后,会通过dma引擎将数据保存在主机内存中,然后将收包事件通知给 cpu,之后cpu便可以处理这个报文;在发包方向,cpu将报文放到对应的内存buffer之后,将信息通知网卡,之后网卡便会通过dma引擎将buffer中的数据取出并发送出去。dma过程中, cpu和网卡之间只需要一些通知指令,cpu不需要参与数据的读写过程。设想一下,如果没有dma, 那么在收发包时就需要cpu不断地从网卡中读写数据进行收发包,这种效率会非常低下。
环形队列
环形队列中的元素称为bd(buffer descriptor),是一个描述符,该描述符中存储的并不是真正的报文数据,而是报文的元数据。考虑最简单的情况,bd中应包括一个buffer指针,指向实际存储数据的内存地址,另外还有一个变量是内存buffer的长度。实际中bd的结构会更复杂,比如一个报文太长,在一个buffer中存不下,这样就需要有其它的信息,比如报文存了几个buffer;另外网卡收到数据之后,并不一定从buffer的开始处存储报文数据,这样就需要在buffer的开始处留出一段空间,有时还需要在buffer的末尾处保留一段空间。环形队列是cpu软件和网卡硬件通信的桥梁,网卡硬件和cpu都可以访问环形队列。
struct buffer_desc {char *buffer;int length;
};
1.2网卡驱动
网卡驱动与网卡通信的方式有两种:中断和轮询,这也是软件和硬件交互的两种方式。以收包为例,对于中断方式,即网卡收到数据包之后,会触发一个中断,然后网卡驱动注册的中断处理程序便会从网卡中读取数据并做后续的处理;轮询方式是cpu不断地查询网卡的相关寄存器,判断当前有没有新的包需要处理,有则处理,没有则这次轮询空转。当流量较小的时候,在没有数据时,轮询方式会导致cpu空跑,浪费cpu资源,所以轮询方式适用于流量较大的场景。
中断提高实时性(因为在linux中,中断的优先级最高,高于线程和软中断,中断到来之后会立即得到响应),但是如果在流量比较大的场景,网卡产生中断的速度就会非常快,这样会让cpu为处理中断事件浪费所有的时间。
轮询方式适用于网络流量比较大的场景,比如路由器,路由器是通信专用设备,功能比较单一,核心功能就是处理网络流量,所以可以使用轮询方式(即使短时间内流量小导致cpu空转也不会造成其它影响,因为路由器上也没有其它任务来抢cpu)。DPDK中就使用了轮询方式。
linux内核中提供了napi方式,该方式既不是纯中断方式,也不是纯轮询方式,而是集合了中断和轮询,发挥了两种方式的优点。当中断到来时,napi便会关中断,然后处理包,因为关了中断,如果在napi处理包的过程中又来了新的报文,napi处理过程就不会被打断。napi每一次处理均是批量处理,不是处理一个报文就返回,也就是说在napi处理过程中,虽然新到的数据包没有中断,napi也会处理它。
napi的退出机制:napi并不是一直在轮询,当接收队列中的报文都被处理完毕,当然就会返回;但是如果接收队列中的报文非常多,短时间内没有处理完,会一直处理报文吗 ?不会的,为了防止处理网络报文的任务一直占用这cpu,napi有主动退出机制,一个是时间维度,一个是报文数量维度,时间维度是 napi 处理时间超过某个时间便会返回,未处理的报文等到下次调度时再次处理,数量维度是处理的报文数量达到一定的数量时,也会主动返回,未处理的包等到下次调度时再次处理。
napi —— linux 网卡驱动收包机制-CSDN博客
1.3网络层
网络层,最常用的是ip。
ip层主要的作用即路由,在收包方向上根据目的ip决定报文是接收并上传到传输层(目的ip是本地 ip),还是转发(目的ip不是本地ip)。
另外 ip 层也需要处理分片,之所以处理分片,是因为报文的长度大于 mtu。
linux中的netfilter功能也是在ip层实现。
netfilter_netfilter模块报文处理-CSDN博客
1.4传输层
传输层包括两个协议,tcp和udp。
tcp有如下3个特点:
1.4.1面向连接
tcp的服务端和客户端通信之前需要建立连接,建立连接之后才可以收发数据,收发数据结束之后需要断开连接。建立连接过程需要3次握手,断开连接过程中需要4次挥手。
tcp断开连接时,可能是4次挥手,也可能是3次挥手。3次挥手的时候,第二次挥手和第三次挥手合并到了一个报文中。
1.4.2延时ack
如果收到数据之后就立即发送ack的话,那么会导致网络带宽利用率低,因为ack报文没有有效数据,只有tcp, ip协议头。
延时ack是在收到数据之后不立即发送ack,而是等待一定的时间(最小等待时间是40ms,最大等待时间是200ms) 再发送ack;如果在等待期间,本端有数据要发送,那么ack也会跟着数据一块发送出去,不会受40ms和200ms的约束,比如收到数据之后开始等待,等待了10ms,还没到最小等待时间 40ms,如果这个时候要发送数据,那么就会停止等待(停止定时器),ack随数据一块发送出去。
延时ack通过定时器来实现,收到数据之后启动一个定时器,定时器的超时时间,最小是40ms,最大是200ms,如果定时器超时,就会发送ack;如果在定时器超时之前,ack随着本端数据一块发送了出去,那么定时器就会被取消。
延时ack的最小等待时间和最大等待时间,用两个宏来表示。
#define TCP_DELACK_MAX ((unsigned)(HZ/5)) /* maximal time to delay before sending an ACK */
#define TCP_DELACK_MIN ((unsigned)(HZ/25)) /* minimal time to delay before sending an ACK */
因为延时ack的存在,被动关闭的一方在收到FIN报文的时候并不会立即发送ack,而是会有一定的延时。如果在等待的这段时间之内,没有数据要发送,本端也没有做其它动作(比如关闭连接),那么超时之后就会单独发送一个ack;如果本端还有数据发送,那么ack就会跟着数据一块发送出去。这种情况下就是4次挥手。
收到FIN之后,说明对端不会再发送新的数据到来,如果这个时候,本端接收完缓存的数据之后也调用了close将连接关闭,并且这个时候延时ack定时器还没有超时,那么ack就会和FIN一块发送出去。这种情况下就是3次挥手。
1.4.3FIN
当tcp连接的一方要关闭本端的发送时便会向对端发送FIN报文,FIN报文通过函数tcp_send_fin发送。
如果本端调用了close或者shutdown(fd, SHUT_WR) 之后就表示本端已经停止了发送,如果之后再调用send函数,那么会受到SIGPIPE信号,在这种情况下应用会被SIGPIPE杀死,如果不想应用被直接杀死,可以在send函数的最后一个参数中带上 MSG_NOSIGNAL 标志,这样就不会被杀死,而是返回错误码 “Broken pipe”。
3 次挥手:
// server 端
// 收到 FIN 之后,说明对端已经停止了发送数据
// 这个时候 read 返回 0 之后说明没有数据需要处理
// 此时立即关闭连接,调用 close 时会发送 FIN
// 因为延时 ack 还没有超时, 所以 ACK 和 FIN 一块发送出去
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>#define PORT (12345)
#define DATA_MAX_LEN 1024int main(int argc, char *argv[])
{int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if(listen_fd < 0){printf("create listen socket error : %s\n", strerror(errno));return -1;}printf("tcp server listen fd: %d\n", listen_fd);struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(struct sockaddr_in));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(PORT);if(bind(listen_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) < 0){printf("bind error: %s\n", strerror(errno));return -1;}if(listen(listen_fd, 32)){printf("listen error: %s\n\a", strerror(errno));return -1;}struct sockaddr_in client_addr;socklen_t client_addrlen = sizeof(client_addr);int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addrlen);if(client_fd < 0) {printf("accept error: %s\n\a", strerror(errno));return -1;}printf("tcp server accept fd: %d\n", client_fd);char data[DATA_MAX_LEN] = {0};while(1) {int n = read(client_fd, data, DATA_MAX_LEN);if(n < 0) {printf("read error: %s\n\a", strerror(errno));break;} else if(n == 0) {// 读取数据之后立即关闭连接,这个时候延时 ack 还没有超时,// close 会导致本端也会向对端发送 FIN,所以此时 ACK 和 FIN 就会合并到一个报文中发送// 如果打开睡眠函数,让睡眠时间超过延时 ack 的时间,那么延时 ack 超时之后便会发送 ack// close 时的 FIN 会单独发送// usleep(220 * 1000);int value = 1;// 设置 QUICKACK, 的时候会立即发送一个 ack// QUICKACK 模式不是永久生效的,如果要想要保证每次收到数据之后都立即返回 ack,需要在每次收到数据之后,都要做一次这个设置// if (setsockopt(client_fd, IPPROTO_TCP, TCP_QUICKACK, (char *)&value, sizeof(int)) < 0) {// printf("set quick ack error\n");// return -1;// }close(client_fd);printf("client fd closed\n");break;}data[n] = 0;printf("received %d bytes: %s\n", n, data);}close(listen_fd);return 0;
}// client 端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>#define PORT (12345)int main(int argc, char *argv[])
{int connect_fd = socket(AF_INET, SOCK_STREAM, 0);if(connect_fd < 0){printf("create socket error: %s\n", strerror(errno));return -1;}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(struct sockaddr_in));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");server_addr.sin_port = htons(PORT);if(connect(connect_fd, (struct sockaddr *)(&server_addr), sizeof(server_addr)) < 0){printf("connect error: %s\n", strerror(errno));return -1;}char data[64] = "hello, server";int ret = send(connect_fd, data, strlen(data), 0);if(ret != strlen(data)) {printf("send data error: %s\n", strerror(errno));return -1;}sleep(2);close(connect_fd);return 0;
}
这是使用 tcpdump 抓包的结果截图,从下图中可以看到
第 6 个报文是客户端向服务端发送的 FIN,第一次挥手
第 7 个报文是服务端向客户端发送的 ACK + FIN 报文,第二次挥手
第 8 个报文是客户端向服务端发送的 ACK, 第三次挥手
4 次挥手:
如果我们将上述服务端的代码做一下修改,把第 69 行的 usleep(220 * 1000) 打开,这样的话当服务端 read 返回 0 之后不立即关闭 fd,而是延时一段时间再调用 close。延时时间是 220ms,超过了延时 ack 的最大时间,这样的话在 usleep() 期间,本端就会返回 ACK。之后调用 close,向对端发送 FIN。这样的话,第二次挥手和第 3 次挥手就不会合并。
抓包结果如下图所示
第 6 个报文是客户端向服务端发送的 FIN,第一次挥手
第 7 个报文是服务端向客户端发送的 ACK,第二次挥手,从第 6 和第 7 个报文的时间可以看出来,延时 ack 这次的延时在 45ms 左右
第 8 个报文,服务端向客户端发送的 FIN,第三次挥手
第 9 个报文,客户端向服务端发送的 ACK,第四次挥手
如果把上述服务端的代码做一下修改,把第 74 - 77 行的代码打开,这样在设置 quickack 的时候会发送 ack。然后再调用 close,发送 FIN,这样也是 4 次挥手。
从下图可以看出,第 6、7、8、9 报文是 4 次挥手的报文。
1.4.4字节流
tcp 是字节流协议,也就是说在 tcp 这一层,收发数据的边界与用户收发数据的边界可能会出现不一致。比如用户发送了一个长度为 2000 字节的报文,tcp 发送的时候可能会分两次发送,一次发送 1000; 如果用户先后发送了两个报文,长度分别是 1000 字节和 2000 字节,tcp 也可能第一次发送 500字节的报文,第二次发送 1000 字节的报文,第 3 次发送 1500 字节的报文, tcp 分 3 次包数据发送出去。
1.4.5可靠
tcp 最大的特点就是可靠性。丢包,乱序是导致不可靠的原因,tcp 可以通过序列号,重传等技术解决这样的问题,从而保证传输是可靠的。
从上边 3 个方面来说,udp 与 tcp 是相反的:
(1)没有连接
udp 的通信双方在通信之前,不需要建立连接。从这个角度来看,tcp 是面向连接的协议,是一对第一的通信方式,那么 tcp 只能支持单播,不支持多播和广播,因为多播和广播是一对多的通信方式;udp 支持多播和广播。
(2)数据报
udp 是数据报协议,也就是说数据收发的边界就是用户收发数据的边界,比如用户发送了 1000 字节的数据,那么 udp 就会发送 1000 字节的数据,udp 不会改变报文的边界。
(3)不可靠
udp 是不可靠的协议,如果报文在传输过程中出现了丢包或者乱序,udp 协议无法发现这些问题。如果使用 udp 协议,还要达到可靠的目标,可以在应用层实现。
1.4.6socket 层和应用层
本片文章值关注使用 socket 进行 tcp 通信。比较简单,不做太多记录。
2struct sk_buff
在内核网络代码中随处可见的一个变量名skb, 数据结构就是struct sk_buff(本文中也会使用skb来表示一个sk_buff)。无论是在哪一层,一个报文在 linux 内核中就用sk_buff 来表示,这方便在各个网络层之间交换数据,而不需要复制数据。
sk_buff 中的字段比较多,本文中只关注两类字段,一类用于报文数据管理,一类用于 sk_buff 管理。
(1)报文数据管理
sk_buff 中有 4 个指针分别指向数据缓存的不同位置,数据缓存就是实际存放报文数据的一段内存。
head: 数据缓存的起始位置
end: 数据缓存的结束位置
data: 数据存放的起始位置
tail: 数据存放的结束位置
也就是说head和end指向缓存的起止位置,缓存申请好之后,这两个指针指向的位置就保持不变;data和tail指向实际数据存储的起止位置,随着报文在不同的层次之间传递,会出现添加协议头(发送方向,tcp, ip, mac 头逐层添加)或者删除报文头(接收方向,mac, ip, tcp 头逐层删除)的情况,这个时候通过操作data来实现,当需要向缓存中追加数据的时候需要移动tail。headroom是head 和data之间的空间,tailroom是tail和end之间的空间,这两个room的大小 >= 0。
另外还有3个成员 transport_header, network_header, mac_header,分别表示 tcp 头,ip 头, mac 头相对于 head 的偏移量。这 3 个成员是每层协议头相对于 head 的偏移量,不是指针。
从上图中可以看到,sk_buff->end 是报文数据可用空间的结尾,但不是内存 buffer 的结尾,内存 buffer 的结尾处还有一个数据结构 struct skb_shared_info。struct skb_shared_info 两个重要的成员是 frags 和 frag_list。frags 是用在 SG, 网卡支持这种方式,才能向这个里边放数据;frag_list 是 ip 分片。
(2)sk_buff 管理
tcp有发送缓冲区,也有接收缓冲区,缓冲区中的元素是一个skb,缓冲区是一个双向链表;tcp接收侧有一个乱序队列,tcp解决乱序问题主要使用这个队列,这个队列使用红黑树实现。
以 tc 发送缓冲区为例,tcp_sendmsg 中调用tcp_sendmsg_locked, tcp_sendmsg_locked中调用skb_entail将skb入队到发送缓冲区中。tcp发送缓冲保存在struct sock 中,成员struct sk_buff_head sk_write_queue。
下图是 tcp 发送缓冲区的示意图:
tcp接收缓冲区在 struct sock 中,成员是sk_receive_queue, 在函数tcp_queue_rcv中入队。
接收时乱序队列的入队函数是tcp_data_queue_ofo,乱序队列在数据结构struct tcp_sock 中,成员是 out_of_order_queue。
相关文章:

linux内核网络分层概述
在开发应用时,我们使用 socket 实现网络数据的收发。以tcp为例,server端通过 socket, bind, listen来创建服务端,然后通过 accept接收客户端连接;客户端通过 socket和 connect系统调用来创建客户端。用于数据收发的系统调用包括 s…...

H3C交换机配置 telnet 服务
使用一个交换机做成 telnet 服务, telnet 可以使用指定端口开启三层交换机, 用于与 pc 互通, 也可以使用自带的 vlan1 设置 ip 然后达到互通, 因为华三的交换机端口默认是 access 口, 默认带 vlan1 , 直接设置 vlan1 的 ip 也就可以实现互通 实现互通 互通的两种方式 设置 vl…...
江苏计算机专转本 技能Mysql知识点总结(二)
三、SQL数据操纵语言(增删改查) 1.insert 语句(增) INSERT INTO 表名 (列1, 列2, 列3) VALUES (值1, 值2, 值3); 2.Delete 语句(删) //1. DELETE FROM 表名 WHERE 条件;//2. truncate table 表名; …...

边缘智能网关助力打造建筑智慧消防物联网
随着经济社会的快速发展,为了满足民众生产、生活、消费需求,高层建筑、大型综合连体建筑持续兴建,各类火灾风险和事故也越发增加。得益于物联网的普及应用,消防监测和管理迎来数字化、智慧化转型升级。 针对各类高层、大型建筑消防…...

学习Cookie 提升
目录 Cookie 的覆盖 Cookie下的path 特点 设置Cookie 路径 实例 Cookie的最大存活时间 设置Cookie 存活时间 实例 Cookie 和session的区别 和联系 Cookie 的覆盖 当 key相同 和只要path的上级目录的路径相同,就可以被替换掉 value 值 如下图…...
OpenAI 发布会 9 天技术总结
OPEN AI 发布会总结 OpenAI 发布会 12 天技术总结Day 1: 开幕与愿景主要内容:体验方式: Day 2: GPT-4 及其突破性进展主要内容:体验方式: Day 3: GPT-4 在编程领域的突破 - Codex & Copilot主要内容:体验方式&…...
免费注册.news域名一年(今日有效)
时间紧迫,就不上图了,需要的尽快。 网址:https://www.namecheap.com/ 优惠码:FREEDOM24...

解决JIRA、Confluence用户自动注销、反复登录的问题
一、问题描述:当工作从从confluence里面打开jira的时候,在回到confluence时候,就自动退出了,需要账号密码登录重复登录,使人十分厌恶。 二、原因分析: 访问 JIRA、Confluence 或任何其他具有相同域或 IP 上…...
Oracle创建逻辑目录
Oracle 在执行逻辑备份及还原时,需要用到逻辑目录。 本文就来简单介绍一下逻辑目录相关的操作,希望对大家有所帮助。 1.登录到Oracle数据库 使用具有足够权限的数据库用户登录到Oracle数据库。通常,这需要是管理员账号,如SYS…...

【AIGC-ChatGPT进阶副业提示词】星际占卜师:探索星象能量的艺术【限时免费阅读,一天之后自动进入进阶课程】
引言 在这个数字化的时代,我们创造了一个独特的角色 —— 星际占卜师。这不仅是一个简单的运势预测工具,更是一个融合了玄学、预言和能量解读的智能向导。通过精心设计的系统提示词和独特的画境生成机制,星际占卜师能够为用户带来沉浸式的占…...

泷羽sec-shell编程(9)
shell(9) 声明! 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他…...
【Vue-4小时速通01-ES6】
1.var和let的区别 1.1作用域与有效范围 var 在函数或全局作用域中声明变量,无论在块级作用域内外都可以访问。 var 即使在块级作用域外部访问,仍然能获取到结果。 let 在块级作用域内声明变量,仅在其所在的作用域中有效。 let 如果在作用域…...

基于STM32的智能仓储环境监测的Proteus仿真
文章目录 一、智能仓储环境监测1.题目要求2.思路3.电路仿真3.1 未仿真时3.2 开始仿真,显示屏显示Init后,正常显示温度湿度光照烟雾数值3.3 切换温度阈值界面,用阈值加减设置温度min和温度max阈值3.4 调整温度数值,触发风扇/加热3.…...
logback日志控制台打印与写入文件
1.创建logback-spring.xml文件放入resource下 <?xml version"1.0" encoding"UTF-8"?> <configuration><property name"LOG_CONTEXT_NAME" value"log"/><!--定义日志文件的存储地址 勿在 LogBack 的配置中使用…...
成方金融科技后端部分笔试题 - 解析
单选题 1.以下关于JAVA自动类型转换,描述错误的是哪一项?(B) A.byte->short B.char->short C.char->int D.float->double 2.请选择运行以下代码后,系统显示的内容什么?(B) public class Test {static {int x1;}static int x,y;publ…...

WatchAlert - 开源多数据源告警引擎
概述 在现代 IT 环境中,监控和告警是确保系统稳定性和可靠性的关键环节。然而,随着业务规模的扩大和数据源的多样化,传统的单一数据源告警系统已经无法满足复杂的需求。为了解决这一问题,我开发了一个开源的多数据源告警引擎——…...
Linux procps-ng 包详解
简介 procps-ng 包是用于监视和管理 Linux 上的进程和系统性能的实用程序集合。它与 /proc 文件系统交互以检索实时系统信息。procps-ng 中的实用程序包括 ps、top、free、uptime 等命令。 安装 procps-ng 使用包管理工具安装 Debian/Ubuntu sudo apt update sudo apt ins…...

[react] <NavLink>自带激活属性
NavLink v6.28.0 | React Router 点谁谁就带上类名 当然类名也是可以自定义 <NavLinkto{item.link}className{({ isActive }) > (isActive ? 测试 : )}>{item.title}</NavLink> 有什么用?他会监听你的路由,刷新的话也会带上激活效果...

智能语音识别模块与声音传感器模块对比分析:原理、优缺点、性价比与应用领域
随着物联网(IoT)和智能家居的发展,智能设备的控制方式越来越多样化,尤其是语音控制和声音感应控制。智能语音识别模块和声音传感器模块作为两种常见的音频输入设备,它们在不同的应用场景中发挥着重要作用。本文将深入分…...

大模型+安全实践之春天何时到来?
引子:距《在大模型实践旅途中摸了下上帝的脚指头》一文发布近一年,2024年笔者继续全情投入在大模型+安全上,深度参与了一些应用实践,包括安全大模型首次大规模应用在国家级攻防演习、部分项目的POC直到项目落地,也推动了一些场景安全大模型应用从0到3的孵化上市。这一年也…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...