Linux - 进程间通信

news/2024/4/24 10:38:02/文章来源:https://blog.csdn.net/ketil27/article/details/136533472

1、进程间通信介绍

1.1、进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程;
  • 资源共享:多个进程之间共享同样的资源;
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程);
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变;

1.2、进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

1.3、进程间通信分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

2、管道

2.1、什么是管道

  • 管道是Unix中最古老的进程间通信的形式;
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”;

 

2.2、匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

 实例代码

例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main( void )
{int fds[2];char buf[100];int len;if ( pipe(fds) == -1 )perror("make pipe"),exit(1);// read from stdinwhile ( fgets(buf, 100, stdin) ) {len = strlen(buf);// write into pipeif ( write(fds[1], buf, len) != len ) {perror("write to pipe");break;}memset(buf, 0x00, sizeof(buf));// read from pipeif ( (len=read(fds[0], buf, 100)) == -1 ) {perror("read from pipe");break;}// write to stdoutif ( write(1, buf, len) != len ) {perror("write to stdout");break;}}
}

2.2.1、用fork来共享管道原理

2.2.2、站在文件描述符角度-深度理解管道 

2.2.3、站在内核角度-管道本质

 所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>#define ERR_EXIT(m) 
#define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0)int main(int argc, char *argv[])
{ int pipefd[2]; if (pipe(pipefd) == -1) ERR_EXIT("pipe error");pid_t pid;pid = fork();if (pid == -1)ERR_EXIT("fork error");if (pid == 0){close(pipefd[0]);write(pipefd[1], "hello", 5);close(pipefd[1]);exit(EXIT_SUCCESS);}close(pipefd[1]);char buf[10] = {0};read(pipefd[0], buf, 10);printf("buf=%s\n", buf);return 0;
}

例子1. 在minishell中添加管道的实现:

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h># define MAX_CMD 1024
char command[MAX_CMD];int do_face()
{memset(command, 0x00, MAX_CMD);printf("minishell$ ");fflush(stdout);if (scanf("%[^\n]%*c", command) == 0) {getchar();return -1; } return 0;
}char **do_parse(char *buff)
{int argc = 0;static char *argv[32];char *ptr = buff;while(*ptr != '\0') {if (!isspace(*ptr)) {argv[argc++] = ptr;while((!isspace(*ptr)) && (*ptr) != '\0'){ptr++;}}else {while(isspace(*ptr)) {*ptr = '\0';ptr++;}}}argv[argc] = NULL;return argv;
}int do_redirect(char *buff)
{char *ptr = buff, *file = NULL;int type = 0, fd, redirect_type = -1;while(*ptr != '\0') {if (*ptr == '>') {*ptr++ = '\0';redirect_type++;if (*ptr == '>') {*ptr++ = '\0';redirect_type++;}while(isspace(*ptr)){ptr++;}file = ptr;while((!isspace(*ptr)) && *ptr != '\0') {ptr++;}*ptr = '\0';if (redirect_type == 0) {fd = open(file, O_CREAT|O_TRUNC|O_WRONLY, 0664);}else {fd = open(file, O_CREAT|O_APPEND|O_WRONLY, 0664);}dup2(fd, 1);}ptr++;}return 0;
}int do_command(char *buff)
{int pipe_num = 0, i;char *ptr = buff;int pipefd[32][2] = {{-1}};int pid = -1;pipe_command[pipe_num] = ptr;while(*ptr != '\0') {if (*ptr == '|') {pipe_num++;*ptr++ = '\0';pipe_command[pipe_num] = ptr;continue;}ptr++;}pipe_command[pipe_num + 1] = NULL;return pipe_num;
}int do_pipe(int pipe_num)
{int pid = 0, i;int pipefd[10][2] = {{0}};char **argv = {NULL};for (i = 0; i <= pipe_num; i++) {pipe(pipefd[i]);}for (i = 0; i <= pipe_num; i++) {  pid = fork();if (pid == 0) {do_redirect(pipe_command[i]);argv = do_parse(pipe_command[i]);if (i != 0) {close(pipefd[i][1]);dup2(pipefd[i][0], 0);}if (i != pipe_num) {close(pipefd[i + 1][0]);dup2(pipefd[i + 1][1], 1);}execvp(argv[0], argv);}else {close(pipefd[i][0]);close(pipefd[i][1]);waitpid(pid, NULL, 0);}}return 0;
}int main(int argc, char *argv[])
{ int num = 0; while(1) { if (do_face() < 0)continue;num = do_command(command);do_pipe(num);}return 0;
}

2.2.4、管道读写规则

1. 当没有数据可读时

  • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止;
  • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN;

2. 当管道满的时候

  • O_NONBLOCK disable: write调用阻塞,直到有进程读走数据;
  • O_NONBLOCK enable:调用返回-1,errno值为EAGAIN;

3. 如果所有管道写端对应的文件描述符被关闭,则read返回0;

4. 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致          write进程退出;

5. 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性;

6. 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性;

2.2.5、管道特点

  • 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创 建,然后该进程调用fork,此后父、子进程之间就可应用该管道;
  • 管道提供流式服务;
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程;
  • 一般而言,内核会对管道操作进行同步与互斥;
  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;

 

2.3、命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信;
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道;
  • 命名管道是一种特殊类型的文件;

2.3.1、创建一个命名管道

  • 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
$ mkfifo filename
  • 命名管道也可以从程序里创建,相关函数有:
int mkfifo(const char *filename,mode_t mode);

 

创建命名管道:

int main(int argc, char *argv[])
{mkfifo("p2", 0644);return 0;
}

2.3.2、 匿名管道与命名管道的区别

  • 匿名管道由pipe函数创建并打开;
  • 命名管道由mkfifo函数创建,打开用open;
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完 成之后,它们具有相同的语义;

2.3.3、命名管道的打开规则

1. 如果当前打开操作是为读而打开FIFO时:

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO;
  • O_NONBLOCK enable:立刻返回成功;

2. 如果当前打开操作是为写而打开FIFO时:

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO;
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO;

2.4、例子1-用命名管道实现文件拷贝

读取文件,写入命名管道:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \do \{  \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(int argc, char *argv[])
{mkfifo("tp", 0644);int infd;infd = open("abc", O_RDONLY);if (infd == -1)ERR_EXIT("open");int outfd;outfd = open("tp", O_WRONLY);if (outfd == -1) ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);return 0;
}

读取管道,写入目标文件:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(int argc, char *argv[])
{int outfd; outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC,
0644);if (outfd == -1) ERR_EXIT("open");int infd;infd = open("tp", O_RDONLY);if (outfd == -1)ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);unlink("tp");return 0;
}

2.5、例子2-用命名管道实现server&client通信

# ll
total 12
-rw-r--r--. 1 root root 46 Sep 18 22:37 clientPipe.c
-rw-r--r--. 1 root root 164 Sep 18 22:37 Makefile
-rw-r--r--. 1 root root 46 Sep 18 22:38 serverPipe.c# cat Makefile
.PHONY:all
all:clientPipe serverPipe
clientPipe:clientPipe.cgcc -o $@ $^
serverPipe:serverPipe.cgcc -o $@ $^.PHONY:clean
clean:rm -f clientPipe serverPipe

serverPipe.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define ERR_EXIT(m) \do{\perror(m);\exit(EXIT_FAILURE);\}while(0)int main()
{umask(0);if(mkfifo("mypipe", 0644) < 0){ERR_EXIT("mkfifo");}int rfd = open("mypipe", O_RDONLY);if(rfd < 0){ERR_EXIT("open");}char buf[1024];while(1){buf[0] = 0;printf("Please wait...\n");ssize_t s = read(rfd, buf, sizeof(buf)-1);if(s > 0 ){buf[s-1] = 0;printf("client say# %s\n", buf);}else if(s == 0){printf("client quit, exit now!\n");exit(EXIT_SUCCESS);}else{ERR_EXIT("read");}}close(rfd);return 0;
}

clientPipe.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define ERR_EXIT(m) \do{\perror(m);\exit(EXIT_FAILURE);\}while(0)int main()
{int wfd = open("mypipe", O_WRONLY);if(wfd < 0){ERR_EXIT("open");}char buf[1024];while(1){buf[0] = 0;printf("Please Enter# ");fflush(stdout);ssize_t s = read(0, buf, sizeof(buf)-1);if(s > 0 ){buf[s] = 0;write(wfd, buf, strlen(buf));}else if(s <= 0){ERR_EXIT("read");}}close(wfd);return 0;
}

3、system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

 

3.1、共享内存数据结构

struct shmid_ds
{struct     ipc_perm shm_perm; /* operation perms */int     shm_segsz; /* size of segment (bytes) */__kernel_time_t     shm_atime; /* last attach time */__kernel_time_t     shm_dtime; /* last detach time */__kernel_time_t     shm_ctime; /* last change time */__kernel_ipc_pid_t     shm_cpid; /* pid of creator */__kernel_ipc_pid_t     shm_lpid; /* pid of last operator */unsigned short     shm_nattch; /* no. of current attaches */unsigned short     shm_unused; /* compatibility */void     *shm_unused2; /* ditto - used by DIPC */void     *shm_unused3; /* unused */
};

3.2、共享内存函数

shmget函数

功能:用来创建共享内存
原型int shmget(key_t key, size_t size, int shmflg);
参数key:这个共享内存段名字size:共享内存大小shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid: 共享内存标识shmaddr:指定连接的地址shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

  • 说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - 
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型int shmdt(const void *shmaddr);
参数shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数shmid:由shmget返回的共享内存标识码cmd:将要采取的动作(有三个可取值)buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
命令说明
IPC_STAT把 shmid_ds 结构中的数据设置为共享内存的当前关联值
IPC_SET把进程有足够权限的前提下,把共享内存的当前关联值设置为 shmid_ds 数据结构中给出的值
IPC_RMID删除共享内存段

3.3、实例代码

测试代码结构

# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile.PHONY:all
all:server clientclient:client.c comm.cgcc -o $@ $^server:server.c comm.cgcc -o $@ $^.PHONY:clean
clean:rm -f client server
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define PATHNAME "."
#define PROJ_ID 0x6666int createShm(int size);
int destroyShm(int shmid);
int getShm(int size);#endifstatic int commShm(int size, int flags)
{key_t _key = ftok(PATHNAME, PROJ_ID);if(_key < 0){perror("ftok");return -1;}int shmid = 0;if( (shmid = shmget(_key, size, flags)) < 0){perror("shmget");return -2;}return shmid; 
}int destroyShm(int shmid)
{if(shmctl(shmid, IPC_RMID, NULL) < 0){perror("shmctl");return -1;}return 0;
}int createShm(int size)
{return commShm(size, IPC_CREAT|IPC_EXCL|0666);
}int getShm(int size)
{return commShm(size, IPC_CREAT);
}

server.c

#include "comm.h"
int main()
{int shmid = createShm(4096);char *addr = shmat(shmid, NULL, 0);sleep(2);int i = 0;while(i++<26){printf("client# %s\n", addr);sleep(1);}shmdt(addr);sleep(2);destroyShm(shmid);return 0;
}

client.c

#include "comm.h"
int main()
{int shmid = getShm(4096);sleep(1);char *addr = shmat(shmid, NULL, 0);sleep(2);int i = 0;while(i<26){addr[i] = 'A'+i;i++;addr[i] = 0;sleep(1);}shmdt(addr);sleep(2);return 0;
}

ctrl+c终止进程,再次重启

# ./server 
shmget: File exists
# ipcs -m
------ Shared Memory Segments --------
key          shmid     owner     perms     bytes     nattch     status 
0x66026a25   688145    root      666       4096      0 # ipcrm -m 688145 #删除shm ipc资源,注意,不是必须通过手动来删除,这里只为演示相关指令,
删除IPC资源是进程该做的事情

bytesnattch 有时间可以研究一下

注意:共享内存没有进行同步与互斥!

3.4、system V消息队列

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法;
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值;
  • 特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核;

3.5、system V信号量

信号量主要用于同步和互斥的,下面先来看看什么是同步和互斥。

进程互斥

  • 由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥;
  • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源;
  • 在进程中涉及到互斥资源的程序段叫临界区;
  • 特性方面:IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核;

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

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

相关文章

2.5K Star,打造个性化博客平台

2.5K Star&#xff0c;打造个性化博客平台 Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub 指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 导语 在当今的信息时代&a…

Python Tkinter GUI 基本概念

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;如果停止&#xff0c;就是低谷&#xf…

AI 应用之路:质疑汤姆猫,成为汤姆猫,超越汤姆猫

过去一年&#xff0c;我对 AI 应用的看法经历了这样一个过程&#xff1a;质疑汤姆猫&#xff0c;理解汤姆猫&#xff0c;成为汤姆猫&#xff0c;超越汤姆猫。 什么是汤姆猫&#xff1f;汤姆猫是 2010 年移动互联网早期的一款应用&#xff0c;迅速走红&#xff0c;又淡出视野。…

【QT】窗口的大小标题图标设置

窗口的大小标题图标设置 添加一个新的类 创建完成&#xff0c;根据上一节最后的在总结&#xff0c;做个测试&#xff1a; #include "mybutton.h" #include <QDebug>//打印&#xff0c;标准输出 MyButton::MyButton(QWidget *parent) : QPushButton(parent) { …

Python爬虫实战(基础篇)—13获取《人民网》【最新】【国内】【国际】写入Word(附完整代码)

文章目录 专栏导读背景测试代码分析请求网址请求参数代码测试数据分析利用lxml+xpath进一步分析将获取链接再获取文章内容测试代码写入word完整代码总结专栏导读 🔥🔥本文已收录于《Python基础篇爬虫》 🉑🉑本专栏专门针对于有爬虫基础准备的一套基础教学,轻松掌握Py…

1分钟带你搞定Python函数分类

python语言中&#xff0c;函数可以分为内置函数、自定义函数、有参数函数、无参数函数、有名字函数和匿名函数。其中&#xff0c;内置函数可以直接使用&#xff0c;自定义函数需要根据需求定义。有参数函数在定义时需要指定参数&#xff0c;调用时传入参数。无参数函数在定义时…

opengl 学习(一)-----创建窗口

创建窗口 分类opengl 学习(一)-----创建窗口效果解析教程补充 分类 c opengl opengl 学习(一)-----创建窗口 demo: #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>using names…

MySQL实战45讲——30答疑文章(二):用动态的观点看加锁

目录 不等号条件里的等值查询 等值查询的过程 怎么看死锁&#xff1f; 怎么看锁等待&#xff1f; update 的例子 小结 上期问题时间 提示 文章摘自林晓斌老师《MySQL实战45讲》&#xff0c;作为笔记而用&#xff0c;故有加一些自己的理解。在第[20]和[21]篇文章中&…

链路聚合+VRRP

链路聚合---作用&#xff1a; 将多条链路聚合为一条逻辑链路&#xff0c;起到叠加带宽的作用 1.通道对端必须是一个设备 2.聚合的链路带宽必须一致 3.聚合的链路配置必须一致---华为设备为了保证聚合的链路配置一致&#xff0c;限制如果聚合的链路存在配 置&#xff0c;则不…

使用 Docker 部署 MrDoc 在线文档管理系统

1&#xff09;MrDoc 介绍 MrDoc 简介 MrDoc 觅思文档&#xff1a;https://mrdoc.pro/ MrDoc 使用手册&#xff1a;https://doc.mrdoc.pro/p/user-guide/ MrDoc 可以创建各类私有化部署的文档应用。你可以使用它进行知识管理、构建团队文库、制作产品手册以及在线教程等。 Mr…

存储架构 NAS 与 SAN:有什么区别?

SAN&#xff08;Storage Area Network&#xff09;和NAS&#xff08;Network Attached Storage&#xff09;是两种存储架构&#xff0c;它们在数据存储和管理方面有着不同的设计理念和应用场景。SAN通常将存储设备连接到一个独立的高速网络&#xff0c;而NAS则通过普通的网络协…

Java:JVM基础

文章目录 参考JVM内存区域程序计数器虚拟机栈本地方法栈堆方法区符号引用与直接引用运行时常量池字符串常量池直接内存 Hotspot虚拟机对象创建过程虚拟机对象的内存布局对象访问 class文件结构 类加载过程加载验证准备解析初始化使用卸载 参考 JavaGuide JVM内存区域 程序计数…

小白在VMware Workstation Pro上安装部署SinoDB V16.8

一、安装环境说明 CPU&#xff1a;2核或以上&#xff0c;内存&#xff1a;2G或以上&#xff1b;磁盘10G或以上&#xff1b;网卡&#xff1a;千兆 1.1检查服务器内存大小 命令&#xff1a;free -m 1.2检查服务器磁盘空间大小 命令&#xff1a;df -h 1.3检查服务器网络配置信息 命…

antvX6 - Vue自定义节点,并实现多种画布操作,拖拽、缩放、连线、双击、检索等等

一、 首先 antv x6 分为两个版本 低版本和高版本 我这里是使用的2.0版本 并且搭配了相关插件 例如&#xff1a;画布的图形变换、地图等 个人推荐 2.0版本&#xff0c;高版本配置多&#xff0c;可使用相关插件多&#xff0c;但是文档描述小&#xff0c;仍在更新&#xff0c; 低…

(正规api接口代发布权限)短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态

短视频账号矩阵系统实现开发--技术全自动化saas营销链路生态源头开发&#xff08;本篇禁止抄袭复刻&#xff09; 一、短视频矩阵系统开发者架构 云罗短视频矩阵系统saas化系统&#xff0c;开发层将在CAP原则基础上使用分布式架构,对此网站的整体架构采用了基于B/S三层架构模式…

R语言数据可视化之美专业图表绘制指南(增强版):第1章 R语言编程与绘图基础

第1章 R语言编程与绘图基础 目录 第1章 R语言编程与绘图基础前言1.1 学术图表的基本概念1.1.1 学术图表的基本作用1.1.2基本类别1.1.3 学术图表的绘制原则 1.2 你为什么要选择R1.3 安装 前言 这是我第一次在博客里展示学习中国作者的教材的笔记。我选择这本书的依据是作者同时…

input输入框的23中类型

HTML 的 <input> 元素支持多种类型&#xff0c;这些类型决定了用户如何与表单控件进行交互。以下是 HTML5 中 <input> 元素的 23 种类型&#xff0c;以及每种类型的代码示例和效果图的描述&#xff08;请注意&#xff0c;由于文本的限制&#xff0c;我无法直接在这…

STM32day2

1.思维导图 个人暂时的学后感&#xff0c;不一定对&#xff0c;没什么东西&#xff0c;为做项目奔波中。。。1.使用ADC采样光敏电阻数值&#xff0c;如何根据这个数值调节LED灯亮度。 while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */adc_val HAL_ADC_GetValue(&a…

Hello C++ (c++是什么/c++怎么学/c++推荐书籍)

引言 其实C基础语法基本上已经学完&#xff0c;早就想开始写C的博客了&#xff0c;却因为其他各种事情一直没开始。原计划是想讲Linux系统虚拟机安装的&#xff0c;后来考虑了一下还是算了&#xff0c;等Linux学到一定程度再开始相关博客的写作和发表吧。今天写博客想给C开个头…

JS函数

目录 1.Function声明 2.匿名函数 3.函数表达式 4.箭头函数 5.构造函数 个人版JS函数使用&#xff1a; 函数的声明&#xff1a;函数如果有return则返回的是 return 后面的值&#xff0c;如果函数没有有return 声明方式一&#xff1a; 声明方式二&#xff1a;变量名声明…