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

【Linux】进程间通信介绍 | 管道

​🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述

目录

    • 👉进程间通信介绍👈
      • 进程间通信目的
      • 进程间通信发展和分类
    • 👉管道👈
      • 什么是管道
      • 管道的原理
      • 匿名管道
      • 管道的特点
      • mini版进程池的实现
      • 命名管道
      • 匿名管道与命名管道的区别
    • 👉总结👈

👉进程间通信介绍👈

进程间通信(Interprocess Communication)就是两个进程之间进行通信。进程是具有独立性(虚拟地址空间 + 页表保证进程运行的独立性),所以进程间通信成本会比较高!进程间通信的前提条件是先让不同的进程看到同一份资源(内存空间),该资源不能隶属于任何一个进程,应该属于操作系统,被进行通信的进程所共享。

进程间通信目的

单进程无法使用并发能力,更加无法实现多进程协同,那么就有了进程间通信。进程间通信的目的如下:

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

进程间通信发展和分类

进程间通信的发展和分类如下:

  • Linux 原生能提供的管道,管道主要包括匿名管道 pipe 和命名管道。
  • SystemV 进程间通信,System V IPC 主要包括 System V 消息队列、System V 共享内存和 System V 信号量。System V 只能本地通信。
  • POSIX 进程间通信,POSIX IPC 主要包括消息队列、共享内存、信号量、互斥量、条件变量和读写锁。POSIX 进程通信既能进行本地通信,又能进行网络远程通信,具有高扩展和高可用性。

👉管道👈

什么是管道

日常生活中,有非常多管道,如:天然气管道、石油管道和自来水管道等。管道是 Unix 中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的一个数据流称为管道。管道传输的都是资源,并且只能单向通信。

在这里插入图片描述

管道的原理

管道的本质就是文件。与文件的区别就是管道中的数据是不用写入到磁盘中的(持久化)。进程间通信都是内存级别的通信,如果还要将数据写入到内存,那么通信的效率就会大大下降。

在这里插入图片描述
在这里插入图片描述
如何做到让不同的进程看到同一份资源的呢?fork 创建子进程,让子进程继承父进程的与进程管理相关的内核数据结构,这样就能够让具有血缘关系的进程进行进程间通信,常用于父子进程。

匿名管道

匿名管道就是没有名字的管道,可以通过系统调用 pipe 来创建匿名管道。pipe 函数的参数是 int pipefd[2],它是输出型参数,通过 pipefd 数组可以拿到系统为我们创建的匿名管道文件。pipefd[0] 是读端,pipefd[1] 是写端(巧记:0 像嘴巴,用来读书;1 像钢笔,用来写字)。如果管道创建成功,返回值为 0;如果管道创建失败,返回值为 -1,并设置相应的错误码。

在这里插入图片描述
在这里插入图片描述

Makefile 文件

mypipe:mypipe.ccg++ $^ -o $@ -std=c++11 #-D DEBUG
.PHONY:clean
clean:rm -f mypipe

注:.cc 后缀也是 C++ 文件的表示方法之一,-D 是命令行定义,可用于 Debug。如果一个变量只声明并没有被使用,在 Realease 版本下会有大量的告警。为了避免告警,可以将该变量强转为 void。assert 在 Realease 版本下不起作用。

#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(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 process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}}// close(pipefd[0]); exit(0);    // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);}pid_t ret = waitpid(id, nullptr, 0);    // 阻塞等待assert(ret > 0);(void)ret;close(pipefd[1]);return 0;
}

在这里插入图片描述
注:不能定义全局缓冲区 buffer 来通信,因为有写时拷贝的存在会保证父子进程信息的独立,所以就无法通过全局的 buffer 来进行通信。

管道的特点

  • 管道是用来进行具有血缘关系的进程进行进程间通信,常用于父子进程。
  • 匿名管道需要在创建子进程之前创建,因为只有这样才能复制到管道的操作句柄,与具有亲缘关系的进程实现访问同一个管道通信。
  • 管道本质是内核中的一块缓冲区,多个进程通过访问同一块缓冲区实现通信。
  • 显示器也是一个文件,父子进程同时向显示器写入的时候,没有一个进程等另一个进程的情况,也就是说缺乏访问控制。而管道是为了让进程间协同,其提供了访问控制。
    • 写快,读满,将管道文件写满了就不能再写了
    • 写满,读快,管道文件中没有数据的时候,读端必须等写端进行数据写入
    • 写关,读 0,标识读到了管道文件的结尾
    • 读关,写继续写,操作系统会终止写进程。
  • 管道提供的是面向流式的通信服务(面向字节流),需要定制协议来进行数据区分。
  • 管道是基于文件的,文件的生命周期是随进程的,那么管道的生命周期也是随进程的。
  • 一般而言,内核会对管道操作进行同步与互斥
  • 管道是单向通信的,就是半双工通信的一种特殊情况,数据只能向一个方向流动。需要双方通信时,需要建立起两个管道。半双工通信就是要么在收数据,要么在发数据,不能同时在收数据和发数据(比如两个人在交流时,一个人在说,另一个人在听);而全双工通信是同时进行收数据和发数据(比如两个人吵架的时候,相互问候对方,一个人既在问候对方又在听对方的问候)。
  • 当要写入的数据量不大于 PIPE_BUF 时,Linux 将保证写入的原子性。
  • 当要写入的数据量大于 PIPE_BUF 时,Linux 将不再保证写入的原子性。
  • 指事务的不可分割性,一个事务的所有操作要么不间断地全部被执行,要么一个也没有执行。

在这里插入图片描述

写关读 0 的情况

#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(pipefd[1]);char buffer[1024];while (true){// 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等// 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}else if(s == 0){cout << "writer quit(father), me quit too!!!" << endl;break;}}// close(pipefd[0]); exit(0);    // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);if(count == 5){cout << "writer quit(father)" << endl;break;}}close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0);    // 阻塞等待assert(ret != -1);(void)ret;return 0;
}

在这里插入图片描述

读关,写继续写,操作系统终止写进程

#include <iostream>
#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstdio>
#include <cstring>using namespace std;int main()
{// 1. 创建管道// pipefd[0]:读端(0像嘴巴,读书)// pipefd[1]:写端(1像钢笔,写字)int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n; // 避免Realease编译时出现大量告警// 条件编译可以搭配命令行定义来进行debug
#ifdef DEBUGcout << "pipefd[0]:" << pipefd[0] << endl; // 3cout << "pipefd[1]:" << pipefd[1] << endl; // 4
#endif// 2. 创建子进程// fork创建子进程失败返回-1pid_t id = fork();assert(id != -1);if (id == 0){// 关闭子进程不需要的fd,子进程进行读取close(pipefd[1]);char buffer[1024];int count = 0;while (true){// 写入的一方,fd没有关闭,如果有数据,就读,没有数据就等// 写入的一方,fd关闭, 读取的一方,read会返回0,表示读到了文件的结尾!ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;++count;cout << "child process[" << getpid() << "]" << "get a message, Fathe#" << buffer << endl;}else{cout << "writer quit(father), me quit too!!!" << endl;break;}// 验证读提前退出,写继续写,操作系统终止写进程的情况if(count == 5){cout << "child quit!" << endl;break;}}close(pipefd[0]); exit(0);    // 进程退出,文件描述符会被关掉,不代表文件被关掉}// 关闭父进程不需要的fd,父进程进行写入close(pipefd[0]);string message = "我是父进程,我正在给你发消息";char send_buffer[1024];int count = 0;while (true){// 构造变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);write(pipefd[1], send_buffer, strlen(send_buffer));sleep(1);if(count == 10){cout << "writer quit(father)" << endl;break;}}close(pipefd[1]);pid_t ret = waitpid(id, nullptr, 0);    // 阻塞等待assert(ret > 0);(void)ret;return 0;
}

在这里插入图片描述

读快写满和读慢写快的两种情况,大家可以自己尝试一下!

mini版进程池的实现

实现思路:首先先定义一些任务并将这些任务加载。然后创建管道文件和子进程,将子进程的写端关闭并等待父进程派发任务(父进程向管道文件中写入数据就是某个子进程派发任务)。如果父进程没有给子进程派发任务的话,子进程只能阻塞等待(对应写满读快的情况)。注:该进程池是单机的负载均衡。

// hpp为后缀的文件既有函数的声明又有函数的定义
// Task.hpp
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <functional>
#include <map>// 包装器:定义一种函数类型,其返回值为void,没有参数
// using func = std::function<void()> ;	// C++11的做法
typedef std::function<void()> func;// callBacks存的是函数类型,也就是任务
std::vector<func> callBacks;
// desc是任务的下标和任务的描述
std::map<int, std::string> desc;void readMySQL()
{std::cout << "sub process[" << getpid() << "] 执行访问数据的任务\n" << std::endl;
}void execulUrl()
{std::cout << "sub process[" << getpid() << "] 执行URL解析\n" << std::endl;
}void cal()
{std::cout << "sub process[" << getpid() << "] 执行加密任务\n" << std::endl;
}void save()
{std::cout << "sub process[" << getpid() << "] 执行数据持久化任务\n" << std::endl;
}// 加载任务
void load()
{desc[callBacks.size()] = "readMySQL: 读取数据库";callBacks.push_back(readMySQL);desc[callBacks.size()] = "execulUrl: 进行URL解析";callBacks.push_back(execulUrl);desc[callBacks.size()] = "cal: 进行加密计算";callBacks.push_back(cal);desc[callBacks.size()] = "save: 进行数据的文件保存";callBacks.push_back(save);
}// 展示任务列表
void showHandler()
{for(const auto& kv : desc){std::cout << kv.first << "\t" << kv.second << std::endl;}
}// 返回任务的个数
int handlerSize()
{return callBacks.size();
}// ProcessPool.cc
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.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));// 写端退出,读端读到0,则写端也要退出if(s == 0){quit = true;return -1;}assert(s == sizeof(uint32_t));  // 要求必须读到4给字节return command;
}void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));// 父进程通过fd唤醒子进程并给它派发任务desc[command]cout << "father process call child process[" << who << "] execul " << desc[command] << " through " << fd << endl;
}int main()
{// 加载任务load();// pid_t是子进程的id, int是写端的fdvector<pair<pid_t, int>> slots;// 先创建多个进程for(int i = 0; i < PROCESS_NUM; ++i){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0); // 判断管道是否创建成功(void)n;// 创建子进程pid_t id = fork();assert(id != -1);   // 判断子进程是否创建成功// child processif(id == 0){// 关闭子进程的写端close(pipefd[1]);// 子进程等待父进程派发任务while(true){// false表示父进程的写端没有关闭bool quit = false;// 如果父进程不派发任务,子进程就阻塞等待int command = waitCommand(pipefd[0], quit);// 父进程的写端关闭,子进程的读端也要退出if(quit)break;// 执行对应的任务if(command >= 0 && command < handlerSize())callBacks[command]();elsecout << "非法command: " << command << endl;}cout << "sender quit, receiver quit too!!!" << endl;  close(pipefd[0]);exit(0);}// father process// 关闭父进程的读端,将子进程的id和父进程的写端保存到slots中close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1]));}// 父进程随机给子进程派发任务srand((unsigned int)(time(nullptr) ^ getpid() ^ 2023222));    // 让数据源更随机int count = 0;  // 父进程给子进程总共派发5个任务后,关闭父进程的所有写端while(true){// 随机选取一个任务int command = rand() % handlerSize();// 随机选取一个子进程,随机数方式的负载均衡int choice = rand() % slots.size();// 把任务派发给指定的进程sendAndWakeup(slots[command].first, slots[command].second, command);sleep(1);++count;if(count == 5){cout << "父进程的任务全部派发完成" << endl;break;}// 下方的代码是用户指定做哪一个任务// int select;// int command;// cout << endl;// cout << "############################################" << endl;// cout << "#   1. show funcitons      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);//     sleep(1);// }// else// {//     cout << "select error!" << endl;//     continue;// }}// 关闭fd, 所有的子进程都会读到0,关闭读端并退出for (const auto &slot : slots){close(slot.second);}// 等待子进程for (const auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}

随机派发任务

在这里插入图片描述

用户派发指定任务

在这里插入图片描述

命名管道

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

在这里插入图片描述

命令行创建管道文件

while :; do echo "hello world"; sleep 1; done > name_pipe	#向命名管道写入数据cat < name_pipe #读取命名管道中的数据

在这里插入图片描述

命名管道就是有名字的管道文件,如上图所示。命名管道主要用于没有任何血缘关系的两个进程进行通信。创建命名管道文件的接口如下:

  • int mkfifo(const char *pathname, mode_t mode);
  • pathname 是命名管道所在的路径和命名管道的名字,如果是在当前路径下创建管道文件,只需要提供管道文件的名字即可。如果不是,需要指明管道文件所处的路径。
  • mode 是管道文件的权限。

模拟客户端和服务端

# Makefile
.PHONY:all
all: server clientserver:server.cxxg++ $^ -o $@ -std=c++11
client:client.cxxg++ $^ -o $@ -std=c++11.PHONY:clean
clean:rm -f client server
// Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include <iostream>
#include <ctime>// 日志信息等级
#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3const std::string msg[] = {"Debug", "Notice", "Warning", "Error"};// 输入日志信息的函数
std::ostream& Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}#endif// Common.hpp
#ifndef _COMM_H_
#define _COMM_H_#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include "Log.hpp"using namespace std;#define MODE 0666   // 权限
#define SIZE 128    // 缓冲区大小string ipcPath = "./fifo.ipc";#endif// server.cxx
#include "Comm.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("server 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 end of file, client quit, server quit too!!!" << endl;break;}else{perror("read error");exit(3);}}// 4. 关闭管道文件close(fd);Log("关闭管道文件成功", Debug) << "step 3" << endl;// 5. 删除管道文件unlink(ipcPath.c_str());Log("删除管道文件成功", Debug) << "step 4" << endl;return 0;
}// client.cxx
#include "Comm.hpp"int main()
{// 1. 获取管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("client open");exit(1);}// 2. IPC过程string buffer;while(true){cout << "Please Enter Message :>";getline(cin, buffer);write(fd, buffer.c_str(), buffer.size());}// 3. 关闭管道文件close(fd);return 0;
}

在这里插入图片描述
在这里插入图片描述
注:client 是客户端,客户端向管道文件写入数据,也就是给服务端发信息;server 是服务端,服务端读取管道文件的数据,接收客户端发过来的信息。管道文件只要在服务端创建接口,客户端不需要创建管道文件。Ctrl + Backspace 可以删除字符。

服务端有多个子进程竞争客户端发来的信息

# Makefile
.PHONY:all
all: multiServer clientmultiServer:server.cxxg++ $^ -o $@ -std=c++11
client:client.cxxg++ $^ -o $@ -std=c++11.PHONY:clean
clean:rm -f client multiServer
// server.cxx
#include "Comm.hpp"static void getMessage(int fd)
{char buffer[SIZE];while(true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer) - 1);if(s > 0){cout << "["  << getpid() << "] " << "client say> " << buffer << endl;}else if(s == 0) // 客户端退出{// end of filecerr << "["  << getpid() << "] " << "read end of file, client quit, server quit too!!!" << endl;break;}else{perror("read error");exit(3);}}
}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("server open");exit(2);}Log("打开管道文件成功", Debug) << "step 2" << endl;int nums = 3;for(int i = 0; i < nums; ++i){pid_t id = fork();if(id == 0){// 3. 编写正常的通信代码getMessage(fd);exit(1);}}for(int i = 0; i < nums; ++i){// -1表示等待任意一个子进程waitpid(-1, nullptr, 0);}// 4. 关闭管道文件close(fd);Log("关闭管道文件成功", Debug) << "step 3" << endl;// 5. 删除管道文件unlink(ipcPath.c_str());Log("删除管道文件成功", Debug) << "step 4" << endl;return 0;
}

在这里插入图片描述

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

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

👉总结👈

本篇博客主要讲解了什么是进程间通信、进程间通信的目的、什么是管道、管道的原理、匿名管道、管道的特点、命名管道等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

相关文章:

【Linux】进程间通信介绍 | 管道

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;进程间通信…...

这次说说腾讯的一场 35K—55K 的 Android 高工面试

一、面试的由来 事情是这样的&#xff0c;因为跟公司发展一些想法的不同&#xff0c;早在十月份的时候就有了跳槽的想法&#xff0c;但是碍于老大的面子就一直就没有跟人事说出口&#xff0c;打算着等到年后金三银四在试试跳槽。 但是发生一件事终于让我忍不住了&#xff0c;…...

Jenkins第一讲

目录 一、Jenkins 1.1 敏捷开发与持续集成 1.1.1 敏捷开发 1.1.2 持续集成 1.2 持续集成工具 1.2.1 jenkins和hudson 1.2.2 技术组合 1.2.3 部署方式对比 1.3 安装Jenkins 1.3.1 下载Jenkins的war包 1.3.2 开启Jenkins 1.4 Jenkins全局安全配置 1.5 使用Jenkins部…...

变分推断 | MATLAB实现VBMC变分贝叶斯蒙特卡洛模拟的贝叶斯推断

变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断 目录 变分推断 | MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断效果一览基本介绍研究内容模型描述模型设计参考资料效果一览 基本介绍 MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断。变分贝叶斯蒙特卡洛(VBMC)是…...

代码随想录【Day25】| 216. 组合总和 III、17. 电话号码的字母组合

216. 组合总和 III 题目链接 题目描述&#xff1a; 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数&#xff0c;并且每种组合中不存在重复的数字。 说明&#xff1a; 所有数字都是正整数。 解集不能包含重复的组合。 示例 1: 输入: k 3, n 7 输…...

web中git漏洞的形成的原理及使用

目录 1.Git漏洞的成因 1.不正确的权限设置&#xff1a; 2.代码注入漏洞&#xff1a; 3.未经身份验证的访问&#xff1a; 4.非安全传输&#xff1a; 5.跨站脚本攻击&#xff08;XSS&#xff09;&#xff1a; 2.git泄露环境的搭建 git init&#xff1a; git add&#xff1…...

【SPSS】单样本T检验分析详细操作教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…...

计算机网络笔记、面试八股(三)—— HTTPS协议

本章目录3. HTTPS协议3.1 HTTPS协议简介3.2 SSL/TLS协议3.2.1 SSL/TLS功能的实现3.3 HTTP和HTTPS的区别3.4 HTTPS协议的优点3.5 HTTPS协议的缺点3.6 HTTPS协议的工作流程3.7 HTTPS是如何解决HTTP的缺点的3.7.1 解决内容可能被窃听的问题——加密3.7.1.1 方法1.对称加密3.7.1.2 …...

浅谈liunx init.d 和 rc.local 两种起动方式

浅谈liunx init.d 和 rc.local 两种起动方式 以rabbitmq 举例 &#xff08;一&#xff09;.init.d 方式 开机自动重启设置 1.在/etc/init.d 目录下新建一个 rabbitmq [rootlocalhost init.d]# vi rabbitmq具体脚本如下所示&#xff1a; #!/bin/bash # # chkconfig: 2345 …...

元宇宙+教育,正在引发哪些剧烈变革?机会在哪里?丨圆桌实录

图片来源&#xff1a;由无界AI绘画工具生成2月23日&#xff0c;温州元宇宙创新中心为2023年第一批申请入驻的项目企业举办了签约仪式。温州临境网络科技有限公司、温州好玩文化产业有限公司、温州云兮科技有限公司&#xff08;筹&#xff09;等企业完成签约。这意味着&#xff…...

追梦之旅【数据结构篇】——详解C语言实现顺序队列

详解C语言实现顺序队列~&#x1f60e;前言&#x1f64c;预备小知识&#x1f64c;队列的概念及结构&#x1f60a;1.顺序队列头文件编写&#x1f64c;2.Queue.c文件的编写&#x1f64c;1&#xff09;队列的初始化函数实现&#x1f60a;2&#xff09;队列的销毁函数实现&#x1f6…...

使用自己的数据集Fine-tune PaddleHub预训练模型

使用自己的数据Fine-tune PaddleHub预训练模型 果农需要根据水果的不同大小和质量进行产品的定价&#xff0c;所以每年收获的季节有大量的人工对水果分类的需求。基于人工智能模型的方案&#xff0c;收获的大堆水果会被机械放到传送带上&#xff0c;模型会根据摄像头拍到的图片…...

带组态物联网平台源码 代码开源可二次开发 web MQTT Modbus

物联网IOT平台开发辅助文档 技术栈&#xff1a;JAVA [ springmvc / spring / mybatis ] 、Mysql 、Html 、 Jquery 、css 使用协议和优势&#xff1a; TCP/IP、HTTP、MQTT 通讯协议 1.1系统简介 IOT通用物联网系统平台带组态&#xff0c;是一套面向通用型业务数据处理的系统…...

计算机网络的发展历程

计算机网络的历史可以追溯到20世纪60年代。那个时候&#xff0c;计算机还非常昂贵&#xff0c;只有少数大型机可以被用于处理重要任务。这些大型机通常被安装在大型企业、政府机构和大学中。由于这些机器非常昂贵&#xff0c;许多企业、机构和大学只能通过终端连接来访问它们。…...

【华为OD机试模拟题】用 C++ 实现 - 不含 101 的数(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

面试题-下单后位置信息上报的方案

面试题&#xff1a;外卖下单后每10min上报位置事件的具体实现方案。需要考虑哪些点。存储方案&#xff1a;考虑到数据量很大&#xff0c;需要快速响应查询请求&#xff0c;建议使用分布式存储方案&#xff0c;如 HBase、MongoDB 等。这些分布式存储系统可以水平扩展&#xff0c…...

视觉人培训团队把它称之为,工业领域人类最伟大的软件创造,它的名字叫Halcon

目前为止&#xff0c;世界上综合能力强大的机器视觉软件&#xff0c;&#xff0c;它的名字叫Halcon。 视觉人培训团队把它称之为&#xff0c;工业领域人类最伟大的软件创造&#xff0c;它的名字叫Halcon。 持续不断更新最新的图像技术&#xff0c;软件综合能力持续提升。 综…...

干了2年的手工点点点,感觉每天浑浑噩噩,我的自动化测试之路...

作为一个测试人员&#xff0c;从业年期从事手工测试的工作是没有太多坏处的&#xff0c;当然&#xff0c;如果一直点来点去那么确实自身得不到提高&#xff0c;这时候选择学习自动化测试是一件很有必要的事情&#xff0c;一来将自己从繁重的重复工作中解放出来&#xff0c;从事…...

嵌入式系统硬件设计与实践(学习方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 刚读书的时候&#xff0c;对什么是嵌入式&#xff0c;其实并不太清楚。等到自己知道的时候&#xff0c;已经毕业很多年了。另外对于计算机毕业的学…...

如何拥有自己的Gitee代码仓库

本教程适用码云代码托管平台 https://gitee.com/ 首先在电脑上安装Git&#xff08;哔站有安装Git教程&#xff09;和注册gitee账号后再来阅读此教程 1、在设置页面中点击 SSH公钥 2、点击 怎样生成公钥 3、点击公钥管理 4、点击 生成\添加SSH公钥 5、打开终端 输入如图红框中的…...

通用信息抽取技术UIE产业案例解析,Prompt 范式落地经验分享!

想了解用户的评价究竟是“真心夸赞”还是“阴阳怪气”&#xff1f;想快速从多角色多事件的繁杂信息中剥茧抽丝提取核心内容&#xff1f;想通过聚合相似事件准确地归纳出特征标签&#xff1f;……想了解UIE技术在产业中的实战落地经验&#xff1f;通用信息抽取技术 UIE 产业案例…...

integrationobjects/OPC AE Client ActiveX Crack

使用 OPC AE 客户端 ActiveX 进行快速 OPC 警报和事件客户端编程&#xff01; OPC AE Client ActiveX包括多个 OPC ActiveX 控件&#xff0c;可以轻松嵌入到最流行的 OLE 容器中。这允许用户与任何 OPC AE 服务器连接并实时检索警报和事件。 这种易于使用的 OPC AE ActiveX 简化…...

JavaScript HTML DOM 简介

文章目录JavaScript HTML DOM 简介HTML DOM (文档对象模型)HTML DOM 树查找 HTML 元素通过 id 查找 HTML 元素通过标签名查找 HTML 元素通过类名找到 HTML 元素下面我们将学到如下内容JavaScript HTML DOM 简介 通过 HTML DOM&#xff0c;可访问 JavaScript HTML 文档的所有元素…...

interrupt多线程设计模式

1. 两阶段终止-interrupt Two Phase Termination 在一个线程T1中如何“优雅”终止线程T2&#xff1f;这里的【优雅】指的是给T2一个料理后事的机会。 错误思路 ● 使用线程对象的stop()方法停止线程&#xff08;强制杀死&#xff09; —— stop&#xff08;&#xff09;方法…...

Spring IoC 和 Spring AOP

Spring IoC Ioc&#xff08;Inversion of control&#xff1a;即控制反转&#xff09;是一种设计思想&#xff0c;而不是一种具体的技术实现。IoC的思想就是将原本在程序中手动创建对象的控制权交给Spring框架来管理。 不过&#xff0c; IoC 并非 Spring 特有&#xff0c;在其…...

taobao.top.oaid.merge( OAID订单合并 )

&#xffe5;开放平台免费API必须用户授权 基于OAID&#xff08;收件人ID&#xff0c; Open Addressee ID)做订单合并&#xff0c;确保相同收件人信息的订单合并到相同组。 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请…...

Python自动获取海量ip,再也不用愁被封啦~

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 本次网站&#xff1a; 本文所有模块\环境\源码\教程皆可点击文章下方名片获取此处跳转 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 模块使用&#xff1a; import parsel >>> pip install parsel…...

XLua学习笔记 { }

Lua调用C# 通过生成的适配代码进行调用 把在白名单上和打上[LuaCallCSharp]标签的C#类转换成Lua的table&#xff0c;然后注册C#类的方法和属性到table中。性能好&#xff0c;但占用安装包的内存大 通过反射机制进行调用 性能差&#xff0c;在运行的时候才去查找C#的方法&#…...

推荐程序员收藏的几个技术社区以及工具网站

常用技术社区 1、GitHub 网站地址&#xff1a;https://github.com/ 全球最大的开源社区&#xff0c;这点我想大家都清楚。但是今年被微软收购&#xff0c;之前很多的人在那说可能以后GitHub就会变样&#xff0c;但是事实并非如此&#xff0c;目前还没有收到什么信息&#xff0c…...

StopWatch计时器

前言 开发中&#xff0c;为了评估性能&#xff0c;我们通常会使用System.currentTimeMillis() 去计算程序运行耗时 long startTimeSystem.currentTimeMillis();//业务代码... long endTimeSystem.currentTimeMillis(); System.out.println("耗时:" (endTime-startT…...