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

news2024/12/23 18:09:05

什么是协议栈呢?

(协议栈(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 service

	unsigned 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// htons

	unsigned 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	2


struct 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 service

	unsigned 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// htons

	unsigned 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.coloradmin.cn/o/1171665.html

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

相关文章

开源 Wiki 软件 wiki.js

wiki.js简介 最强大、 可扩展的开源Wiki 软件。使用 Wiki.js 美观直观的界面让编写文档成为一种乐趣&#xff01;根据 AGPL-v3 许可证发布。 官方网站&#xff1a;https://js.wiki/ 项目地址&#xff1a;https://github.com/requarks/wiki 主要特性&#xff1a; 随处安装&a…

【从零开始学习Redis | 第四篇】基于延时双删对Cache Aside的优化

前言&#xff1a; 在如今的单体项目中&#xff0c;为了减轻大量相同请求对数据库的压力&#xff0c;我们采取了缓存中间件Redis。核心思想为&#xff1a;把数据写入到redis中&#xff0c;在查询的时候&#xff0c;就可以直接从Redis中拿取数据&#xff0c;这样我们原本对数据库…

Microsoft Dynamics 365 CE 扩展定制 - 5. 外部集成

本章内容包括: 使用.NET从其他系统连接到Dynamics 365使用OData(Java)从其他系统连接到Dynamics 365使用外部库从外部源检索数据使用web应用程序连接到Dynamics 365运行Azure计划任务设置Azure Service Bus终结点与Azure Service Bus构建近乎实时的集成使用来自Azure服务总线…

单目标应用:粒子群优化算法(PSO)求解微电网优化MATLAB

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、粒子群优化算法&#xff08;PSO&#xff09;求解微电网优化 &#xff08;1&#xff09;部分代码 close all; clear ; clc; global P_load; %电负荷 gl…

[SSD综述 1.4] SSD固态硬盘的架构和功能导论

依公知及经验整理,原创保护,禁止转载。 专栏 《SSD入门到精通系列》 <<<< 返回总目录 <<<< ​ 前言 机械硬盘的存储系统由于内部结构, 其IO访问性能无法进一步提高,CPU与存储器之间的性能差距逐渐扩大。以Nand Flash为存储介质的固态硬盘技术的发展,…

mooc单元测验第一单元

TCP和OSI参考模型对比 OSI参考模型与TCP/IP参考模型(计算机网络)_osi模型 tcpip模型_李桥桉的博客-CSDN博客 会话层和物理层

分享女儿的冬季时尚穿搭~经典款的羽绒服

分享女儿冬季的时尚穿搭—羽绒服 经典百搭的时尚款 90白鸭绒&#xff0b;杜邦三防工艺&#xff0b;精细走线&#xff0b;腰部抽绳 满满的设计感&#xff0c;穿上简直不要太好看啊&#xff01;

计算机组成与结构-安全性和可靠性

系统可靠性分析 概念 平均无故障时间 MTTF 1/失效率 平均故障修复时间 MTTR1/修复率 平均故障间隔时间 MTBFMTTFMTTR 系统可用性 MTTF/(MTTFMTTR)*100% 计算 串联系统 一个设备不可靠&#xff0c;整个系统崩溃RR1R2Rn 并联系统 所有设备不可靠&#xff0c;整个系统崩溃R1-(1…

数据结构(超详细讲解!!)第二十一节 特殊矩阵的压缩存储

1.压缩存储的目标 值相同的元素只存储一次 压缩掉对零元的存储&#xff0c;只存储非零元 特殊形状矩阵&#xff1a; 是指非零元&#xff08;如值相同的元素&#xff09;或零元素分布具有一定规律性的矩阵。 如&#xff1a; 对称矩阵 上三角矩阵 下三角矩阵 对角矩阵 准…

汽车标定技术(二)--基于XCP的标定测量实战

目录 1.工程创建 1.1 新建工程 1.2 设备配置 1.3 标定观测 1.4 刷写 2.原始hex文件与标定文件的合并 2.1 修改memory segment file 2.2 标定量地址偏移 ​编辑 2.3 标定后与原始hex文件合并 2.4 标定后直接merge 2.5 不用对ram地址进行偏移实现hex文件合并 本文使用…

算法?认识一下啦

一、什么是算法&#xff1f; 算法 &#xff0c;是对特定问题求解方法和步骤的一种描述。它是有限指令的有限序列&#xff0c;其中每个指令表示一个或多个操作。 算法和程序的关系 算法​是解决问题的一种方法或一个过程&#xff0c;考虑如何将输入转换成输出&#xff0c;一个…

小程序如何设置用户同意服务协议并上传头像和昵称

为了保护用户权益和提供更好的用户体验&#xff0c;设置一些必填项和必读协议是非常必要的。首先&#xff0c;用户必须阅读服务协议。服务协议是明确规定用户和商家之间权益和义务的文件。通过要求用户在下单前必须同意协议&#xff0c;可以确保用户在使用服务之前了解并同意相…

Android Studio布局

线性布局 水平或竖直排列子元素的布局容器 相对布局 可针对容器内每个子元素设置相对位置&#xff08;相对于父容器或同级子元素的位置&#xff09; 网格布局 找了下面这篇文章连接可以参考&#xff08;不再赘述&#xff09; GridLayout(网格布局) | 菜鸟教程 (runoob.com) …

【原创】java+swing+mysql校园共享单车管理系统设计与实现

摘要&#xff1a; 校园共享单车作为一种绿色、便捷的出行方式&#xff0c;在校园内得到了广泛的应用。然而&#xff0c;随着单车数量的增加&#xff0c;管理难度也不断加大。如何提高单车的利用率和管理效率&#xff0c;成为校园共享单车发展面临的重要问题。本文针对这一问题…

Python武器库开发-常用模块之collections模块(十七)

常用模块之collections模块(十七) 除python提供的内置数据类型&#xff08;int、float、str、list、tuple、dict&#xff09;外&#xff0c;collections模块还提供了其他数据类型: 计数器&#xff08;counter&#xff09;有序字典&#xff08;orderedDict&#xff09;可命名元…

基于动力学模型的机械臂滑膜控制

一、滑模控制设计思路 参考资料&#xff1a;https://zhuanlan.zhihu.com/p/463230163&#xff08;思路理解&#xff09; https://blog.csdn.net/xiaohejiaoyiya/article/details/90271529&#xff08;干扰的处理&#xff09; 滑模控制的思路有两个关键&#xff0c;一个是设计…

【C++】多态 ⑫ ( 多继承 “ 弊端 “ | 多继承被禁用的场景 | 菱形继承结构的二义性 | 使用虚继承解决菱形继承结构的二义性 )

文章目录 一、多继承 " 弊端 "1、多继承被禁用的场景2、多继承弊端 二、代码示例 - 多继承弊端1、错误示例 - 菱形继承结构的二义性2、代码示例 - 使用虚继承解决菱形继承结构的二义性 一、多继承 " 弊端 " 1、多继承被禁用的场景 禁止使用多继承的场景 : …

LV.12 D16 轮询与中断 学习笔记

一、CPU与硬件的交互方式 轮询 CPU执行程序时不断地询问硬件是否需要其服务&#xff0c;若需要则给予其服务&#xff0c;若不需要一段时间后再次询问&#xff0c;周而复始 中断 CPU执行程序时若硬件需要其服务&#xff0c;对应的硬件给CPU发送中断信号&#xff0c…

如何使用Python和Matplotlib创建双Y轴动态风格折线图 | 数据可视化教程

前言 我的科研论文中需要绘制一个精美的折线图&#xff0c;我的折线图中有三条曲线&#xff0c;分别表示期望角速度指令信号&#xff0c;和实际的角速度信号&#xff0c;还有实际的航向角信号&#xff0c;现在我已经拥有了数据&#xff0c;使用Python中matplotlib.plt.plot来直…

Java之图书管理系统

&#x1f937;‍♀️&#x1f937;‍♀️&#x1f937;‍♀️ 今天给大家分享一下Java实现一个简易的图书管理系统&#xff01; 清风的个人主页&#x1f389;✏️✏️ &#x1f302;c/java领域新星创作者 &#x1f389;欢迎&#x1f44d;点赞✍评论❤️收藏 &#x1f61b;&…