什么是协议栈? 用户态协议栈设计(udp协议栈)

什么是协议栈呢?

(协议栈(Protocol Stack)是计算机网络和通信系统中的一个重要概念,它指的是一组协议层的层次结构,这些协议层一起协同工作,以便在不同计算机或设备之间实现数据通信和交换。每个协议层都有特定的功能和责任,从物理层到应用层,每一层都在不同的抽象级别上处理数据和通信任务)友情提示,请阅读代码的注释

通过mmap可以将网卡里的数据映射到内存中去
这里是零拷贝,指的是cpu指令没有参与,但并不是没有拷贝,这是一种DMA的方式

实现协议栈有几种方式,如raw-socket、netmap、dpdk等,这里用netmap实现

如果不这样实现的话,会多拷贝一次,下面看原理图

. 获取原始数据

获取原始数据的三种方法介绍

不经过网络协议栈解析,拿到原始数据sk_buff;

  1. 使用原始套接字raw socket , tcpdump和wireshark就是使用这个做的,raw socket主要用来抓包。
  2. dbdk
  3. netmap是用于用户层应用程序收发原始网络数据的高性能框架,本文使用netmap进行数据的收发。
1、netmap 原理

网卡即不在物理层,也不在数据链路层,是在这两层之间做转换。

数据传输的流程

网卡将物理层的光电信号转换为数字信号(0101010)。给到网卡驱动,然后把这个数据(通过sk_buff(搬运工)) 拷贝迁移到协议栈。 然后协议栈解析完数据之后将数据拷贝放入recv buffer,然后应用程序通过系统调用就能得到这个数据。
 

netmap 采用 mmap 的方式,将网卡驱动的 ring 内存空间映射到用户空间。这样用户态可以直接操作内存,获取原始的数据,避免了内核和用户态的两次拷贝(网卡 -> 内核协议栈 -> 内存)

 

如果不用netmap走内核协议栈的话,我们在驱动和协议栈之间拷贝一次,在协议栈和应用层拷贝一次,那么就走了两次,当大量数据到来的话就会造成 传输速度下降,因为我们的IO操作

其实是很费时间的,所以我们就拷贝一次,大大的缩短了时间

2、netmap 环境搭建

安装 netmap

# 安装 netmap
git clone https://github.com/luigirizzo/netmap.git
cd netmap/LINUX
./configure
make && make install# 将头文件拷贝到 /usr/include/net
cd ./netmap/sys/net/ # netmap 头文件位置
cp * /usr/include/net  

 启动 netmap

# 开启 netmap
insmod netmap.ko 
ls /dev/netmap -l
# 关闭 netmap
rmmod netmap.ko
 3、udp 协议栈的实现

3.1.以太网协议头格式
struct ethhdr {unsigned char h_dst[ETH_ALEN];//源mac,6字节unsigned char h_src[ETH_ALEN];//目的mac,6字节unsigned short h_proto;//协议类型,2字节
};
3.2 ip协议头格式

struct iphdr {unsigned char hdrlen:4,  //为什么跟报头看着不一样呢,那是因为我们的网络字节序是大端的version:4; // 0x45     //我们的协议报头的时候,低位是版本号,高位是报头长度//那么编程的时候,低位的报头长度,高位是版本号//那么在转到网络上去之后就是低位是版本号			  //字节序问题,请去百度一下大小端问题unsigned char tos;//type of serviceunsigned short totlen;//total length//ip包总大小 - 首部大小等于数据大小unsigned short id;//16位标识//标识分片的包,因为网络层向下传的时候//会受mtu的大小,进行分片//所以要想确保数据是正常的,就需要设置一个标识,标识完整的数据包//也以便ip上传到传输层后续重组unsigned short flag_offset; //3位标志+13位片偏移//3位标识一个是df为1表示数据包不可以分片,0表示告知可以分片,//mf标识是否有更多分片,为0就表示最后一个分片了//那么我们在收到包的时候可以根据这个标志位和16位标识以及片偏移量重组数据了//unsigned char ttl; //time to live 生存周期(比如:每经过一个网关ttl-1)// 0x1234// htonsunsigned char type;//8位协议  用于指明IP的上层协议.传输层的报头没有协议unsigned short check;//16位首部校验和unsigned int sip;//源ip,标识发送方主机unsigned int dip;//目的ip,标识接收方主机}; // 20字节

3.3 udp协议头

//udp协议头
struct udphdr {unsigned short sport;//源端口unsigned short dport;//目的端口unsigned short length;//udp长度unsigned short check;//校验值}; // 8字节
3.4 arp协议头

struct arphdr {unsigned short h_type;//硬件类型unsigned short h_proto;//协议类型//真正的地址是mac地址,//ip地址是逻辑地址,mac地址是物理地址,唯一标识一台主机的unsigned char h_addrlen;unsigned char h_protolen;unsigned short oper;//操作码,在发送arp包的时候,会//用到操作码,arp响应2和arp请求1//有了这个操作码,我们就知道是请求获取我的mac地址还是//我的arp请求已经到了(响应)//因为刚开始发arp包的时候,只携带自身的mac地址和arp请求//发送过后,再回发arp响应,将mac地址填上,此时收到的//arp包的源mac地址就是我们之前广播的主机的mac地址//然后做一个映射unsigned char smac[ETH_ADDR_LENGTH];unsigned int sip;unsigned char dmac[ETH_ADDR_LENGTH];unsigned int dip;
};

ICMP协议头我就不实现了,主要是用来进行ping命令的

3.5 各层数据包格式

我们还得定义一下OSI七层模型的数据包,因为网络层的数据包从上到下是解包和封包的过程

 越下面的层,会封装上面的层的协议头

struct udppkt {struct ethhdr eh;struct iphdr ip;struct udphdr udp;unsigned char data[0];//用户数据//柔性数组,这样就可以在结构体末尾动态地分配内存空间。//不会发生越界情况
};//定义完以太网包,ip包和udp包之后
//我们还需要定义一个arp包
//为什么呢,因为arp缓存
//在我们xshell连接上之后会将
//eh0这张网卡的mac地址和ip地址做一个映射关系
//过一段时间之后这个mac和ip地址的映射关系就会消失
//所以我们需要自己搞一个arp包或者自己设置arp缓存
//或者静态的//没有设置也没有静态arp缓存的话客户端就会发一次arp包
//那么既然我们是用netmap的方式接收的包,那么就需要自己接收
//到包,封装包,struct arppkt {struct ethhdr eh;struct arphdr arp;};

其他的包就不写了,其实就是在上层的包那里,添加下当前网络层的协议头


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>#include <sys/poll.h>
#include <arpa/inet.h>#define ETH_ADDR_LENGTH		6
#define NETMAP_WITH_LIBS#include <net/netmap_user.h> 
#pragma pack(1)
//内存对齐设置为1,如果不设置为1的话
//会出问题,参考我的博客里的内存对齐篇#define ETH_ALEN	6
#define PROTO_IP	0x0800
#define PROTO_ARP	0x0806#define PROTO_UDP	17
#define PROTO_ICMP	1
#define PROTO_IGMP	2struct ethhdr {unsigned char h_dst[ETH_ALEN];//源mac,6字节unsigned char h_src[ETH_ALEN];//目的mac,6字节unsigned short h_proto;//协议类型,2字节
};struct iphdr {unsigned char hdrlen:4,  //为什么跟报头看着不一样呢,那是因为我们的网络字节序是大端的version:4; // 0x45     //我们的协议报头的时候,低位是版本号,高位是报头长度//那么编程的时候,低位的报头长度,高位是版本号//那么在转到网络上去之后就是低位是版本号			  //字节序问题,请去百度一下大小端问题unsigned char tos;//type of serviceunsigned short totlen;//total length//ip包总大小 - 首部大小等于数据大小unsigned short id;//16位标识//标识分片的包,因为网络层向下传的时候//会受mtu的大小,进行分片//所以要想确保数据是正常的,就需要设置一个标识,标识完整的数据包//也以便ip上传到传输层后续重组unsigned short flag_offset; //3位标志+13位片偏移//3位标识一个是df为1表示数据包不可以分片,0表示告知可以分片,//mf标识是否有更多分片,为0就表示最后一个分片了//那么我们在收到包的时候可以根据这个标志位和16位标识以及片偏移量重组数据了//unsigned char ttl; //time to live 生存周期(比如:每经过一个网关ttl-1)// 0x1234// htonsunsigned char type;//8位协议  用于指明IP的上层协议.传输层的报头没有协议unsigned short check;//16位首部校验和unsigned int sip;//源ip,标识发送方主机unsigned int dip;//目的ip,标识接收方主机}; // 20字节//udp协议头
struct udphdr {unsigned short sport;//源端口unsigned short dport;//目的端口unsigned short length;//udp长度unsigned short check;//校验值}; // 8字节struct udppkt {struct ethhdr eh;struct iphdr ip;struct udphdr udp;unsigned char data[0];//用户数据//柔性数组,这样就可以在结构体末尾动态地分配内存空间。//不会发生越界情况
};//定义完以太网包,ip包和udp包之后
//我们还需要定义一个arp包
//为什么呢,因为arp缓存
//在我们xshell连接上之后会将
//eh0这张网卡的mac地址和ip地址做一个映射关系
//过一段时间之后这个mac和ip地址的映射关系就会消失
//所以我们需要自己搞一个arp包或者自己设置arp缓存
//或者静态的//没有设置也没有静态arp缓存的话客户端就会发一次arp包
//那么既然我们是用netmap的方式接收的包,那么就需要自己接收
//到包,封装包,struct arphdr {unsigned short h_type;//硬件类型unsigned short h_proto;//协议类型//真正的地址是mac地址,//ip地址是逻辑地址,mac地址是物理地址,唯一标识一台主机的unsigned char h_addrlen;unsigned char h_protolen;unsigned short oper;//操作码,在发送arp包的时候,会//用到操作码,arp响应2和arp请求1//有了这个操作码,我们就知道是请求获取我的mac地址还是//我的arp请求已经到了(响应)//因为刚开始发arp包的时候,只携带自身的mac地址和arp请求//发送过后,再回发arp响应,将mac地址填上,此时收到的//arp包的源mac地址就是我们之前广播的主机的mac地址//然后做一个映射unsigned char smac[ETH_ADDR_LENGTH];unsigned int sip;unsigned char dmac[ETH_ADDR_LENGTH];unsigned int dip;
};struct arppkt {struct ethhdr eh;struct arphdr arp;};
//icmp我就不封装了,有icmp协议我们才能用ping命令,否则用ping命令是
//ping不通的,可以用wireshark抓包看一下有没有icmp协议int str2mac(char *mac, char *str) {char *p = str;unsigned char value = 0x0;int i = 0;while (*p != '\0') {if (*p == ':') {mac[i++] = value;value = 0x0;} else {unsigned char temp = *p;if (temp <= '9' && temp >= '0') {temp -= '0';} else if (temp <= 'f' && temp >= 'a') {temp -= 'a';temp += 10;} else if (temp <= 'F' && temp >= 'A') {temp -= 'A';temp += 10;} else {	break;}value <<= 4;value |= temp;}p ++;}mac[i] = value;return 0;
}void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *mac) {//把源和目的 的ip换一下就行了,然后补个mac地址memcpy(arp_rt, arp, sizeof(struct arppkt));memcpy(arp_rt->eh.h_dst, arp->eh.h_src, ETH_ADDR_LENGTH);str2mac(arp_rt->eh.h_src, mac);arp_rt->eh.h_proto = arp->eh.h_proto;arp_rt->arp.h_addrlen = 6;arp_rt->arp.h_protolen = 4;arp_rt->arp.oper = htons(2);str2mac(arp_rt->arp.smac, mac);arp_rt->arp.sip = arp->arp.dip;memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ADDR_LENGTH);arp_rt->arp.dip = arp->arp.sip;}
//就是解析完arp包后再发过去,目的mac和源mac什么的变一下
// void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *mac):
//这是一个函数定义,它接受三个参数,其中 arp 是指向输入ARP数据包的指针,arp_rt 
//是指向输出ARP数据包的指针,mac 是一个字符数组,可能用于存储MAC地址。// // memcpy(arp_rt, arp, sizeof(struct arppkt)):
// 这行代码将从输入ARP数据包 arp 复制整个数据包的内容到输出ARP数据包 arp_rt 中,
// 复制的字节数为 sizeof(struct arppkt)。// // memcpy(arp_rt->eh.h_dst, arp->eh.h_src, ETH_ADDR_LENGTH):
// 这行代码将输入ARP数据包中的目标MAC地址(eh.h_dst)复制到输出ARP数据包的源MAC地址(eh.h_src),
// 以交换它们的值。 ETH_ADDR_LENGTH 可能是一个常量,表示MAC地址的长度。// // str2mac(arp_rt->eh.h_src, mac):
// 这行代码似乎是将 mac 中的MAC地址数据复制到输出ARP数据包的源MAC地址字段(eh.h_src)中。// // arp_rt->eh.h_proto = arp->eh.h_proto:
// 这行代码将输出ARP数据包的以太网协议类型字段(eh.h_proto)设置为与输入ARP数据包相同的值,以保持协议类型不变。// // arp_rt->arp.h_addrlen = 6 和 arp_rt->arp.h_protolen = 4:
// 这两行代码设置输出ARP数据包的地址长度字段和协议地址长度字段。// // arp_rt->arp.oper = htons(2):这行代码将输出ARP数据包的操作码字段(oper)设置为2,这表示ARP响应。// // str2mac(arp_rt->arp.smac, mac):这行代码将 mac 中的MAC地址数据复制到
// 输出ARP数据包的发送方MAC地址字段(smac)中。// // arp_rt->arp.sip = arp->arp.dip:这行代码将输出ARP数据包的发送方IP地址字段(sip)
// 设置为输入ARP数据包的目标IP地址字段(dip)的值。// // memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ADDR_LENGTH):这行代码将输入ARP数据包的源MAC地址(smac)
// 复制到输出ARP数据包的目标MAC地址字段(dmac),以交换它们的值。// // arp_rt->arp.dip = arp->arp.sip:这行代码将输出ARP数据包的目标IP地址字段(dip)
// 设置为输入ARP数据包的发送方IP地址字段(sip)的值。// // 总的来说,这个函数接受一个ARP请求数据包,将其内容复制到一个ARP响应数据包中,
// 同时交换了源和目标的MAC地址和IP地址,以制作一个相应的ARP响应数据包,用于回应原始ARP请求。
// 这种操作通常用于网络通信中,以满足地址解析的需求。函数的实现可能依赖于其他未提供的函数或数据结构,
// 如 struct arppkt 和 str2mac。//
int main() {struct nm_pkthdr h;//ringbuffer的头struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);if (nmr == NULL) return -1;//把fd放入pollfd中,如果fd可读,就去操作数据,不可读就不操作struct pollfd pfd = {0};pfd.fd = nmr->fd;pfd.events = POLLIN;while (1) {int ret = poll(&pfd, 1, -1);//第一个参数:pollfd,第二个参数:fd个数,第三个参数:-1代表一直阻塞,直到数据过来if (ret < 0) continue;if (pfd.revents & POLLIN) {//有数据来了unsigned char *stream = nm_nextpkt(nmr, &h);//取数据(因为已经在内存中了,不能用读,由于是环形ringbuffer,因此取数据叫next package)struct ethhdr *eh = (struct ethhdr *)stream;//把stream中的第一个部分转换为以太网头if (ntohs(eh->h_proto) ==  PROTO_IP) { //取出来的上层协议是IP协议struct udppkt *udp = (struct udppkt *)stream;//转化为udp帧数据格式if (udp->ip.type == PROTO_UDP) { //udp包int udplength = ntohs(udp->udp.length);udp->data[udplength-8] = '\0'; //udp总长度-8个字节长度的udp头  就是upd数据部分的长度。  末尾加上字符串结尾'\0'printf("udp --> %s\n", udp->data);} else if (udp->ip.type == PROTO_ICMP) {}} else if (ntohs(eh->h_proto) ==  PROTO_ARP) {//ARP包struct arppkt *arp = (struct arppkt *)stream;struct arppkt arp_rt;//eth0的ip地址,eth0是网卡接口if (arp->arp.dip == inet_addr("10.0.4.12")) { //如果接受到的广播arp是本机的就回复 (如果不进行判断就是ARP攻击了,不管是什么arp请求,都回复,会导致它们的arp表更新错误的信息)//eth0的mac地址echo_arp_pkt(arp, &arp_rt, "52:54:00:d5:c3:82");//创建一个arp回复的包(源和目的互换,补充上mac地址(ifconfig可以查看))nm_inject(nmr, &arp_rt, sizeof(arp_rt));//发送arp应答printf("arp ret\n");}}}}}

使用nm_open()函数时,需要指定的是物理网卡名。eth0是物理显卡名,ens33是虚拟网卡名。
修改网卡名字:

sudo vim /etc/default/grub//修改GRUB_CMDLINE_LINUX为如下,主要是增加 net.ifnames=0 biosdevname=0 这句
GRUB_CMDLINE_LINUX="find_presend=/presend.cfg noprompt net.ifnames=0 biosdevname=0 default_hugepagesz=1G hugepagesz=2M hugepages=1024 isolcpus=0-2"

启动程序后刚开始可以接收udp包,过一段时间后就接受不到了

1.原因:程序把网卡的数据发送到了共享内存,不经过协议栈。而局域网内所有机器每隔一段时间会发送arp协议告知局域网内其他机器自己的IP和MAC地址,如果一段时间内没有收到对方的arp协议,那么本机就会把arp表对应的arp协议信息(IP和MAC地址)删掉。
因此,因为一开始发送udp包对方的时候,还知道对方的IP和MAC地址。对方因为没有走协议栈,对方就会不发arp协议给我,那么过段时间后,我的arp表就会把对方的IP和MAC地址信息删掉,我就没办法知道对方的IP和MAC地址,因此后面就无法发送upd包给到对方了。

没开启进程前,可以ping通进程所在的机器,过段时间后无法ping通。
2.原因:程序把网卡的数据发送到了共享内存,不经过协议栈。而ping协议的反馈是走ICMP协议的
因此,因为ping对方的时候,对方因为没有走协议栈,对如果对方处理网卡信息的时候,没有实现对ICMP协议的解析和回复,那么我ping对方就没办法收到对方的反馈。

解决方法:

1.ping命令只需要实现一下icmp包就行
2.怎么保存arp缓存呢

   2.1 自己添加一下arp,设置成静态的,那么数据包就知道发到局域网的哪台主机了,

   因为路由器保存着局域网内的arp缓存表,arp缓存表的ip地址是局域网内部的私有ip

   地址,添加了之后,路由器就有一条arp缓存,当数据包到来的时候,路由器识别到 

   的是公网ip地址,然后路由器收到数据包之后,根据arp缓存表,找到对应的mac地址

   和,先去发到mac层,判断是否符合数据包的mac地址,再发到网络层,判断ip是否

   是数据包的ip,是的话,就向传输层传输

  2.2 自己写arp包,收到arp请求的时候,填充我们的eth0的ip地址和mac地址,重新发送

  过去

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

暴力递归转动态规划(十三)

题目 给定3个参数&#xff0c;N&#xff0c;M&#xff0c;K 怪兽有N滴血&#xff0c;等着英雄来砍自己 英雄每一次打击&#xff0c;都会让怪兽流失[0~M]的血量 到底流失多少&#xff1f;每一次在[0~M]上等概率的获得一个值 求K次打击之后&#xff0c;英雄把怪兽砍死的概率。 暴…

httpclient工具类(支持泛型转换)

1、网上搜到的httpclient工具类的问题&#xff1a; 1.1、如下图我们都能够发现这种封装的问题&#xff1a; 代码繁杂、充斥了很多重复性代码返回值单一&#xff0c;无法拿到对应的Java Bean对象及List对象集合实际场景中会对接大量第三方的OPEN API&#xff0c;下述方法的扩展…

zookeeper集群搭建

zookeeper&#xff08;动物园管理员&#xff09;是一个广泛应用于分布式服务提供协调服务Apache的开源框架 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它 负责存储和管理大家都关心的数据 &#xff0c;然 后 接受观察…

第 04 章_逻辑架构

第 04 章_逻辑架构 1. 逻辑架构剖析 1. 1 服务器处理客户端请求 那服务器进程对客户端进程发送的请求做了什么处理&#xff0c;才能产生最后的处理结果呢&#xff1f;这里以查询请求为 例展示&#xff1a; 下面具体展开看一下&#xff1a; 1.2 Connectors 1.3 第 1 层&…

Docker网络模式_Docker常用命令_以及Docker如何给运行的镜像内容连接互联网_Docker网络模式原理---Docker工作笔记004

然后我们来看一下docker的网络模式: 这个docker我们先看一下电脑上的网络,有两个,1个是lo是测试用的一个是enp0s3这个是我们以太网地址,然后我们去: 安装docker 安装后我们再去ip address可以看到多出来一个网络是docker0 这里ip地址是172.17.0.1这个是私有地址外部无法访问 这…

VSCode实用远程主机功能

作为嵌入式开发者&#xff0c;经常在各种系统平台或者开发工具之间切换&#xff0c;比如你的代码在Linux虚拟机上&#xff0c;如果不习惯在Linux下用IDE&#xff0c;那么我尝试将Linux的目录通过samba共享出来&#xff0c;在windows下用网络映射盘的方式映射出来&#xff0c;VS…

Python某建筑平台数据, 实现网站JS逆向解密

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: 首先我们先来安装一下写代码的软件&#xff08;对没安装的小白说&#xff09; Python 3.8 / 编译器 Pycharm 2021.2版本 / 编辑器 专业版是付费的 <文章下方名片可获取魔法永久用~> 社区版是免费的 模块…

Java Web 学习笔记(二) —— JDBC

目录 1 JDBC 概述2 JDBC 快速入门3 JDBC API 详解3.1 DriverManager3.2 Connection3.3 Statement3.4 ResultSet3.5 PreparedStatement3.5.1 代码模拟 SQL 注入3.5.2 PreparedStatement 的使用3.5.3 PreparedStatement 原理 4 数据库连接池4.1 数据库连接池概述4.2 数据库连接池…

佳易王配件进出库开单打印进销存管理系统软件下载

用版配件进出库开单打印系统&#xff0c;可以有效的管理&#xff1a;供货商信息&#xff0c;客户信息&#xff0c;进货入库打印&#xff0c;销售出库打印&#xff0c;进货明细或汇总统计查询&#xff0c;销售出库明细或汇总统计查询&#xff0c;库存查询&#xff0c;客户往来账…

NodeJS 安装及环境配置

下载地址&#xff1a;https://nodejs.org/zh-cn/download/ 安装 NodeJS 根据自己电脑系统及位数选择&#xff0c;一般都选择 windows 64位 .msi 格式安装包。 所用命令&#xff1a; node -v npm -v PS&#xff1a;如果以上两条命令都能执行成功&#xff0c;表示安装完成&#…

stable diffusion公司发布4款LLM大语言模型,为何大家都喜爱LLM?

stable diffusion模型是Stability AI开源的一个text-to-image的扩散模型&#xff0c;其模型在速度与质量上面有了质的突破&#xff0c;玩家们可以在自己消费级GPU上面来运行此模型&#xff0c;本模型基于CompVis 和 Runway 团队的Latent Diffusion Models。本期我们不介绍stabl…

Java自学第4课:Java数组,类,对象

1 一维数组的创建和使用 2种创建形式&#xff1a; &#xff08;1&#xff09;先声明&#xff0c;再用new分配内存 &#xff08;2&#xff09;声明的同时分配内存 2种幅值形式 &#xff08;1&#xff09;用new{}赋值 &#xff08;2&#xff09;用{}赋值 如果不使用的话&a…

apb介绍

https://www.cnblogs.com/xianyuIC/p/17279209.html***带testbench https://zhuanlan.zhihu.com/p/623829190?utm_id0 https://zhuanlan.zhihu.com/p/607964532带testbench by四人独行 https://blog.csdn.net/weixin_40377195/article/details/124899571 APB是最简单的AMBA总…

产品手册应该如何组织内容,以便用户能够快速找到所需信息?

产品手册应该如何组织内容&#xff0c;以便用户能够快速找到所需信息&#xff1f;这是一个关乎用户体验和产品文档效力的重要问题。当用户需要了解产品的功能、操作指南或故障排除时&#xff0c;他们希望能够轻松地找到准确、清晰的信息&#xff0c;而不是在冗长的手册中迷失方…

Selenium处理Cookie

01、cookie介绍 HTTP协议是无状态的协议。一旦数据交换完毕&#xff0c;客户端与服务器端的连接就会关闭&#xff0c;再次交换数据需要建立新的连接&#xff0c;这就意味着服务器无法从连接上跟踪会话。也就是说即使第一次和服务器连接后并且登录成功后&#xff0c;第二次请求…

uniapp 离线打包 google 登录

官方文档&#xff1a; Oauth 模块 | uni小程序SDK 其中有 clientid 和反向url clientid 是 xxxx.apps.googleusercontent.com 反向url 是 com.googleusercontent.apps.xxx

【Android】android studio 怎么下载NDK

序言 新版的android studio在【Project Structure】里面的NDK路径是灰色的&#xff0c;无法点击&#xff0c;导致找不到ndk路径&#xff0c;也无法添加ndk。 下载方法 去这里找&#xff0c;一定要点这个按钮才能出现ndk。 下载之后&#xff0c;要在这个文件里面添加ndk路径

HT5010 音频转换器工作原理

HT5010是一款低成B的立体声DA转换器&#xff0c;内部集成了内插滤波器、DA转换器和输出模拟滤波等电路。其可支持多种音频数字输入格式&#xff0c;支持24-bit字节。 该HT5010 基于一个多比特位的Δ-Σ调制器&#xff0c;将数字信号转化成两个声道的模拟信号并经过模拟滤波器滤…

Hadoop环境搭建

1 Hadoop集群环境搭建概述 所谓集群&#xff0c;就是一组通过网络互联的计算机&#xff0c;集群中的每一台计算机称作一个节点&#xff0c;Hadoop集群搭建就是在这个物理集群之上安装部署Hadoop相关的软件&#xff0c;然后对外提供大数据存储和分析等相关服务。 一个前提&…

设置IDEA快捷生成方法头,类头注释

1.File->settings->editor->live templates进入Live Template界面进行设置&#xff1a; 下一步&#xff1a; 下一步&#xff1a; /*** Title: $title$* author: sunyanzeng* date: $datatime$*/在需要添加文件头的地方打出“aa”&#xff0c;回车&#xff0c;会自…