当前位置: 首页 > news >正文

【Linux】进程间通信——管道

目录

写在前面的话

什么是进程间通信

为什么要进行进程间通信

进程间通信的本质理解

进程间通信的方式

管道

System V IPC

POSIX IPC

管道

什么是管道

 匿名管道

什么是匿名管道

匿名管道通信的原理

pipe()的使用

匿名管道通信的特点

拓展代码

 命名管道

什么是命名管道

命名管道通信的原理

mkfifo的使用

代码模拟命名管道通信过程


写在前面的话

        本章是首次提出进程间通信的概念,所以会先介绍进程间通信的相关概念,以及整体的框架结构。

        而本文重点是先介绍进程间通信的基本概念,然后重点介绍进程间通信的第一种方式:管道。

什么是进程间通信

        进程间通信(Inter-Process Communication,IPC)是指操作系统或计算机系统中,不同进程之间进行数据交换和通信的机制或技术。由于进程是操作系统中独立运行的程序实例,而进程间通信允许这些独立的进程之间相互协作、共享资源和进行数据交换。

为什么要进行进程间通信

        根据我们前面讲的,进程间是相互独立的,进程具有独立性啊,那通信不就不独立了吗?

    答案是正确的,进程通信的确会破坏进程的完全独立性,因为进程通信的目的是为了实现进程之间的数据共享、同步和协作。通过进程通信,各个进程可以相互交互和共享资源,这意味着它们不再完全独立,而是具有一定的相互依赖性和关联性。

        尽管进程通信破坏了进程的完全独立性,但这种破坏是有意义且必要的。在实际的计算机系统和操作系统中,进程往往需要协同工作、共享资源和交换数据才能完成复杂的任务。进程通信提供了一种机制,使得不同进程之间可以进行必要的协作和交流,并提供了相应的同步和保护机制来确保数据的正确性和一致性。

        所以这是一种权衡和折中的方案,但大部分情况下进程是相互独立的。


综上,进程间通信主要是为了完成下面这些作用:

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

进程间通信的本质理解

        1.我们知道进程具有独立性,是通过虚拟地址空间 + 页表映射的方式来保持独立性的,所以通信起来成本会比较高。

        2.既然通信,那么前提是一定要让不同的进程看到同一块“内存”(特定的结构组织),这块"内存"不能隶属于任何一个进程,而更应该强调共享

进程间通信不是目的,而是手段!


进程间通信的方式

        大体上可以分为3种通信方式:

  • 管道

    • 匿名管道pipe
    • 命名管道
  • System V IPC

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

System V只能用于单机通信(本地通信).

  • POSIX IPC

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

POSIX IPC可以在单机通信的基础上,进行网络通信(远程资源共享)。

        以上所提到的方式,我会在后面的章节逐一讲解,这个是进程间通信的方式.

今天我们将首要讲解进程间通信方式(一)——管道.

管道

什么是管道

        管道(Pipe)是一种进程间通信机制,用于在相关进程之间传输数据。它是一种特殊的文件描述符,它可以连接一个进程的输出(写入端)到另一个进程的输入(读取端),从而使得这两个进程可以通过管道进行数据传输。

        也就是说管道是单向传输的!现实生活中,我们所看听到的天然气管道、石油管道基本上都是单向传输的.

 匿名管道

什么是匿名管道

匿名管道(Anonymous Pipe)是进程间通信的一种机制,用于在具有亲缘关系(例如父子进程)或共享同一终端的兄弟进程之间传输数据。

        匿名管道是一种单向的数据流通道,它可以用于在进程之间传递数据。通常,一个进程作为管道的写入端(称为管道写入端),将数据写入管道;另一个进程作为管道的读取端(称为管道读取端),从管道中读取数据。

        匿名管道的创建是通过系统调用 pipe() 来完成的。pipe的使用后面会讲。

匿名管道通信的原理

        管道通信的背后是进程之间通过管道进行通信。

        我们知道一个进程要运行,首先要加载到内存,然后创建一个task_struct结构体,里面会有一个files_struct结构体,然后这个结构体里又有一个fd_array[]数组,每个元素指向对应的文件struct_file,里面包含了文件内容等.

        此时我们fork之后的子进程会重新创建一份task_struct,内容继承父进程的,此时fd_array[]里的内容也被子进程继承,即父进程打开的文件 子进程也继承了下来。它们指向的文件是 相同的.

        假设父进程3号文件描述符是读取文件的,4号文件描述符也用来写入文件的,子进程继承以后,fd=3也是用来读取文件的,fd=4也是用来写入文件的

         此时我们想让父进程进行写入fd=4,子进程进行读取fd=3,所以父进程就要关闭读端fd=3,子进程关闭写端fd=4。

这样我们就做到了不同的进程看到了同一份资源(通过fork子进程),而且通过文件描述符的方式完成了进程间的单向通信。

综上,管道内部本质大体是如下流程:

        1.父进程分别以读写方式打开一个文件

        2.fork()创建子进程

        3.双方各自关闭不需要的文件描述符

整体图如下:

pipe()的使用

        既然我们知道了思路,那我们可以用代码来使用一下管道。

        首先,父进程如何使用读和写方式分别打开文件呢?

这里使用到了pipe函数,函数用法及原型如下:

        参数pipefd为输出型参数,我们提前在外部定义好数组,然后传入,结果就会保存在这个数组中,分别为pipefd[0],代表的是读端,pipefd[1],代表的是写端.

        第二步我们利用fork创建子进程。

        最后,子进程用来读取文件的内容,并关闭写端pipefd[1],父进程用来写入内容,同时关闭读端。

匿名管道通信的特点

一个小demo如下:

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{// 1.创建管道int pipefd[2] = {0}; // pipefd[0] :读端, pipefd[1] :写端int n = pipe(pipefd);assert(n != -1);#ifdef DEBUG#endif// cout << pipefd[0] << "  " << pipefd[1] << endl;// 2.创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程// 3.构建单向通信的信道,父进程写入,子进程读取// 3.1关闭子进程不需要的fdclose(pipefd[1]);char buffer[1024];while (true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child get a message [" << getpid() << "] Father# " << buffer << endl;}}exit(0);}// 父进程// 构建单向通信的信道// 3.1 关闭父进程不需要的fdclose(pipefd[0]);string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024];while (true){// 3.2 构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);// 3.3 写入write(pipefd[1], send_buffer, strlen(send_buffer));// 3.4 sleepsleep(1);}pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);close(pipefd[1]);return 0;
}

然后此时我们编译运行 :

 可以看到,父进程写入的内容,子进程全部读到了。        

这里是静态图,看不出效果,这些信息其实是时隔1秒打印一条的.

        我们看只有子进程在读取,可子进程我们并没有加任何的sleep啊,理论上应该子进程一直打印才对啊。

        显示器是一个文件,而管道也是一个文件,父子进程同时向显示器 写入时,没有说一个进程会等另一个进程,而是各自打印各自的消息,互相干扰,这是缺乏访问控制。而管道文件提供了访问控制,使得子进程读取完得等待父进程写入才能读取。

        这样,如果我们让子进程读取先sleep 10秒,期间父进程每隔1秒写入,等10秒过后,子进程开始读取,但会把 父进程写入10次的文件的内容全部一下读出来。这说明写入次数和读取次数没有直接关系。即管道是面向字节流的,具体怎么读需要定制协议,后面会说,

        这里就针对与管道的特点做一些总结:

  • 管道是用来进行具有血缘关系的进程进行进程间通信 --- 常用于父子间通信
  • 管道具有通过让进程间协同,提供了访问控制
  • 管道提供的是面向字节流式的通信服务 --- 面向字节流 --- 通过定制协议实现
  • 管道是基于文件的,文件的生命周期是随进程的,即管道的生命周期也随进程的!
  • 管道是单向通信的,就是半双工通信的一种特殊方式.

        上面最后一条提到了半双工概念,这里来解释一下:

        半双工通信的双方只能在同一时间点单向的传输数据,即两个参与者不能同时发送和接收数据。在半双工通信中,通信双方必须交替使用共享的通信信道。例如,当一个人在对讲机上说话时,另一个人必须停止接收,然后才能回应。典型的半双工通信方式包括对讲机和卫星电台。

        全双工:全双工通信允许在同一时间点双向地传输数据。这意味着通信的两个参与者能够同时发送和接收数据,而不需要交替使用通信信道。在全双工通信中,通信双方可以同时进行发送和接收操作,彼此之间的数据传输互不干扰。例如,电话通话是一个典型的全双工通信场景,双方可以同时说话和倾听对方的声音。

         顺带总结一下管道的几种情况:

        a.写快,读慢,写满就不能再写了

        b.写慢,读快,管道没有数据时,读必须等待

        这两种是由访问控制提供的.

        c.写关,读继续读,会标识读到了文件结尾

        d.写继续写,读关,OS会终止写进程

拓展代码

        利用匿名管道的方式,创建多个子进程,然后父进程分别派发随机的任务,

        总代码流程是:父进程首先load()加载方法,然后for循环创建多个进程,每次创建完成后,该进程都要与父进程(pipefd[1])建立关联,以方便父进程管理这些子进程。

        其中每个子进程调用 waitCommand函数,会阻塞在read,等待着父进程的写入,然后父进程开始分发任务,当是对应的子进程时,子进程会执行对应的任务,然后继续while循环等待。

共两个文件,第一个文件ProcessPool.cc文件

#include <iostream>
#include <vector>
#include <ctime>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;#define PROCESS_NUM 5
int waitCommand(int waitFd, bool quit) // 如果对方不发任务就阻塞
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(command));return command;
}
void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{load();// pid : pipefdvector<pair<pid_t, int>> slots;// create multiple child processfor (int i = 0; i < PROCESS_NUM; i++){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);pid_t id = fork();assert(id != -1);// 让子进程读取if (id == 0){// 关闭写端close(pipefd[1]);// child processwhile (true){// 等命令bool quit = false;int command = waitCommand(pipefd[0], quit); // 如果对方不发任务就阻塞if (quit)break;// 执行对应的命令if (command >= 0 && command < handlerSize()){callbacks[command]();}else{cout << "非法 command" << endl;}}exit(1);}// father processclose(pipefd[0]);slots.push_back(pair<pid_t, int>(id, pipefd[1]));}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 2311156L);while (true){   //选择一个任务 int command = rand() % handlerSize();//选择一个进程,采用随机数的方式,选择进程来完成任务,随机数的方式负载均衡int choice = rand() % slots.size();// 把任务给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);sleep(1);int select;//以下是手动派发任务// int command = 0;// cout << "######################################" << endl;// cout << "1.show functions        2.send command" << endl;// cout << "######################################" << endl;// cout << "Please Select > ";// cin >> select;// if (select == 1)//     showHandler();// else if (select == 2)// {//     cout << "Enter your Command > ";//     // 选择任务//     cin >> command;//     // 选择进程//     int choice = rand() % slots.size();//     // 把任务给指定的进程//     sendAndWakeup(slots[choice].first, slots[choice].second, command);// }// else// {// }}// 关闭fd,结束所有进程for (auto &slot : slots){close(slot.second);}// 回收所有子进程for (auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}

        第二个文件为Task.hpp文件,主要是包含了任务的加载及任务的执行方法。

        

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<unistd.h>
#include<functional>
using namespace std;typedef function<void()> func;vector<func> callbacks;
unordered_map<int,string> desc;void readMySQL()
{cout << "sub process[" << getpid() << "] 执行数据库被访问的任务\n" << endl;
}
void executeURL()
{cout << "sub process[" << getpid() << "] 执行url解析任务\n" << endl;
}
void cal()
{cout << "sub process[" << getpid() << "] 执行加密任务\n" << endl;
}
void save()
{cout << "sub process[" << getpid() << "] 执行数据持久化\n" << endl;
}void load()
{desc.insert({callbacks.size(),"readMySQWL:读取数据库"});callbacks.push_back(readMySQL);desc.insert({callbacks.size(),"executeURL:解析URL"});callbacks.push_back(executeURL);desc.insert({callbacks.size(),"cal:进行加密计算"});callbacks.push_back(cal);desc.insert({callbacks.size(),"save:进行数据的文件保存"});callbacks.push_back(save);
}
void showHandler()
{for(auto& iter: desc){cout << iter.first << "\t" << iter.second << endl;}
}
int handlerSize()
{return callbacks.size();
}

 

这样每次父进程都会随机给子进程派发随机的任务:

 

 命名管道

         与匿名管道不同,命名管道不需要亲缘关系的进程之间,也不需要共享同一终端。任意进程可以通过打开命名管道的读取端和写入端来与其进行通信。

什么是命名管道

命名管道(Named Pipe)是一种独立进程之间通信的机制,用于在无关的进程之间进行数据传输。

        命名管道通过在文件系统中创建一个特殊的文件来实现通信。这个特殊的文件被称为FIFO(First-in, First-out)或命名管道。

命名管道通信的原理

        和匿名管道一样,想让双方通信,必须先让双方看到同一份资源!它和匿名管道本质是一样的,只是看到资源的方式不同

        匿名管道是通过父子进程继承来看到同一份资源的,也叫做管道文件,这个文件是纯内存级的,所以没有名字,叫做匿名管道。

        而命名管道是在磁盘上有一个特殊的文件,这个文件可以被打开,但是打开后不会将内存中的数据刷新到磁盘。在磁盘上就有了路径,而路径是唯一的,所以双方就可以通过文件的路径 来看到同一份资源,即管道文件。

这是命名管道的流程:

  1. 创建命名管道:通过调用系统调用 mkfifo() 在文件系统中创建一个特殊的文件,这个文件就是命名管道。创建命名管道时,需要指定管道的名称和所需的权限。

  2. 打开命名管道:进程通过调用系统调用 open() 来打开命名管道,得到一个文件描述符。进程可以通过打开具有相同名称的文件来打开命名管道的读取端和写入端。

  3. 进程通信:一旦命名管道被打开,进程就可以使用文件描述符进行通信。每个进程可以选择读取端或写入端与命名管道进行交互。

  4. 数据传输:进程在读取端通过调用 read() 系统调用从命名管道中读取数据,而在写入端通过调用 write() 系统调用将数据写入命名管道中。读取端和写入端可以通过文件描述符进行数据的发送和接收。

  5. 关闭命名管道:进程完成通信后,可以通过调用 close() 关闭命名管道的文件描述符来释放资源。当所有对命名管道的引用都被关闭时,管道的文件系统条目将被删除。

mkfifo的使用

        上面提到了需要使用mkfifo来创建这个特殊的文件,来让独立的进程之间通过它进行通信,下面来看一下它的用法。

mkfifo [选项] 文件名

         非常简单的使用方法,选项我们一般不用,所以直接mkfifo + 文件名即可

        我们在当前路径下创建一个name_pipe的文件.

注意权限的最前面是p,代表是管道文件。


我们此时echo一句消息到这个管道文件中:

        我们发现这里阻塞住了,这是因为一方向管道文件里写入了,但是另外一方还没有读,所以此时我们新建一个窗口,然后读取name_pipe里的内容:

 

         这样信息便成功的被读取出来了,这就是mkfifo的简单使用。

代码模拟命名管道通信过程

        其实过程和匿名管道类似,只是看到同一资源的手段不一样。

        上面讲的mkfifo是指令创建,但是如果我想用代码该如何实现呢?这里有一个mkfifo函数:

         第一个参数是创建的管道文件的路径,第二个是权限。

         当创建成功时,mkfifo返回0,否则返回-1.

整体的流程是是这样的:

我们首先可以分成 服务端 和 客户端,服务端负责

        1.创建管道文件并打开

        2.进行与客户端正常的通信

        3.最后关闭并删除管道文件

而客户端

        1.首先要打开管道文件

        2.然后进行与服务端正常的通信流程即可

        这里为了方便,我们加入了日志,可以看到每一步的动作。

        所以一共四个文件,分别为comm.hpp,client.cc,server.cc,Log.hpp.

comm.hpp

#pragma once
#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<vector>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>using namespace std;#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";

client.cc

#include "comm.hpp"
int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(),O_WRONLY);if(fd < 0){perror("open");exit(1); }//2.通信过程string buffer;while(true){cout << "please Enter Message Line :> ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());}//3.关闭文件return 0;
}

server.cc

#include"comm.hpp"
#include"Log.hpp"
int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(),MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功",Debug) << "step 1" << endl;//2.正常的文件操作int fd = open(ipcPath.c_str(),O_RDONLY);if(fd < 0){perror("open");exit(2);}Log("打开管道文件成功",Debug) << "step 2" << endl;//3.编写正常的通信代码char buffer[SIZE]; while(true){memset(buffer,'\0',sizeof(buffer));ssize_t s = read(fd,buffer,sizeof(buffer)-1);if(s > 0){cout << "client say> " << buffer << endl;}else if(s == 0){//end of filecerr << "read emd of file, client quit, server quit too!" << endl;break;}else{//read errorperror("read");}}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug) << "step 3" << endl;unlink(ipcPath.c_str());//通信完毕就删除文件Log("删除管道文件成功",Debug) << "step 4" << endl;return 0;
}

Log.hpp

#pragma once
#include <iostream>
#include <ctime>
#include<string>
using namespace std;#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3string msg[] = {"Debug ","Notice","Warning","Error"
};ostream& Log(string message,int level)
{cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;return cout;
}

 然后我们再编译运行,可以再创建一个Makefile文件,直接编译好所有的文件,内容如下:

.PHONY:all
all:client serverclient:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf client server

此时我们直接make即可。然后会得到两个可执行文件client和server.

我们打开两个窗口,首先运行server.

 第一步创建管道完成,然后我们在另一个窗口运行客户端.

运行起来后,显示打开文件也成功了,这个时候,我们在客户端输入,服务端都能读取到:

 然后我们ctrl + c 退出客户端,此时服务端也会break跳出循环,然后结束.

 这样,利用命名管道通信的代码流程也就完成了.

 

相关文章:

【Linux】进程间通信——管道

目录 写在前面的话 什么是进程间通信 为什么要进行进程间通信 进程间通信的本质理解 进程间通信的方式 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 什么是匿名管道 匿名管道通信的原理 pipe()的使用 匿名管道通信的特点 拓展代码 命名管道 什么是命…...

Element-plus中tooltip 提示框修改宽度——解决方案

tooltip 提示框修改宽度方法&#xff1a; 在element中&#xff0c;想要设置表格的内容&#xff0c;超出部分隐藏&#xff0c;鼠标悬浮提示 可以在el-table 上添加show-overflow-tooltip属性 同时可以通过tooltip-options配置提示信息 如下图代码 <el-tableshow-overflo…...

java实现当前系统时间格式化

import java.text.SimpleDateFormat; import java.util.Date;public class DateTest {public static void main(String[] args) {Date date new Date();System.out.println("当前系统时间&#xff1a;" date);SimpleDateFormat simpleDateFormat new SimpleDateFo…...

篇十一:享元模式:共享细粒度对象

篇十一&#xff1a;“享元模式&#xff1a;共享细粒度对象” 设计模式是软件开发中的重要工具&#xff0c;享元模式&#xff08;Flyweight Pattern&#xff09;是结构型设计模式的一种。享元模式旨在通过共享细粒度的对象&#xff0c;减少内存消耗和提高性能。在设计模式学习中…...

Dev控件 Gridcontrol,gridview 实现多选功能

在网上看了好多实现dev控件GridControl多选功能的方法&#xff0c;都很麻烦&#xff0c;其实GridControl有一个自带的实现多选功能的控件&#xff0c;很简单。 实现效果如下 无需代码代码&#xff0c;使用GridControl中自带的多选功能&#xff0c;在界面直接设置即可 1.找到要…...

内网穿透:如何通过公网访问本地Web服务器?

文章目录 前言1. 首先安装PHPStudy2.下载一个开源的网页文件3. 选择“创建网站”并将网页内容指向下载好的开源网页文件4. 打开本地网页5. 打开本地cpolar客户端6. 保存隧道设置 生成数据隧道 前言 随着科技进步和时代发展&#xff0c;计算机及互联网已经深深融入我们的生活和…...

在qemu中挂载镜像文件

将镜像文件作为交换分区 创建镜像文件&#xff1a; dd if/dev/zero ofswap.img bs512 count131072 qemu-system-arm -nographic -M vexpress-a9 -m 64M -kernel arch/arm/boot/zImage -append "rdinit/linuxrc consolettyAMA0 loglevel8" -dtb arch/arm/boot/dts/…...

报错注入(主键重复)攻击原理

基本原理 利用数据表中主键不能重复的特点&#xff0c;通过构造重复的主键&#xff0c;使得数据库报错&#xff0c;并将报错结果返回到前端。 SQL说明函数 以pet数据表为例进行说明 rond(): 返回[0,1)区间内的任意浮点数。 count(): 返回每个组的列行数。 如&#xff0…...

Golang基础教程

Golang基础教程 golang简介安装golanggolang开发工具go常用命令golang开发 vscode快捷键如何编写golang代码golang标识符、关键字、命名规则golang变量go语言常量go语言数据类型go语言布尔类型go语言数字类型golang字符串golang格式化输出golang运算符go语言中的流程控制golan…...

ppt压缩文件怎么压缩最小?文件压缩技巧分享

在日常的工作和学习中&#xff0c;难免会遇到PPT太大&#xff0c;需要将其压缩变小的情况&#xff0c;但很多朋友还不知道怎么压缩PPT文件&#xff0c;下面就给大家分享几个简单的方法&#xff0c;分分钟缩小过大的PPT文件。 一、PowerPoint PowerPoint就是微软公司的演示文稿…...

实例033 制作闪烁的窗体

实例说明 Windows系统中&#xff0c;当程序在后台运行时&#xff0c;如果某个窗口的提示信息需要用户浏览&#xff0c;该窗口就会不停的闪烁&#xff0c;这样就会吸引用户的注意。同样&#xff0c;如果在自己的程序中使某个窗口不停的闪烁就会吸引用户的注意。本例设计了一个闪…...

【JavaEE进阶】Spring创建与使用

文章目录 一. 创建 Spring 项目1.1 创建一个Maven项目1.2 添加Spring依赖1.4. 创建一个启动类 二. 将 Bean 对象存放至 Spring 容器中三. 从 Spring 容器中读取到 Bean1. 得到Spring对象2. 通过Spring 对象getBean方法获取到 Bean对象【DI操作】 一. 创建 Spring 项目 接下来使…...

PHP8的循环控制语句-PHP8知识详解

我们在上一节讲的是条件控制语句&#xff0c;本节课程我们讲解循环控制语句。循环控制语句中&#xff0c;主要有for循环、while循环、do...while循环和foreach循环。 在编写代码时&#xff0c;经常需要反复运行同一代码块。我们可以使用循环来执行这样的任务&#xff0c;而不是…...

第八次作业

一&#xff0c;.什么是数据认证&#xff0c;有什么作用&#xff0c;有哪些实现的技术手段? 数据认证是指保证数据的真实性、完整性和可信度&#xff0c;以确保数据不被篡改或伪造。其作用包括但不限于&#xff1a; 保护关键数据不被恶意篡改或损坏 提供数据来源的可靠性和安全…...

LeetCode //C - 290. Word Pattern

290. Word Pattern Given a pattern and a string s, find if s follows the same pattern. Here follow means a full match, such that there is a bijection between a letter in pattern and a non-empty word in s. Example 1: Input: pattern “abba”, s “dog c…...

[保研/考研机试] 括号匹配问题 C++实现

题目描述&#xff1a; 在某个字符串(长度不超过100)中有左括号、右括号和大小写字母&#xff1b;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序&#xff0c;找到无法匹配的左括号和右括号&#xff0c;输出原来的字符串&am…...

springBoot集成caffeine,自定义缓存配置 CacheManager

目录 springboot集成caffeine Maven依赖 配置信息&#xff1a;properties文件 config配置 使用案例 Caffeine定制化配置多个cachemanager springboot集成redis并且定制化配置cachemanager springboot集成caffeine Caffeine是一种基于服务器内存的缓存库。它将数据存储在…...

【瑞吉外卖】Git部分学习

Git简介 Git是一个分布式版本控制工具&#xff0c;通常用来对软件开发过程中的源代码文件进行管理。通过Git仓库来存储和管理这些文件&#xff0c;Git仓库分为两种&#xff1a; 本地仓库&#xff1a;开发人员自己电脑上的Git仓库 远程仓库&#xff1a;远程服务器上的Git仓库…...

如何阐述自己做了一个什么样的东西

线上qps2000&#xff0c;主要的性能瓶颈在于出现在数据库I/O上。另外&#xff0c;如果是一个正常部署的容器&#xff0c;qps能达到几百就不错了。资讯服务现在做了静态的底层页&#xff0c;所以热点新闻多数会命中底层页&#xff0c;即便没有命中底层页&#xff0c;也会走多层的…...

TC3XX - MCAL知识点(二十二):QSPI 同步与异步 Mcal配置及代码实战

目录 1、MCAL配置 1.1、配置目标 1.2、同步QSPI配置 1.2.1、SpiGeneral 1.2.2、SpiMaxChannel 1.2.3、SpiMaxJob...

led台灯哪些牌子性价比高?推荐几款性价比高的护眼台灯

作为学龄期儿童的家长&#xff0c;最担心的就是孩子长时间学习影响视力健康。无论是上网课、写作业、玩桌游还是陪伴孩子读绘本&#xff0c;都需要一个足够明亮的照明环境&#xff0c;因此选购一款为孩子视力发展保驾护航的台灯非常重要。为大家推荐几款性价比高的护眼台灯。 …...

什么情况下容易发生锁表及如何处理

目录 什么情况下容易发生锁表发生锁表怎么解决 什么情况下容易发生锁表 在数据库中&#xff0c;当多个事务同时竞争访问同一个表的资源时&#xff0c;可能会发生锁表现象&#xff0c;导致性能下降甚至阻塞。以下情况容易导致锁表问题&#xff1a; 大事务操作&#xff1a;如果一…...

elk开启组件监控

elk开启组件监控 效果&#xff1a; logstash配置 /etc/logstash/logstash.yml rootnode1:~# grep -Ev "^#|^$" /etc/logstash/logstash.yml path.data: /var/lib/logstash path.logs: /var/log/logstash xpack.monitoring.enabled: true xpack.monitoring.elasti…...

Java Random 类的使用

Java中的Random类是用来生成伪随机数的工具类。它可以用来生成随机的整数、浮点数和布尔值。以下是Java Random类的一些常见用法&#xff1a; 创建Random对象&#xff1a; Random random new Random();生成随机整数&#xff1a; int randomNumber random.nextInt(); // 生…...

完美的分布式监控系统——Prometheus(普罗米修斯)与优雅的开源可视化平台——Grafana(格鲁夫娜)

一、基本概念 1、之间的关系 prometheus与grafana之间是相辅相成的关系。作为完美的分布式监控系统的Prometheus&#xff0c;就想布加迪威龙一样示例和动力强劲。在猛的车也少不了仪表盘来观察。于是优雅的可视化平台Grafana出现了。 简而言之Grafana作为可视化的平台&#xff…...

pycharm的Terminal中如何设置打开anaconda3的虚拟环境

在pycharm的File -> Settings -> Tools -> Terminal下面&#xff0c;如下图所示 修改为红框中内容&#xff0c;然后关闭终端在重新打开终端&#xff0c;即可看到anaconda3的虚拟环境就已经会被更新...

Jmeter(四) - 从入门到精通 - 创建网络测试计划(详解教程)

1.简介 在本节中&#xff0c;您将学习如何创建基本的 测试计划来测试网站。您将创建五个用户&#xff0c;这些用户将请求发送到JMeter网站上的两个页面。另外&#xff0c;您将告诉用户两次运行测试。因此&#xff0c;请求总数为&#xff08;5个用户&#xff09;x&#xff08;2…...

Flowable-结束事件-空结束事件

目录 定义图形标记XML内容 定义 空结束事件是最常见的一种结束事件&#xff0c;也是最简单的一种结束事件&#xff0c;只要把结束任务置于流程 或分支的最后节点&#xff0c;流程实例运行到该节点的时候&#xff0c;流程引擎就会结束该流程实例或分支。前面提 到&#xff0c;结…...

Elasticsearch:如何创建 Elasticsearch PEM 和/或 P12 证书?

你是否希望使用 SSL/TLS 证书来保护你的 Elasticsearch 部署&#xff1f; 在本文中&#xff0c;我们将指导你完成为 Elasticsearch 创建 PEM 和 P12 证书的过程。 这些证书在建立安全连接和确保 Elasticsearch 集群的完整性方面发挥着至关重要的作用。 友情提示&#xff1a;你可…...

数仓架构模型设计参考

1、数据技术架构 1.1、技术架构 1.2、数据分层 将数据仓库分为三层&#xff0c;自下而上为&#xff1a;数据引入层&#xff08;ODS&#xff0c;Operation Data Store&#xff09;、数据公共层&#xff08;CDM&#xff0c;Common Data Model&#xff09;和数据应用层&#xff…...

海南三亚做网站/小程序开发平台有哪些

Thinkphp操作当前数据库以外的数据表时&#xff0c;发现更新字段的时候返回0&#xff0c;是跨库就不没有写入权限了么&#xff1f;namespace Home\Model;use Think\Model;class CategoryModel extends Model {protected $trueTableName top_categories;protected $dbName top…...

网站充值平台怎么做/百度秒收录技术最新

Gartner公司的分析师曾进行过调查&#xff0c;调查结果表明&#xff0c;就技术方面而言&#xff0c;亚洲CIO正将重点放在商务智能与商业分析&#xff0c;移动技术&#xff0c;以及云计算技术。 Gartner调查公司称&#xff0c;亚洲CIO心中商务与科技的优先顺序&#xff0c;在很大…...

哪里找做网站的公司/活动推广软文

1 饼状图 import mx.collections.ArrayCollection; //绑定需要显示的数据 [Bindable] //设定要显示的数据 private var modelData:ArrayCollection new ArrayCollection( [ { Year: "2010年世界杯", Count: 4 }, { Year: "2014年世界杯", Count: 10 }, { …...

什么是网站分析/seo

Powered by:NEFU AB-IN Link 文章目录1275. 最大数题意思路代码1275. 最大数 题意 给定一个正整数数列 a1,a2,…,an&#xff0c;每一个数都在 0∼p−1之间。 可以对这列数进行两种操作&#xff1a; 添加操作&#xff1a;向序列后添加一个数&#xff0c;序列长度变成 n1&#x…...

网站怎么做有创意/优秀软文案例

首先介绍矩阵的迹&#xff08;trace&#xff09;的概念&#xff1a; 如果一个矩阵是方阵&#xff0c;那它的迹tr(A)等于对角线的元素之和。 多元函数判断驻点性质的方法&#xff1a; 找到多元函数jacobian向量&#xff08;即目标函数对自变量的一阶偏导数向量&#xff09;为…...

wordpress免登录评论/如何网站关键词优化

身处大数据时代&#xff0c;各企业对云计算相关业务越来越依赖。各种服务器类型的市场需求也愈之加大&#xff0c;更多的虚拟机主机服务商转阵成服务器&#xff0c;当然不乏新踏入行业的新人。怎么才能做好业务&#xff0c;根据以往虚拟机的经验来说&#xff0c;选择一套管理系…...