网络协议栈简单设计(udp)

news2025/1/18 3:29:02

网络协议栈简单设计

操作系统内核中实现了网络协议栈,但今天利用netmap(也可利用dpdk)绕过内核协议栈进行网络数据的收发

netmap

  • 内核协议栈加载数据:
20230203224918

数据从网卡到内核再到内存,需要经过两次拷贝

  • netmap映射数据:
20230203225108

直接将网卡数据映射到内存,应用程序可以直接通过mmap操作相应内存的数据,实现0拷贝;

所谓零拷贝,指的是不由CPU操作,copy这个动作是由cpu发出指令move实现的,所以零拷贝就是不由CPU管理,由DMA管理。DMA允许外设与内存直接进行数据传输,这个过程不需要CPU的参与

netmap api:

  1. nm_open():
    • 通过d->fd =open(NETMAP_DEVICE_NAME, O_RDWR);打开一个特殊的设备/dev/netmap来创建文件描述符 d->fd
    • 而这个fd是/dev/netmap这个网卡设备(所以只要有/dev/netmap这个设备,就说明netmap启动成功了),网卡只要来数据了,相应的这个fd就会有EPOLLIN事件,这个fd是检测网卡有没有数据的,因为是mmap,只要网卡有数据了,那么内存就有数据
    • 对于消息检测,一般就是两种方法:
      • 轮询:适合大量数据收发
      • 事件通知:适合数据较为稀疏的情景(netmap采用poll、epoll事件通知)
    • 所以,数据检测是通过fd是指向网卡实现,而操作数据是操作内存中的数据,内存和网卡数据的同步的,而cpu只能操作内存,不能操作外设
    • 调用该函数后,网络数据就不走协议栈了,这时候最好在虚拟机中建两个网卡,一个用于netmap,一个用于ssh等应用程序的正常工作。
  2. nm_nextpkt():
    • 用来接收网卡上到来的数据包,nm_nextpkt会将所有 rx 环都检查一遍,当发现有一个 rx 环有需要接收的数据包时,得到这个数据包的地址,并返回。所以 nm_nextpkt()每次只能取一个数据包。 因为接收到的数据包没有经过协议栈处理,因此需要在用户程序中自己解析。
    • rx:环形缓冲区 参考文章
  3. nm_inject():
    • 往共享内存中写入待发送的数据包数据的。数据包经共享内存拷贝到网卡,然后发送出去
  4. nm_close():
    • 回收动态内存,回收共享内存,关闭文件描述符

基于UDP的协议栈设计

注:只实现udp服务器接收客户端数据,并进行回应的功能

udp包需要从应用层开始经过层层包装:以客户端send(buff)为例,其包装后是:【以太网头】【网络层头】【传输层头】【buff】

而这些头部信息需要我们去定义:

#pragma pack(1)   // 单字节对齐,udp包对齐

struct ethhdr {           // 定义以太网头:源目MAC地址、协议   =》 数据链路层
	unsigned char h_dst[ETH_ADDR_LENGTH];
	unsigned char h_src[ETH_ADDR_LENGTH];
	unsigned short h_proto;
}; // 14

struct iphdr {       // IP头   =》 网络层
	unsigned char hdrlen:4,
				  version:4; // 一个bytes,注意高低位,版本号在前面,这里要写后面
	unsigned char tos;
	unsigned short totlen;

	unsigned short id;
	unsigned short flag_offset; //
	unsigned char ttl; //time to live
	// 0x1234// htons
	unsigned char type;
	unsigned short check;

	unsigned int sip;
	unsigned int dip;
}; // 20

struct udphdr {   // UDP:没有源目IP   =》 传输层
	unsigned short sport;
	unsigned short dport;

	unsigned short length;
	unsigned short check;

}; // 8

/*
    结构体对齐:以最大成员的类型对齐
            结构体中含有数组:只需比较数组值类型
            结构体S2中含有结构体S1:取S1的最大类型与S2的类型比较
*/

struct udppkt {    
	struct ethhdr eh; // 14   =》它后面会有2字节的空间,数据容易读取错误,统一一字节对齐
	struct iphdr ip;  // 20     4直接对齐
	struct udphdr udp; // 8
    // 这里不能用指针,因为包发出去了,带走了指针,但是没带走数据,而且使用也不方便
    // 定义0长数据,其实就是指向传输层尾部的偏移量,数组并不占空间
    // 0长数组经常写在结构体的后面,使得结构体长度可变  =》 需要编译器支持
	unsigned char data[0];    // 应用层数据
};
  • 单字节申明两个变量时,注意大小端顺序
  • 设置单字节对齐,不然udppkt会以4直接对齐,struct ethhdr eh后会有2字节的空缺,造成数据读取出错
  • 设置0长数据,使得udp包可以不定长(udp协议规定不能超过64k,但大小最好不超过MTU)

然后,接收udp包并解析:

int main() {

	struct nm_pkthdr h;    // ringbuff

    // netmap 通过打开一个特殊的设备/dev/netmap来创建文件描述符 d->fd
    // 注意这个fd是/dev/netmap这个网卡设备,网卡只要来数据了,相应的这个fd就会有EPOLLIN事件,这个fd是检测网卡有没有数据的,因为是mmap,只要网卡有数据了,那么内存就有数据的
	struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
	if (nmr == NULL) return -1;

	struct pollfd pfd = {0};
	pfd.fd = nmr->fd;
	pfd.events = POLLIN;

	while (1) {

		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;

		if (pfd.revents & POLLIN) {

			unsigned char *stream = nm_nextpkt(nmr, &h);

			struct ethhdr *eh = (struct ethhdr *)stream;
			if (ntohs(eh->h_proto) ==  PROTO_IP) {

				struct udppkt *udp = (struct udppkt *)stream;   // 网络字节序转本机字节序

				if (udp->ip.type == PROTO_UDP) { // udp包

					int udplength = ntohs(udp->udp.length);

					udp->data[udplength-8] = '\0';    // 去除udp的8个字节,注意这里已经把以太网头和网络层头去掉了

					printf("udp --> %s\n", udp->data);   // 打印包中的应用层数据

				} else if (udp->ip.type == PROTO_ICMP) {
				}	
			} else if (ntohs(eh->h_proto) ==  PROTO_ARP) {  
				
				}
			}
		}
	}

进一步,实现arp协议和ICMP协议:

ARP:

struct arphdr {    // arp头:按照arp协议字段定义即可

	unsigned short h_type;
	unsigned short h_proto;

	unsigned char h_addrlen;
	unsigned char h_protolen;

	unsigned short oper;

	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;
};

// arp回包:源目ip、mac互换
void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *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;

}

int main() {

	struct nm_pkthdr h;    // ringbuff

    // netmap 通过打开一个特殊的设备/dev/netmap来创建文件描述符 d->fd
    // 注意这个fd是/dev/netmap这个网卡设备,网卡只要来数据了,相应的这个fd就会有EPOLLIN事件,这个fd是检测网卡有没有数据的,因为是mmap,只要网卡有数据了,那么内存就有数据的
	struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
	if (nmr == NULL) return -1;

	struct pollfd pfd = {0};
	pfd.fd = nmr->fd;
	pfd.events = POLLIN;

	while (1) {

		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;

		if (pfd.revents & POLLIN) {

			unsigned char *stream = nm_nextpkt(nmr, &h);

			struct ethhdr *eh = (struct ethhdr *)stream;
			if (ntohs(eh->h_proto) ==  PROTO_IP) {

				struct udppkt *udp = (struct udppkt *)stream;   // 网络字节序转本机字节序

				if (udp->ip.type == PROTO_UDP) { // udp包

					int udplength = ntohs(udp->udp.length);

					udp->data[udplength-8] = '\0';    // 去除udp的8个字节,注意这里已经把以太网头和网络层头去掉了

					printf("udp --> %s\n", udp->data);   // 打印包中的应用层数据

				} else if (udp->ip.type == PROTO_ICMP) {

					

				}
				

			} else if (ntohs(eh->h_proto) ==  PROTO_ARP) {  // 因为开启了net_map,就不用系统的协议栈了

				struct arppkt *arp = (struct arppkt *)stream;

				struct arppkt arp_rt;  // 回包

				if (arp->arp.dip == inet_addr("192.168.0.123")) { // 是否是发送给我本机的

					echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");

					nm_inject(nmr, &arp_rt, sizeof(arp_rt));

					printf("arp ret\n");
				
				}

			}

		}

	}
	
}

arp攻击:攻击者通过响应客户端发出的arp请求包,更改arp表的IP-MAC条目,造成网络中断或中间人攻击,在上述代码中,把if (ntohs(eh->h_proto) == PROTO_ARP)去掉就可对别的主机造成arp攻击了

ICMP:

struct icmphdr {
	unsigned char type;
	unsigned char code;
	unsigned short check;
	unsigned short identifier;
	unsigned short seq;
	unsigned char data[32];
};

struct icmppkt {
	struct ethhdr eh;
	struct iphdr ip;
	struct icmphdr icmp;
};

unsigned short in_cksum(unsigned short *addr, int len)
{
	register int nleft = len;
	register unsigned short *w = addr;
	register int sum = 0;
	unsigned short answer = 0;

	while (nleft > 1)  {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(u_char *)(&answer) = *(u_char *)w ;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xffff);	
	sum += (sum >> 16);			
	answer = ~sum;
	
	return (answer);
}

void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt) {

	memcpy(icmp_rt, icmp, sizeof(struct icmppkt));

	icmp_rt->icmp.type = 0x0; //
	icmp_rt->icmp.code = 0x0; //
	icmp_rt->icmp.check = 0x0;

	icmp_rt->ip.saddr = icmp->ip.daddr;
	icmp_rt->ip.daddr = icmp->ip.saddr;

	memcpy(icmp_rt->eh.h_dest, icmp->eh.h_source, ETH_ALEN);
	memcpy(icmp_rt->eh.h_source, icmp->eh.h_dest, ETH_ALEN);

	icmp_rt->icmp.check = in_cksum((unsigned short*)&icmp_rt->icmp, sizeof(struct icmphdr));
	
}

可以看出,协议族一个一个互不影响,独立实现

完整代码



#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <sys/poll.h>
#include <arpa/inet.h>


#define NETMAP_WITH_LIBS

#include <net/netmap_user.h> 
#pragma pack(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_dest[ETH_ALEN];
	unsigned char h_source[ETH_ALEN];
	unsigned short h_proto;
};



struct iphdr {
	unsigned char version;
	unsigned char tos;
	unsigned short tot_len;
	unsigned short id;
	unsigned short flag_off;
	unsigned char ttl;
	unsigned char protocol;
	unsigned short check;
	unsigned int saddr;
	unsigned int daddr;
};


struct udphdr {
	unsigned short source;
	unsigned short dest;
	unsigned short len;
	unsigned short check;
};


struct udppkt {
	struct ethhdr eh;
	struct iphdr ip;
	struct udphdr udp;
	unsigned char body[0];
};



struct arphdr {
	unsigned short h_type;
	unsigned short h_proto;
	unsigned char h_addrlen;
	unsigned char protolen;
	unsigned short oper;
	unsigned char smac[ETH_ALEN];
	unsigned int sip;
	unsigned char dmac[ETH_ALEN];
	unsigned int dip;
};

struct arppkt {
	struct ethhdr eh;
	struct arphdr arp;
};


struct icmphdr {
	unsigned char type;
	unsigned char code;
	unsigned short check;
	unsigned short identifier;
	unsigned short seq;
	unsigned char data[32];
};

struct icmppkt {
	struct ethhdr eh;
	struct iphdr ip;
	struct icmphdr icmp;
};

void print_mac(unsigned char *mac) {
	int i = 0;
	for (i = 0;i < ETH_ALEN-1;i ++) {
		printf("%02x:", mac[i]);
	}
	printf("%02x", mac[i]);
}

void print_ip(unsigned char *ip) {
	int i = 0;

	for (i = 0;i < 3;i ++) {
		printf("%d.", ip[i]);
	}
	printf("%d", ip[i]);
}


void print_arp(struct arppkt *arp) {
	print_mac(arp->eh.h_dest);
	printf(" ");

	print_mac(arp->eh.h_source);
	printf(" ");

	printf("0x%04x ", ntohs(arp->eh.h_proto));
	printf("  ");
	
}

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 *hmac) {

	memcpy(arp_rt, arp, sizeof(struct arppkt));

	memcpy(arp_rt->eh.h_dest, arp->eh.h_source, ETH_ALEN);
	str2mac(arp_rt->eh.h_source, hmac);
	arp_rt->eh.h_proto = arp->eh.h_proto;

	arp_rt->arp.h_addrlen = 6;
	arp_rt->arp.protolen = 4;
	arp_rt->arp.oper = htons(2);
	
	str2mac(arp_rt->arp.smac, hmac);
	arp_rt->arp.sip = arp->arp.dip;
	
	memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ALEN);
	arp_rt->arp.dip = arp->arp.sip;

}


void echo_udp_pkt(struct udppkt *udp, struct udppkt *udp_rt) {

	memcpy(udp_rt, udp, sizeof(struct udppkt));

	memcpy(udp_rt->eh.h_dest, udp->eh.h_source, ETH_ALEN);
	memcpy(udp_rt->eh.h_source, udp->eh.h_dest, ETH_ALEN);

	udp_rt->ip.saddr = udp->ip.daddr;
	udp_rt->ip.daddr = udp->ip.saddr;

	udp_rt->udp.source = udp->udp.dest;
	udp_rt->udp.dest = udp->udp.source;

}

unsigned short in_cksum(unsigned short *addr, int len)
{
	register int nleft = len;
	register unsigned short *w = addr;
	register int sum = 0;
	unsigned short answer = 0;

	while (nleft > 1)  {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(u_char *)(&answer) = *(u_char *)w ;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xffff);	
	sum += (sum >> 16);			
	answer = ~sum;
	
	return (answer);

}


void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt) {

	memcpy(icmp_rt, icmp, sizeof(struct icmppkt));

	icmp_rt->icmp.type = 0x0; //
	icmp_rt->icmp.code = 0x0; //
	icmp_rt->icmp.check = 0x0;

	icmp_rt->ip.saddr = icmp->ip.daddr;
	icmp_rt->ip.daddr = icmp->ip.saddr;

	memcpy(icmp_rt->eh.h_dest, icmp->eh.h_source, ETH_ALEN);
	memcpy(icmp_rt->eh.h_source, icmp->eh.h_dest, ETH_ALEN);

	icmp_rt->icmp.check = in_cksum((unsigned short*)&icmp_rt->icmp, sizeof(struct icmphdr));
	
}


int main() {
	
	struct ethhdr *eh;
	struct pollfd pfd = {0};
	struct nm_pkthdr h;
	unsigned char *stream = NULL;

	struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
	if (nmr == NULL) {
		return -1;
	}

	pfd.fd = nmr->fd;
	pfd.events = POLLIN;

	while (1) {
		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;
		
		if (pfd.revents & POLLIN) {
			stream = nm_nextpkt(nmr, &h);
			eh = (struct ethhdr*)stream;

			if (ntohs(eh->h_proto) == PROTO_IP) {

				struct udppkt *udp = (struct udppkt*)stream;
				if (udp->ip.protocol == PROTO_UDP) {

					struct in_addr addr;
					addr.s_addr = udp->ip.saddr;

					int udp_length = ntohs(udp->udp.len);
					printf("%s:%d:length:%d, ip_len:%d --> ", inet_ntoa(addr), udp->udp.source, 
						udp_length, ntohs(udp->ip.tot_len));

					udp->body[udp_length-8] = '\0';
					printf("udp --> %s\n", udp->body);
#if 1
					struct udppkt udp_rt;
					echo_udp_pkt(udp, &udp_rt);
					nm_inject(nmr, &udp_rt, sizeof(struct udppkt));
#endif
				} else if (udp->ip.protocol == PROTO_ICMP) {
					
					struct icmppkt *icmp = (struct icmppkt*)stream;

					printf("icmp ---------- --> %d, %x\n", icmp->icmp.type, icmp->icmp.check);
					if (icmp->icmp.type == 0x08) {
						struct icmppkt icmp_rt = {0};
						echo_icmp_pkt(icmp, &icmp_rt);

						//printf("icmp check %x\n", icmp_rt.icmp.check);
						nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt));
					}
					
				} else if (udp->ip.protocol == PROTO_IGMP) {

				} else {
					printf("other ip packet");
				}
				
			}  else if (ntohs(eh->h_proto) == PROTO_ARP) {

				struct arppkt *arp = (struct arppkt *)stream;
				struct arppkt arp_rt;

				if (arp->arp.dip == inet_addr("192.168.0.123")) {
					echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");
					nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
				}
			}
		} 
	}
}

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

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

相关文章

List底层源码剖析之List扩容机制

在list集合中有一个add方法&#xff1a; 在众多类中&#xff0c;最长使用的是ArrayList,其中有个方法是add方法 在add方法底层存在 private int size&#xff1b; ensureCapacityInternal(size 1) 其中的size1会对add&#xff08;&#xff09;方法的调用次数进计数&#x…

Docker系列(常用命令) 02

Docker常用命令总结 docker官方命令文档 一、Docker环境信息命令 docker version # 查看docker版本信息 docker info # 查看docker详细信息二、系统日志信息常用命令 2.1 docker events 作用&#xff1a;从服务器获取实时事件&#xff0c;比如&#xff1a;启动、关闭和创…

九龙证券|美国散户疯狂抄底,嗅到了什么?华尔街最新警告

当地时间周五&#xff0c;美股三大指数低开后经历“过山车”行情&#xff0c;虽然盘中一度转涨&#xff0c;但午后再度回落。截至收盘&#xff0c;道指跌0.38%&#xff0c;报收33926.01点&#xff1b;纳指跌1.59%&#xff0c;收于12006.95点&#xff1b;标普500指数跌1.04%&…

九龙证券|全市场注册制下 多层次资本市场定位更清晰

全商场施行注册制的启动&#xff0c;让多层次本钱商场各个板块之间的定位愈加明晰。沪深交易所主板将杰出大盘蓝筹定位&#xff0c;各个板块互联互通也在逐渐加强。 分析人士认为&#xff0c;全面施行股票发行注册制是一场触动本钱商场全局的革新。注册制在全商场推广后&#x…

解读测试能力素质模型

软件测试的能力素质模型(Job Model)&#xff0c;是对不同层级测试工程的能力要求进行明确的定义。目的是为了对每位工程师的能力进行科学的评估&#xff0c;然后分配合理的工作&#xff0c;也帮助大家明确职业规划的方向。 淘宝测试工程师的最常用的有4个&#xff0c;分别是&am…

uniapp(一)

一、初识微信小程序1、什么是微信小程序微信小程序简称小程序&#xff0c;英文名Mini Program&#xff0c;是一种不需要下载安装即可使用的应用&#xff0c;它实现了应用“触手可及”的梦想&#xff0c;用户扫一扫或搜一下即可打开应用小程序是一种新的开放能力&#xff0c;开发…

【Redis学习笔记】主从复制

读写分离&#xff0c;性能扩展&#xff1b;快速容灾恢复 一主两从 准备一台服务器&#xff0c;启动不同的redis端口&#xff0c;6379、6380、6381 连接redis-cli redis-cli查看主从信息 info replication主机6379 从机6380、6381 设置从机 config set masterauth password -…

【青训营】架构初探

单机架构 单机架构是把所有功能都实现在一个进程里&#xff0c;并且部署在一台机器上。优点是简单&#xff0c;但是缺点在于其能够承载的带宽有限&#xff0c;而且进行运行维护必须关停服务器。模块之间相互影响&#xff0c; 单体架构 单体架构和单机架构最大的不同是单体架构…

【JavaEE】认识Tomcat

✨哈喽&#xff0c;大家好&#xff0c;我是辰柒&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaEE】 ✈️✈️本篇内容:如何构造 HTTP 请求同时认识HTTPS&#xff01; &#x1f680;&#x1f680;代码存放仓库github&#xff1a;JavaEE代码&#xff01; ⛵⛵作者…

排序模型进阶-WideDeepWDL模型导出

8.5 排序模型进阶-Wide&Deep 学习目标 目标 无应用 无 8.5.1 wide&deep Wide部分的输入特征&#xff1a; raw input features and transformed featuresnotice: W&D这里的cross-product transformation&#xff1a;只在离散特征之间做组合&#xff0c;不管是文本…

《从0开始学大数据》之构建一个大数据平台

在分布式系统中分发执行代码并启动执行&#xff0c;这样的计算方式必然不会很快&#xff0c;即使在一个规模不太大的数据集上进行一次简单计算&#xff0c;MapReduce 也可能需要几分钟&#xff0c;Spark 快一点&#xff0c;也至少需要数秒的时间。而互联网产品处理用户请求&…

微信短视频怎么提取gif?三步教你在线提取gif动画

现在各大社交软件上短视频是越来越多&#xff0c;为了方便传播、保存可以将短视频制作成GIF。那么&#xff0c;如何从视频中提取动图呢&#xff1f;很简单&#xff0c;两招就能完成在线视频转换成gif动图的操作&#xff0c;只需要使用【GIF中文网】的视频转gif&#xff08;http…

OBS使用WebRTC进行腾讯云推流播流

推流&#xff1a; 首先&#xff1a;OBS想要推送WebRTC格式的推流需要满足以下两点&#xff1a; 1&#xff1a;OBS版本在26及以上 2&#xff1a;需要给OBS安装腾讯云插件&#xff0c;而且只支持Windows版。 OBS下载地址&#xff1a;Download | OBSDownload OBS Studio for W…

电路方案分析(十六)带有C2000微控制器且精度为 ±0.1° 的分立式旋转变压器前端参考设计

带有C2000微控制器且精度为 0.1 的分立式旋转变压器前端参考设计 tips&#xff1a;参考Ti设计资源&#xff1a;TIDA-01527 旋转变压器详细介绍&#xff1a; https://blog.csdn.net/qq_41600018/article/details/127597875&#xff1f;spm1001.2014.3001.5501 该参考方案设计…

用投资思维做好招商工作:湘商回归,长沙急企业之所急

在中国经济发展40年后&#xff0c;当下经济发展的底层逻辑已发生了根本性变化。企业发展所面临的问题&#xff0c;投资所思考的方向也已不同以往。一味再强调本地资源优势&#xff0c;介绍当地优惠政策的招商工作方式不再适应当下形式&#xff0c;往往反而会导致忽略企业的真实…

58.Isaac教程--OTG5 直线运动规划器

OTG5 直线运动规划器 ISAAC教程合集地址文章目录OTG5 直线运动规划器最大值和期望值的配置OTG5 的 Flatsim 演示用于直线运动的在线轨迹生成 - V 型 (OTG5) 规划器允许线性运动&#xff0c;同时明确防止曲线。 这在即使与一般直线运动方向有轻微偏差也会导致意外结果的情况下很…

Redis核心技术-高可靠-集群方案(客户端分片、代理分片、Redis Cluster)

Redis在3.0版本前只支持单实例模式&#xff0c;虽然Redis的开发者Antirez早在博客上就提出在Redis 3.0版本中加入集群的功能&#xff0c;但3.0版本等到2015年才发布正式版。 各大企业等不急了&#xff0c;在3.0版本还没发布前为了解决Redis的存储瓶颈&#xff0c;纷纷推出了各…

【每日一题】【LeetCode】【第二十四天】【Python】两个数组的交集 II

解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 顺着“两个数组的交集”的思路想&#xff0c;先用集合处理nums1和nums2&#xff0c;然后通过“交集”运算得出列表res&#xff0c;然后循环检查列表res&#xff0c;得出各个元素在两个数组中出现的最小次数&…

2022生化原理I复习资料汇总

文章目录1.2022复习重点及参考题2022年考试复习题&#xff1a;附录&#xff1a;参考答案及复习重点2.2021复习重点及参考题3.往年复习重点及参考题汇总4.复习重点整理及考试题型生化原理I复习资料及往年考题1.2022复习重点及参考题 2022年考试复习题&#xff1a; 1.2021-2022…

Python Stock安装与使用

这个是使用python 开发股票系统。 使用 tushare 获取股票数据&#xff0c;然后使用tornado 进行web 展示。 使用pandas numpy 数据处理。 项目代码 项目代码放到github上面 GitHub - pythonstock/stock: stock&#xff0c;股票系统。使用python进行开发。 因为为了简单&#x…