Linux之进程间通信——管道

文章目录

  • 前言
  • 一、进程间通信
    • 1.概念
    • 2.目的
    • 3.进程间通信分类
  • 二、管道
    • 1.管道介绍
    • 2.管道分类
      • 1.匿名管道
        • pipi
        • 创建管道文件,打开读写端
        • fork子进程
        • 关闭父进程的读取端,关闭子进程的写入端
        • 读写特征
        • 管道特征
      • 2.命名管道
        • mkfifo
        • 创建管道文件
        • 删除管道文件
        • 通信
  • 三、匿名管道和命名管道的区别和联系
  • 总结


前言

管道是Linux中最古老的进程间通信的方式,本文介绍了进程间通信的相关概念,主要介绍了匿名管道和命名管道。


一、进程间通信

1.概念

什么是进程间通信?

进程具有独立性,每个进程之间是互不干扰的状态,但是一个大的项目,不会只让一个进程独立完成所有工作,所以进程间是一定会有通信的情况,同时进程间通信的成本一定不低(通信的本质:OS需要直接或间接给通信双方的进程提供“内存空间”,而要通信的进程,必须看到一份公共的资源)。
成本不低的原因:我们需要让不同的进程看到同一份资源。

2.目的

进程间通信的目的是:

  • 数据传输:一个进程需要将它的数据发送给另一个进程;
  • 资源共享:多个进程之间共享同一个资源;
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某件事(例如:子进程终止时,要通知父进程);
  • 进程控制:有的进程希望完全控制另一个进程大的执行(例如:Debug进程)。

为什么需要进程间通信?
有时候我们需要多进程协同,共同完成某种业务内容。例如:管道。

3.进程间通信分类

我们所说的不同通信种类本质上是按照:上面所说的资源是OS中的哪一个模块提供的来划分的。如:文件系统提供的叫做管道通信;OS对应的System V模块提供的……

  1. 采用标准的做法:System V进程间通信(聚焦在本地通信,如共享内存)、POSIX进程间通信(让通信过程可以跨主机);
  2. 采用文件的做法:管道——基于文件系统(匿名管道、命名管道)。

本文主要介绍的是管道。

二、管道

1.管道介绍

  1. 管道是Unix中最古老的进程间通信的方式,我们把连接两个进程的数据流称为“管道”。
  2. 任何一个文件包括两套资源:1.file的操作方法;2.属于自己的内核缓冲区。所以父子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以在对应的文件缓冲区读取,这样就可以完成进程间通信。这种方式中被子进程写入和父进程读取的文件,我们称为管道文件。管道文件本质就是内存级文件(不需要IO)。
  3. 两个进程如何看到同一个管道文件?
    fork创建子进程,管道创建时,要分别以读和写的方式打开同一个文件(如果父进程是以只读或只写的方式(其中一种方式)打开文件,子进程也只会继承只读或者只写,父子双方打开文件的方式一样,导致无法完成单向通信)
    父子进程正确进行管道通信的方式:
  4. 父进程创建管道,同时具有读写权限。
    在这里插入图片描述
  5. 父进程创建子进程,子进程继承管道以及对管道的读写
    在这里插入图片描述
  6. 父进程关闭读取端,子进程关闭写入端。自此父进程只能向管道中写入,子进程只能从管道中读取,完成了父子进程的单向通讯。
    在这里插入图片描述

2.管道分类

管道根据是否具有文件名,分为匿名管道和有名管道。

1.匿名管道

通过父进程创建子进程,子进程继承文件地址的方式,让父子进程看到同一个内存级文件,该内存级文件没有名称,则就称为匿名管道。匿名管道可以用来进行父进程和子进程之间的进程间通信。

pipi

pipi创建一个管道,只需要调用pipe系统调用。
它的头文件是unistd.h;调用成功就返回0,调用失败就返回-1;其参数是输出型参数。
在这里插入图片描述

创建管道文件,打开读写端

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 int main()
  5 {
  6         int fds[2];
  7         int n = pipe(fds);
  8         assert(n == 0);
  9         printf("fds[0]:%d\n",fds[0]);
 10         printf("fds[1]:%d\n",fds[1]);
 11         return 0;
 12 }

在这里插入图片描述
因此,fds[0]:3代表读取,fds[1]:4代表写入;

fork子进程

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 int main()
  5 {
  6         int fds[2];
  7         int n = pipe(fds);
  8         assert(n == 0);
  9         pid_t id = fork();
 10         assert(id >= 0);
 11         if(id == 0)//子进程
 12         {
 13                 //子进程通信
 14                 exit(0);
 15         } 
 16         //父进程
 17         n = waitpid(id, NULL, 0);
 18         assert(n == id);
 19         return 0;
 20 }

关闭父进程的读取端,关闭子进程的写入端

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         sleep(1);//每一秒写一次
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 char buffer[1024];
 39                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 40                 if(s > 0) buffer[s] = 0;
 41                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 42         }
 43         n = waitpid(id, NULL, 0);
 44         assert(n == id);
 45         close(fds[0]);//退出程序前,关闭读取端
 46         return 0;
 47 }

在这里插入图片描述

读写特征

  1. 读快,写慢
    子进程休眠时,不再写入,父进程仍在读取(如果管道内没有数据,而读端在读取,则会默认直接阻塞当前正在读取的进程);
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         sleep(50);//每一秒写一次
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 char buffer[1024];
 39                 printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
 40                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 41                 printf("888888888888888888888888888888888888!!\n");
 42                 if(s > 0) buffer[s] = 0;
  43                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 44         }
 45         n = waitpid(id, NULL, 0);
 46         assert(n == id);
 47         close(fds[0]);//退出程序前,关闭读取端
 48         return 0;
 49 }


在这里插入图片描述

2.读慢写快
读取管道的进程一直不进行读取,而写端一直在写入。写端可以向管道内写入,但是管道是固定大小的缓冲区,不断的只写不读管道会被写满。满了以后就不能再写入了,此时写端会处于阻塞状态。
文件test.c

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         printf("count: %d\n",cnt);
 30                 }
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 exit(0);
 33         }
 34         //父进程
 35         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 36         while(1)
 37         {
 38                 sleep(50);//父进程不读
 39                 char buffer[1024];
 40                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 41                 if(s > 0) buffer[s] = 0;
 42                 printf("Get Message : %s | mypid = %d\n", buffer, getpid());
  43         }
 44         n = waitpid(id, NULL, 0);
 45         assert(n == id);
 46         close(fds[0]);//退出程序前,关闭读取端
 47         return 0;
 48 }

在这里插入图片描述
如果,让父进程只sleep(2)的话,读取的速度稍微比较慢:
在这里插入图片描述
这种情况,写端是将数据塞到管道内,管道读取是安装指定大小读取(并非一行一行的读取,最初安装一行来读取是因为写入的慢,一次只写一行数据,数据就被读取了)。

  1. 写入端关闭,则读取到0(管道末尾)读取端关闭
    文件test.c
  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         printf("count: %d\n",cnt);
 30                         break;
 31                 }
 32                 close(fds[1]);//退出子进程前关闭文件写入端
 33                 exit(0);
 34         }
 35         //父进程
 36         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 37         while(1)
 38         {
 39                 sleep(2);//父进程不读
 40                 char buffer[1024];
 41                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 42                 if(s > 0)
  43                 {
 44                         buffer[s] = 0;
 45                         printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 46                 }
 47                 else if(s == 0)//写入端关闭,读到文件末尾了
 48                 {
 49                         printf("read: %d\n", s);
 50                         break;//关闭读取端      
 51                 }
 52         }
 53         n = waitpid(id, NULL, 0);
 54         assert(n == id);
 55         close(fds[0]);//退出程序前,关闭读取端
 56         return 0;
 57 }

在这里插入图片描述
4. 读取端关闭,写入端直接关闭
关闭读取端后,写入端就没有意义了,因此OS会给写入的进程发送信号,终止该进程。

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<assert.h>
  4 #include<sys/types.h>
  5 #include<sys/stat.h>
  6 #include<fcntl.h>
  7 #include<sys/wait.h>
  8 #include<string.h>
  9 #include<stdlib.h>
 10 int main()
 11 {
 12         int fds[2];
 13         int n = pipe(fds);
 14         assert(n == 0);
 15         pid_t id = fork();
 16         assert(id >= 0);
 17         if(id == 0)//子进程
 18         {
 19                 //子进程通信,关闭子进程的读取端,即子进程进行写入
 20                 close(fds[0]);
 21                 const char* s = "你好,我是子进程,正在进行通信";
 22                 int cnt = 0;
 23                 while(1)
 24                 {
 25                         cnt++;
 26                         char buffer[1024];
 27                         snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid());
 28                         write(fds[1], buffer, strlen(buffer));
 29                         printf("count: %d\n",cnt);
 30                 }       
 31                 close(fds[1]);//退出子进程前关闭文件写入端
 32                 printf("子进程关闭写入端\n");
 33                 exit(0);
 34         }
 35         //父进程
 36         close(fds[1]);//父进程关闭写入端,即父进程进行读取
 37         while(1)
 38         {
 39                 sleep(2);//父进程不读
 40                 char buffer[1024];
 41                 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
 42                 if(s > 0)
 43                 {
 44                         buffer[s] = 0;
 45                         printf("Get Message : %s | mypid = %d\n", buffer, getpid());
 46                 }
 47                         break;//关闭读取端      
 48         }
 49         close(fds[0]);//退出程序前,关闭读取端
 50         printf("父进程关闭读取端\n");
 51         n = waitpid(id, NULL, 0);
 52         assert(n == id);
 53         return 0;
 54 }


在这里插入图片描述

管道特征

  1. 管道的生命周期是根据进程的,进程退出,则管道释放;
  2. 管道可以用来进行具有血缘关系的进程间通信(常用于父子进程);
  3. 管道是面向字节流的;
  4. 半双工——单向管道(特殊);
  5. 互斥与同步机制——对共享资源进行保护的方案。

2.命名管道

匿名管道的限制就是只能在具有血缘关系的进程间通信,那么如果是两个毫不相干的进程间通信交互呢?
如果我们想要在两个不相关的进程之间进行通信,我们可以使用FIFO文件,它被称为命名管道。(命名管道是一种特殊类型的文件)

mkfifo

在这里插入图片描述
在当前路径下创建命名管道:

mkfifo named_pipe

在这里插入图片描述

创建管道文件

comm.hpp文件(同一份资源)

  1 #pragma once
  2 #include<iostream>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<cassert>
  7 #include<cstring>
  8 #include<cerrno>
  9 using namespace std;
 10 #define NAMED_PIPO "/tmp/mypipe.name"
 11 #include<string>
 12 bool createFIFO(const string &path)
 13 {
 14         umask(0);
 15         int n = mkfifo(path.c_str(), 0666);
 16         if(n ==0) return true;
 17         else
 18         {
 19                 cout<<"errno:"<<errno<<"err string:"<<strerror(errno)<<endl;
 20         }
 21         return false;
 22 }

server.cc文件(读取端)

  1 #include"comm.hpp"
  2 int main()
  3 {
  4         bool ret = createFIFO(NAMED_PIPO);
  5         assert(ret == true);
  6         (void)ret;
  7         return 0;
  8 }

在这里插入图片描述

删除管道文件

unlink
在这里插入图片描述
头文件:unistd.h;参数为const char* path;返回值:如果删除成功返回0,如果删除失败返回-1
使用:
在这里插入图片描述
在comm.hpp中封装删除函数

 23 void removeFIFO(const string &path)
 24 {
 25         int n = unlink(path.c_str());
 26         assert(n == 0);
 27         (void)n;//避免因为没有使用n导致爆警告
 28 }

在文件server中调用删除函数

  1 #include"comm.hpp"
  2 int main()
  3 {
  4         bool ret = createFIFO(NAMED_PIPO);
  5         assert(ret == true);
  6         (void)ret;      
  7         
  8         removeFIFO(NAMED_PIPO);
  9         return 0;
 10 }

到此管道文件的创建和删除就完成了,接下来我们进入通信阶段:

通信

clinet.cc文件(写入端)

  1 #include"comm.hpp"
  2 int main()
  3 {
  4         printf("HHHHHH\n");
  5         int wfd = open(NAMED_PIPO, O_WRONLY);
  6         if(wfd < 0) exit(1);
  7         char buffer[1024];
  8         while(1)
  9         {
 10                 cout<<"Please say:";
 11                 fgets(buffer, sizeof buffer, stdin);
 					//if(strlen(buffer) > 0) buffer[strlen(buffer) - 1] = 0;
 12                 ssize_t s = write(wfd, buffer, strlen(buffer));
 13                 assert(s == strlen(buffer));
 14                 (void)s;
 15         }
 16         close(wfd);
 17         return 0;
 18 }

server.cc文件(读取端)

  1 #include"comm.hpp"
  2 int main()
  3 {
  4         bool ret = createFIFO(NAMED_PIPO);
  5         assert(ret);
  6         (void)ret;
  7         int rfd = open(NAMED_PIPO, O_RDONLY);
  8         if(rfd < 0) exit(1);
  9 
 10         char buffer[1024];
 11         while(1)
 12         {
 13                 ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
 14                 if(s > 0)
 15                 {
 16                         buffer[s] = 0;
 17                         cout<<"client -> server"<< buffer<<endl;
 18                 }
 19                 else if(s == 0)
 20                 {
 21                         cout<<"client quit. me, too"<<endl;
 22                         break;
 23                 }
 24                 else
 25                 {
 26                         cout<<"err string:"<<strerror(errno)<<endl;
 27                         break;
 28                 }
 29         }
 30         removeFIFO(NAMED_PIPO);
 31         return 0;
 32 }

comm.hpp文件(同一份资源)

  1 #pragma once
  2 #include<iostream>
  3 #include<sys/types.h>
  4 #include<sys/stat.h>
  5 #include<fcntl.h>
  6 #include<cassert>
  7 #include<cstring>
  8 #include<cerrno>
  9 #include<unistd.h>
 10 #include<stdlib.h>
 11 #include<stdio.h>
 12 using namespace std;
 13 #define NAMED_PIPO "/tmp/mypipe.name"
 14 #include<string>
 15 bool createFIFO(const string &path)
 16 {
 17         umask(0);
 18         int n = mkfifo(path.c_str(), 0666);
 19         if(n ==0) return true;
 20         else
 21         {
 22                 cout<<"errno:"<<errno<<"err string:"<<strerror(errno)<<endl;
 23         }
 24         return false;
 25 }
 26 void removeFIFO(const string &path)
 27 {
 28         int n = unlink(path.c_str());
 29         assert(n == 0);
 30         (void)n;//避免因为没有使用n导致爆警告
 31 }

运行:
在这里插入图片描述
我们发现读端每次会多打印一行空白,实际上是将写入端最后的Enter也作为数据传送了,因此我们将写端缓冲区的最后一个位置内容改为0即可。

if(strlen(buffer) > 0) buffer[strlen(buffer) - 1] = 0;

在这里插入图片描述

三、匿名管道和命名管道的区别和联系

它们都是往管道文件里写东西,

两个进程打开同一个文件:站在内核的角度,第二个文件不需要继续创建struct file对象,因为OS会识别到需要打开的文件已经被打开了。在内核中,两个进程此时就看到了同一份资源,有对应文件的操作方法和缓冲区,不需要将数据刷新到磁盘上(不需要IO),所以无论是匿名管道还是命名管道,本质都是内存级文件。

匿名管道是通过继承的方式让两个进程看到一个文件(资源),命名管道是通过让不同的进程打开指定名称(路径+文件名)的文件,来看待同一份资源。所以命名管道是通过文件的文件名来唯一标定资源的唯一性,而匿名管道是通过继承的方式来标定的。


总结

以上就是今天要讲的内容,本文介绍了进程间通信——管道的相关概念。
本文作者目前也是正在学习Linux相关的知识,如果文章中的内有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!

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

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

相关文章

Blindly Assess Image Quality in the Wild Guided by ASelf-Adaptive Hyper Network

Abstract 真实失真图像的盲图像质量评估(BIQA)一直是一个具有挑战性的问题&#xff0c;因为在野外采集的图像包含各种各样的内容和各种类型的失真。目前绝大多数的BIQA方法都专注于如何预测合成图像的质量&#xff0c;但当应用于真实世界的失真图像时却失败了。为了应对这一挑…

一站式完成车牌识别任务:从模型优化到端侧部署

交通领域的应用智能化不断往纵深发展&#xff0c;其中最为成熟的车牌识别早已融入人们的日常生活之中&#xff0c;在高速公路电子收费系统、停车场等场景中随处可见。一些企业在具体业务中倾向采用开源方案降低研发成本&#xff0c;但现有公开的方案中少有完成端到端的车牌应用…

DMBOK知识梳理for CDGA/CDGP——第三章数据治理

关 注gzh“大数据食铁兽” 回复“知识点”获取《DMBOK知识梳理for CDGA/CDGP》常考知识点&#xff08;第三章数据治理&#xff09; 第三章 数据治理 第三章在是CDGA|CDGP考试的重点考核章节之一&#xff0c;知识点比较密集&#xff0c;本章重点为语境关系图及数据治理概念…

给电脑重装系统的时间需要多久才能装好

在进行电脑重装系统时&#xff0c;如果遇到系统安装时间过长的情况&#xff0c;可能会引起用户的困惑和不安。本文将介绍一些常见的原因和解决方法&#xff0c;以帮助您理解并应对系统安装时间过长的情况。 ​工具/原料&#xff1a; 系统版本&#xff1a;Windows 10 专业版 品…

Python数据攻略-Pandas的数据创建与基础特性

大家好&#xff0c;我是Mr数据杨&#xff01;今天将进入Python的Pandas数据世界&#xff0c;就像三国演义中的英雄们&#xff0c;用聪明才智塑造自己的命运。 记得三国中&#xff0c;周瑜曾利用兵法巧妙策划火烧赤壁&#xff0c;击败曹军。这就像创建一个Pandas DataFrame&…

JavaSE笔记(七)重制版

多线程与反射 前面我们已经讲解了JavaSE的大部分核心内容&#xff0c;最后一章&#xff0c;我们还将继续学习JavaSE中提供的各种高级特性。这些高级特性对于我们之后的学习&#xff0c;会有着举足轻重的作用。 多线程 **注意&#xff1a;**本章节会涉及到 操作系统 相关知识…

宝塔面板搭建网站教程:Linux下使用宝塔一键搭建网站,内网穿透发布公网上线

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 转载自cpolar内网穿透的文章&#xff1a;使用宝塔面板快速搭建网站&#xff0c;并内网穿透实现公网远程访问 前言 宝塔面板作为简单好用的服务器运维管理面板&…

Spring为什么默认是单例的?

目录 一、五种作用域 二、单例bean与原型bean的区别 三、单例Bean的优势与劣势 一、五种作用域 1.singleton: singleton是Spring Bean的默认作用域&#xff0c;也就是单例模式。在整个应用程序中&#xff0c;只会创建一个实例&#xff0c;Bean的所有请求都会共享这个实例。 …

简单上手!快速将另一个报表的页面添加到 FastReport .NET!

FastReport 是功能齐全的报表控件&#xff0c;可以帮助开发者可以快速并高效地为.NET&#xff0c;VCL&#xff0c;COM&#xff0c;ActiveX应用程序添加报表支持&#xff0c;由于其独特的编程原则&#xff0c;现在已经成为了Delphi平台最优秀的报表控件&#xff0c;支持将编程开…

人车网租赁软件开发|人车网租赁系统|租赁系统源码功能

经过租赁小程序不只可以使物品得到充沛的运用&#xff0c;还能减少一些资源的浪费&#xff0c;租赁行业这两年因为互联网技术的完善&#xff0c;发展也在不断进步&#xff0c;租赁系统定制开发功能也在不断完善&#xff0c;那么企业想要开发租赁小程序的时分需求留意哪些方面呢…

长尾词挖掘,长尾词的优化方法有哪些

我们都知道&#xff0c;长尾词能给我们带来较高的流量和转化率&#xff0c;且优化难度低&#xff0c;成本低。今天就来分享长尾词的优化方法。 首先需要挖掘长尾词&#xff0c;挖掘长尾词的方法以下3种比较实用&#xff1a; 1、使用长尾词挖掘工具 可以通过第三方工…

小波和小波变换(应试)

零基础小白共计花费2小时38分04秒完成对小波的“平地起高楼”学习。 记录所有学习过程。 一、大致浏览PPT 这个阶段跳着看&#xff0c;太难的跳过 1.总结知识点 共四个部分 1.小波介绍 2.小波变换 小波变换的定义连续小波变换的定义离散小波变换小波重构 3.哈尔小波变换 …

【C++初阶】初识模板

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;C航路 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&#x1…

地理空间数据云管理-四维轻云1.3.2-版本更新内容

很多用户想要在不上传数据的情况下查看案例数据&#xff0c;我们在四维轻云1.3.2版本中为新用户新增了示例项目。此外&#xff0c;此次更新还新增了标绘数据模式和场景定时保存提醒功能&#xff0c;优化了插件启动流程以及移动和旋转变化轴。 1、新增示例项目 示例项目中包含…

Ubuntu 登录提示信息`Message of The Day`(MOTD)定制与开关

一、效果 登录Ubuntu的时候&#xff0c;在控制台可能会弹出一系列提示消息&#xff0c;有欢迎消息、系统信息、更新信息等等&#xff1a; 这些提示消息被称为Message of The Day&#xff0c;简称MOTD。 Ubuntu与其它Linux版本不太一样&#xff0c;它引入了MOTD 的概念。 这些…

健康管理系统开发笔记

健康管理系统 项目介绍功能架构项目结构maven项目搭建 项目介绍 健康管理系统是一款应用于健康管理机构的业务系统&#xff0c;实现健康管理机构工作内容 可视化、会员管理专业化、健康评估数字化、健康干预流程化、知识库集成化&#xff0c;从而提 高健康管理师的工作效率&a…

Intradeco通过适用于Excel的Liquid UI自动执行SAP MM并节省80%的处理时间

背景 Intradeco为服装制造提供整体方法&#xff0c;涵盖所有阶段&#xff1a;从构思阶段到最终产品分销。它已发展成为一家全球垂直制造公司&#xff0c;客户遍布美国、墨西哥和加拿大。 挑战 提高运营效率 原因&#xff1a;人员必须浏览多个 SAP 事务才能为新材料创建采购订单…

面向对象——权限修饰符、匿名内部类

package关键字 为什么要有包&#xff1f; 将字节码&#xff08;.class&#xff09;进行分类存放 包其实就是文件夹 包的定义及注意事项 定义包的格式 package 包名 多级包用.分割&#xff0c;如package com.heima里面的.就是分隔符 定义包的注意事项 package语句必须是程序的第…

Dockerfile详解

Dockerfile是什么 Dockerfile就是一个纯文本&#xff0c;里面记录了一系列的构建指令&#xff0c;如选择基础镜像、拷贝文件、运行脚本等等&#xff0c;RUN, COPY, ADD指令都会生成一个 Layer&#xff0c;而 Docker 顺序执行这个文件里的所有步骤&#xff0c;最后就会创建出一…

分布式系统概念和设计——分布式事务

分布式系统概念和设计 分布式事务 访问多个服务器管理的对象的事务称为分布式事务。 当一个分布式事务结束时&#xff0c;事务的原子特性要求所有参与事务的服务器必须全部提交或全部放弃。 实现&#xff1a; 其中一个服务器承担了协调者的角色&#xff0c;保证在所有的服务器…