163 Linux C++ 通讯架构实战17,本地套接字整理对比,IPC:pipe,fifo,mmap,信号,本地套

news2024/10/7 7:29:07

IPC:

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

IPC 技术有如下几种:pipe,fifo,mmap,信号,本地套

pipe:血缘关系的进程之间,完成数据传递

 int pipe(int pipefd[2]);

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。

规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。

fifo:可以用于无血缘进程间的通讯

int mkfifo(const char *pathname,  mode_t mode);  成功:0; 失败:-1

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。

mmap 使用共享存储映射 重点,效率高

void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);

mmap 的替换方案,匿名映射

int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);

    "4"随意举例,该位置表大小,可依实际需要填写。

本地套(C/S 模型)

ocket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。UNIX Domain Socket也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。

UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIXDomain Socket通讯的。

使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。

UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。

对比网络套接字地址结构和本地套接字地址结构:

struct sockaddr_in {

        __kernel_sa_family_t sin_family; /* Address family */   地址结构类型

        __be16 sin_port;   /* Port number */ 端口号

        struct in_addr sin_addr; /* Internet address */ IP地址

};

struct sockaddr_un {

        __kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型

        char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)

};

以下程序将UNIX Domain socket绑定到一个地址。

	size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
	#define offsetof(type, member) ((int)&((type *)0)->MEMBER)

server代码

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>

#define QLEN 10
/*
* Create a server endpoint of a connection.
* Returns fd if all OK, <0 on error.
*/
int serv_listen(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;

	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);
	/* in case it already exists */
	unlink(name); 			

	/* fill in socket address structure */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);

	/* bind the name to the descriptor */
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}
	if (listen(fd, QLEN) < 0) { /* tell kernel we're a server */
		rval = -3;
		goto errout;
	}
	return(fd);

errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int serv_accept(int listenfd, uid_t *uidptr)
{
	int clifd, len, err, rval;
	time_t staletime;
	struct sockaddr_un un;
	struct stat statbuf;

	len = sizeof(un);
	if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
		return(-1); /* often errno=EINTR, if signal caught */

	/* obtain the client's uid from its calling address */
	len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
	un.sun_path[len] = 0; /* null terminate */

	if (stat(un.sun_path, &statbuf) < 0) {
		rval = -2;
		goto errout;
	}
	if (S_ISSOCK(statbuf.st_mode) == 0) {
		rval = -3; /* not a socket */
		goto errout;
	}
	if (uidptr != NULL)
		*uidptr = statbuf.st_uid; /* return uid of caller */
	/* we're done with pathname now */
	unlink(un.sun_path); 
	return(clifd);

errout:
	err = errno;
	close(clifd);
	errno = err;
	return(rval);
}
int main(void)
{
	int lfd, cfd, n, i;
	uid_t cuid;
	char buf[1024];
	lfd = serv_listen("foo.socket");

	if (lfd < 0) {
		switch (lfd) {
			case -3:perror("listen"); break;
			case -2:perror("bind"); break;
			case -1:perror("socket"); break;
		}
		exit(-1);
	}
	cfd = serv_accept(lfd, &cuid);
	if (cfd < 0) {
		switch (cfd) {
			case -3:perror("not a socket"); break;
			case -2:perror("a bad filename"); break;
			case -1:perror("accept"); break;
		}
		exit(-1);
	}
	while (1) {
r_again:
		n = read(cfd, buf, 1024);
		if (n == -1) {
		if (errno == EINTR)
		goto r_again;
	}
	else if (n == 0) {
		printf("the other side has been closed.\n");
		break;
	}
	for (i = 0; i < n; i++)
		buf[i] = toupper(buf[i]);
		write(cfd, buf, n);
	}
	close(cfd);
	close(lfd);
	return 0;
}

client代码

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>

#define CLI_PATH "/var/tmp/" /* +5 for pid = 14 chars */
/*
* Create a client endpoint and connect to a server.
* Returns fd if all OK, <0 on error.
*/
int cli_conn(const char *name)
{
	int fd, len, err, rval;
	struct sockaddr_un un;

	/* create a UNIX domain stream socket */
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
		return(-1);

	/* fill socket address structure with our address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
	len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);

	/* in case it already exists */
	unlink(un.sun_path); 
	if (bind(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -2;
		goto errout;
	}

	/* fill socket address structure with server's address */
	memset(&un, 0, sizeof(un));
	un.sun_family = AF_UNIX;
	strcpy(un.sun_path, name);
	len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
	if (connect(fd, (struct sockaddr *)&un, len) < 0) {
		rval = -4;
		goto errout;
	}
return(fd);
	errout:
	err = errno;
	close(fd);
	errno = err;
	return(rval);
}
int main(void)
{
	int fd, n;
	char buf[1024];

	fd = cli_conn("foo.socket");
	if (fd < 0) {
		switch (fd) {
			case -4:perror("connect"); break;
			case -3:perror("listen"); break;
			case -2:perror("bind"); break;
			case -1:perror("socket"); break;
		}
		exit(-1);
	}
	while (fgets(buf, sizeof(buf), stdin) != NULL) {
		write(fd, buf, strlen(buf));
		n = read(fd, buf, sizeof(buf));
		write(STDOUT_FILENO, buf, n);
	}
	close(fd);
	return 0;
}

对比 网络编程

广播

当我们绑定的IP地址的最后一位是255时,表示这是一个广播

192.168.31.255

类似这样的需求,局域网内,老师上课,下面的同学每个人都有一个电脑,可以显示老师屏幕上的东西,实际上大部分都用的UDP,和广播实现的。

server

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERVER_PORT 8000                                    /* 无关紧要 */
#define MAXLINE 1500

#define BROADCAST_IP "192.168.42.255"
#define CLIENT_PORT 9000                                    /* 重要 */

int main(void)
{
    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    char buf[MAXLINE];

    /* 构造用于UDP通信的套接字 */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;                        /* IPv4 */
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);         /* 本地任意IP INADDR_ANY = 0 */
    serveraddr.sin_port = htons(SERVER_PORT);

    bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    int flag = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));

    /*构造 client 地址 IP+端口  192.168.7.255+9000 */
    bzero(&clientaddr, sizeof(clientaddr));
    clientaddr.sin_family = AF_INET;
    inet_pton(AF_INET, BROADCAST_IP, &clientaddr.sin_addr.s_addr);
    clientaddr.sin_port = htons(CLIENT_PORT);

    int i = 0;
    while (1) {
        sprintf(buf, "Drink %d glasses of water\n", i++);
        //fgets(buf, sizeof(buf), stdin);
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
        sleep(1);
    }
    close(sockfd);
    return 0;
}

client

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_PORT 8000
#define MAXLINE 4096
#define CLIENT_PORT 9000

int main(int argc, char *argv[])
{
    struct sockaddr_in localaddr;
    int confd;
    ssize_t len;
    char buf[MAXLINE];

    //1.创建一个socket
    confd = socket(AF_INET, SOCK_DGRAM, 0);

    //2.初始化本地端地址
    bzero(&localaddr, sizeof(localaddr));
    localaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr);
    localaddr.sin_port = htons(CLIENT_PORT);

    int ret = bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr));  //显示绑定不能省略
    if (ret == 0)
        printf("...bind ok...\n");

    while (1) {
        len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0);
        write(STDOUT_FILENO, buf, len);
    }
    close(confd);

    return 0;
}

组播

这是相对于广播来说的一个概念,广播是对所有的,组播是一部分

组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。

224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;

224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。

224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;

239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。

可使用ip ad命令查看网卡编号,如:

itcast$ ip ad

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default

    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

    inet 127.0.0.1/8 scope host lo

       valid_lft forever preferred_lft forever

    inet6 ::1/128 scope host

       valid_lft forever preferred_lft forever

2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000

    link/ether 00:0c:29:0a:c4:f4 brd ff:ff:ff:ff:ff:ff

    inet6 fe80::20c:29ff:fe0a:c4f4/64 scope link

       valid_lft forever preferred_lft forever

if_nametoindex 命令可以根据网卡名,获取网卡序号。

    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

    server:获取组播权限。

        setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));

    client:将本客户端加入组播。

        setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);    

server

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERVER_PORT 8000
#define CLIENT_PORT 9000
#define MAXLINE 1500

#define GROUP "239.0.0.2"

int main(void)
{
    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    char buf[MAXLINE] = "itcast\n";
    struct ip_mreqn group;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);                /* 构造用于UDP通信的套接字 */
    
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;                        /* IPv4 */
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);         /* 本地任意IP INADDR_ANY = 0 */
    serveraddr.sin_port = htons(SERVER_PORT);

    bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    inet_pton(AF_INET, GROUP, &group.imr_multiaddr);        /* 设置组地址 */
    inet_pton(AF_INET, "0.0.0.0", &group.imr_address);      /* 本地任意IP */
    group.imr_ifindex = if_nametoindex("eth0");             /* 给出网卡名,转换为对应编号: eth0 --> 编号  命令:ip ad */

    setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));  /* 组播权限 */

    bzero(&clientaddr, sizeof(clientaddr));                 /* 构造 client 地址 IP+端口 */
    clientaddr.sin_family = AF_INET;
    inet_pton(AF_INET, GROUP, &clientaddr.sin_addr.s_addr); /* IPv4  239.0.0.2+9000 */
    clientaddr.sin_port = htons(CLIENT_PORT);

    int i = 0;
    while (1) {
        sprintf(buf, "itcast %d\n", i++);
        //fgets(buf, sizeof(buf), stdin);
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
        sleep(1);
    }

    close(sockfd);

    return 0;
}

client

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

#define SERVER_PORT 8000
#define CLIENT_PORT 9000

#define GROUP "239.0.0.2"

int main(int argc, char *argv[])
{
    struct sockaddr_in localaddr;
    int confd;
    ssize_t len;
    char buf[BUFSIZ];

    struct ip_mreqn group;                                                  /* 组播结构体 */

    confd = socket(AF_INET, SOCK_DGRAM, 0);

    bzero(&localaddr, sizeof(localaddr));                                   /* 初始化 */
    localaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr);
    localaddr.sin_port = htons(CLIENT_PORT);

    bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr));

    inet_pton(AF_INET, GROUP, &group.imr_multiaddr);                        /* 设置组地址 */
    inet_pton(AF_INET, "0.0.0.0", &group.imr_address);                      /* 使用本地任意IP添加到组播组 */
    group.imr_ifindex = if_nametoindex("eth0");                             /* 通过网卡名-->编号 ip ad */
    
    setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));/* 设置client 加入多播组 */

    while (1) {
        len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0);
        write(STDOUT_FILENO, buf, len);
    }
    close(confd);

    return 0;
}

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

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

相关文章

【C语言】——指针八:指针运算笔试题解析

【C语言】——指针八&#xff1a;指针运算笔试题解析 一、题一二、题二三、题三四、题四五、题五六、题六七、题七 一、题一 //程序输出结果是什么 int main() {int a[5] { 1,2,3,4,5 };int* ptr (int*)(&a 1);printf("%d, %d", *(a 1), *(ptr - 1));return…

基于JAVA+SSM+微信小程序+MySql的图书捐赠管理系统设计与实现(前后端分类)

一、项目背景介绍&#xff1a; 在当今社会&#xff0c;图书捐赠是一种普遍而有益的行为&#xff0c;旨在促进阅读、教育和知识传播。图书捐赠可以帮助改善教育资源不足的地区、学校和社区的阅读环境&#xff0c;提供更多的学习机会和知识获取途径。随着互联网和移动技术的发展&…

Java 面试宝典:Redis 的线程模型是怎么样的?

大家好&#xff0c;我是大明哥&#xff0c;一个专注「死磕 Java」系列创作的硬核程序员。 本文已收录到我的技术网站&#xff1a;https://www.skjava.com。有全网最优质的系列文章、Java 全栈技术文档以及大厂完整面经 Redis 的线程模型其实是分两块的&#xff1a; Redis 6.0 …

PostgreSQL:所有支持的数据类型及建表语句实例

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在当今这个数据驱动的时代&#xff0c;数据库已经成为了企业和个人不可或缺的工具。而在众多数据库产品中&#xff0c;PostgreSQL以其强大的功能和高度的可扩展性&#xff0c;受到了越来越多开发者的青睐。…

FreeRTOSFreeRTOS列表和列表项

FreeRTOS列表和列表项 今天继续跟着正点原子学习FreeRTOS列表和列表项的内容。列表和列表项这个知识点用到了C语言链表的知识点。所以必须对C语言中的链表这个数据结构才能更好的理解这部分内容。TIPS&#xff1a;正点原子这节课内容讲的特别好&#xff0c;强烈推荐&#xff1…

《QT实用小工具·十八》高亮发光按钮控件

1、概述 源码放在文章末尾 该项目实现了高亮发光按钮控件 可设置文本&#xff0c;居中显示。可设置文本颜色。可设置外边框渐变颜色。可设置里边框渐变颜色。可设置背景色。可直接调用内置的设置 绿色、红色、黄色、黑色、蓝色 等公有槽函数。可设置是否在容器中可移动&#…

2024 抖音欢笑中国年(二):AnnieX互动容器创新玩法解析

本文基于24年抖音春节活动业务背景&#xff0c;介绍了字节跨端容器AnnieX在游戏互动套件上的探索&#xff0c;致力于提升容器在游戏互动场景的优化能力。 业务背景 AnnieX作为字节一方游戏统一容器&#xff0c;服务字节内部电商、直播、UG等跨端场景业务。在字节一方游戏互动场…

YOLOv8模型剪枝实战:Network Slimming网络瘦身方法

课程链接&#xff1a;YOLOv8模型剪枝实战&#xff1a;Network Slimming网络瘦身方法_在线视频教程-CSDN程序员研修院 YOLOv8是一个当前非常流行的目标检测器&#xff0c;本课程使用Network Slimming&#xff08;网络瘦身&#xff09;剪枝方法对YOLOv8进行模型剪枝&#xff0c;…

springboot国际化多语言

1,新建国际化多语言文件 在resources目录下新建 messages.properties 其他语言的文件 编辑messages.properties文件,下方从text切换到Resource Bundle ,即可对照着编辑多语言文件 (如果没有找到Resource Bundle,先在settings->plugins中安装Resource Bundle Editor) 2,配…

学习 MongoDB:打开强大的数据库技术大门

一、基本概念 MongoDB 是一个基于分布式文件存储的文档数据库&#xff0c;由 C 语言编写。它旨在为 Web 应用提供可扩展的高性能数据存储解决方案。 相信MySQL我们非常的熟悉&#xff0c;那么MySQL的表结构与MongoDB的文档结构进行类比的话可能更好理解MongoDB。 MySQL的数据…

Spyder无法载入(load)或者闪退问题

在Anaconda prompt中直接输入spyder&#xff0c;报错如下 Traceback (most recent call last):File "C:\Users\user\.conda\envs\KB\Scripts\spyder-script.py", line 10, in sys.exit(main())File "C:\Users\user\.conda\envs\KB\lib\site-packages\spyder\a…

baseline SE SP YI是什么?

SE、SP和YI是评估分类模型性能时常用的几个统计指标&#xff0c;特别是在医学影像处理、疾病诊断等领域&#xff0c;这些指标帮助了解模型对于正负类样本的识别能力。 SE (Sensitivity)&#xff0c;也称为真正率&#xff08;True Positive Rate, TPR&#xff09;或召回率&#…

泛型(java学习)

目录 1.泛型介绍&#xff1a; 2.泛型的好处&#xff1a; 3.泛型的语法 4.泛型的细节 5.自定义泛型 6.自定义泛型接口 8.泛型的继承和通配符 1.泛型介绍&#xff1a; 1&#xff09;泛型又称参数化类型&#xff0c;解决数据类型的安全性问题。 2&#xff09;在类声明或实例…

5.3 用栈翻转数组,动态规划求斐波那契数列

5.3 用栈翻转数组&#xff0c;动态规划求斐波那契数列 1. 用栈翻转数组 assume cs:code,ds:data,ss:stack data segmentarr dw 1111h,2222h,3333h,4444h,5555h,6666h,7777h,8888hres db 800 dup(0) data endsstack segmentdb 100 dup(0) stack endscode segmentstart:mov ax,…

计算机视觉入门:开启图像理解之旅

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

【算法篇】三道题理解算法思想——认识BFS

BFS&#xff08;宽搜&#xff09; 宽度优先遍历和深度优先遍历组成了大家熟悉的搜索算法&#xff0c;这两种算法也是蓝桥杯之类竞赛题的常考思想&#xff0c;正巧马上蓝桥杯临近&#xff0c;博主也是刷了很多BFS相关的题型&#xff0c;在这篇文章中会从力扣上选取三道简单的宽搜…

小象超市(原美团买菜) 的大屏图表

文章目录 概要技术细节技术名词解释小结 概要 20203年12月1日&#xff0c;美团旗下自营零售品牌“美团买菜”升级为全新品牌“小象超市”。 &#xff0c;“小象超市”坚持美团自营零售模式&#xff0c;通过在社区设立的集存储、分拣、配送为一体的便民服务站&#xff0c;为社区…

关于16:9和4:3的有关知识,看这篇文章就差不多了

序言 在你拍照或录制视频之前,你需要考虑使用哪个纵横比。最常见的两种纵横比是16:9和4:3,但哪一种最适合你? 16:9的纵横比最适合视频,因为宽度比高度宽78%,这使你更容易在水平方向上适应更多画面,同时优化视频以适应现代屏幕。 同时,4:3的纵横比更适合摄影,因为宽度…

书籍《笔记的方法》读后感

读完《笔记的方法》有几周的时间&#xff0c;书里有些记录的内容&#xff0c;觉得非常有价值的&#xff0c;自己的观点&#xff0c;当下读书&#xff0c;其实并没有那么高大尚&#xff0c;就是存粹陶冶下情操&#xff0c;读书还是有一定作用的&#xff0c;毕竟看书只能慢慢来&a…

【PDF-XSS攻击】Java项目-上传文件-解决PDF文件XSS攻击

文章目录 背景解决pdfbox依赖控制器代码PdfUtils工具类 验证最后源码参考 背景 上传xss-pdf造成存储型xss因为在浏览器直接预览的PDF&#xff0c;而不是预览&#xff0c;所以安全部门认为会有XSS漏洞 解决 安全部门修复建议 1、根据白名单的标签和属性对数据进行过滤&#…