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

Linux——进程间通信之管道

进程间通信之管道

在这里插入图片描述

文章目录

  • 进程间通信之管道
  • 1. 进程间通信
    • 1.1 为什么要进行进程间的通信
    • 1.2 如何进行进程间的通信
    • 1.3 进程间通信的方式
  • 2. 管道
    • 2.1 匿名管道
      • 2.1.1 系统调用pipe()
      • 2.1.2 使用匿名管道进行通信
      • 2.1.1 匿名管道四种情况
      • 2.1.2 匿名管道的五大特性
      • 2.1.3 进程池
    • 2.2 命名管道
      • 2.2.1 创建命名管道
      • 2.2.2 利用命名管道进行进程间通信
      • 2.2.3 命名管道的一个特性

1. 进程间通信

1.1 为什么要进行进程间的通信

对于这个问题,答案显而易见:一个进程必然不能解决所有的问题,系统中往往需要多个进程的协作来进行工作,而进程间的协作就需要进程之间进行信息的交互,这个过程也叫做进程间的通信

简单来说,进程间的通信可以实现以下功能:

  • 数据传输
  • 进程控制
  • 资源共享
  • 事件通知
  • ………………

1.2 如何进行进程间的通信

由于进程具有独立性,因此两个进程之间不能直接进行数据之间的传输(否则就会破坏进程的独立性)

因此我们需要一片公共的区域来供进程之间进行信息交流,而这片公共区域就是由OS操作系统提供

根据上面的分析,我们也可以得出关于进程间通信的本质:

进程间通信的实质上就是:让不同的进程看到同一份资源

1.3 进程间通信的方式

根据操作系统OS提供公共区域方式的不同,进程间的通信也会有不同的形式,例如:

  • 管道
  • 共享内存
  • 信号量
  • 消息队列

2. 管道

我们来假设这样一个场景:

一个进程同时以读写的方式打开一个文件,那么就会产生两个文件描述符fd:

在这里插入图片描述

此时,创建子进程,子进程会继承父进程属性:

在这里插入图片描述

我们可以发现,这时子进程和父进程就同时看到同一份文件file了。这时,如果我们关闭父进程的写端,再关闭子进程的读端:

在这里插入图片描述

这样,就可以通过子进程向公共文件写数据,父进程向公共文件读数据的方式,进行父子进程之间的通信了。


向上面这样的,基于文件的形式进行进程间通信的方式,叫做管道

通过上面的例子,我们也知道:管道只允许一端读,一端写,因此基于管道的通信都是单向的

管道可以分为匿名管道和命名管道

2.1 匿名管道

2.1.1 系统调用pipe()

系统不允许用磁盘文件当作进程间通信的公共资源,为此系统提供了系统调用pipe()来为我们创建进程通信需要的文件:

#include <unistd.h>
int pipe(int pipefd[2]);
  • 该系统调用会创建并以读写方式打开一个不存在于磁盘且不需要向磁盘刷新的,只存在于内存中的匿名文件(因为这个文件只需用来给两个进程进行通信,不需要持久的保存),这个文件就叫做匿名管道
  • 数组pipefd是一个输出型参数,用于存放匿名管道的fd,其中pipefd[0]存放的是读端;pipefd[1]存放的是写端

2.1.2 使用匿名管道进行通信

使用pipe()创建了系统调用后,我们再创建一个子进程,让子进程继承父进程的数据,此时父子进程就会同时以读写的方式指向同一个匿名管道了。最后只要确定数据传输方向,关闭父进程的读(写)端,关闭子进程的写(读)端,就可以正常进程进程间的通信

同时,我们应该清楚:

由于是通过创建子进程的方式,来让子进程继承父进程的数据,使其指向相同的匿名管道来进行通信,因此匿名管道只能用来进行血缘进程之间的通信(通常用于父子进程)

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)	//写端操作
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 20){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)	//读端操作
{char buf[1024] = {0};int cnt = 20;while(cnt--){read(rfd, buf, sizeof(buf) - 1); //-1:预留'\0',防止出现错误printf("%s\n", buf);}
}int main()
{int pipefd[2] = {0};	int ret = pipe(pipefd);	//创建匿名管道if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id)	//子进程{//chile(w)close(pipefd[0]);	//关闭读端my_write(pipefd[1]);	//进行写操作exit(-1);}//father(r)close(pipefd[1]);	//关闭写端my_read(pipefd[0]);	//进行读操作wait(NULL);	//等待子进程退出,防止出现僵尸进程return 0;
}

效果如图所示:

在这里插入图片描述

2.1.1 匿名管道四种情况

情况一:写端停止写入并且管道没有数据,那么读端就会阻塞,直到读到数据

例如,将上述代码的my_write()函数修改:

void my_write(int wfd)
{//写端不写
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到,如果写端不向读端写入数据,并且管道没有数据,读端就会陷入阻塞等待状态

情况二:写端一直在写直到管道写满,读端不读,那么写端就会阻塞等待,直到管道的数据被读取

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{int cnt = 0;char a = 'A';//每次只写入一个字符,并输出输入的字符个数while(1){write(wfd, &a, 1);printf("cnt: %d\n", cnt++);}
}void my_read(int rfd)
{//读端不读
}

重新编译后,运行效果如图:

在这里插入图片描述

可以看到:当读端不读时,写端向管道写入了65535 + 1个字符后,就进入了阻塞等待状态,这说明管道已经被填满了。同时也可以推出,在博主所用的系统中,默认的管道大小为65536Byte也就是64kB

如果我们再次修改代码,让读端每2秒读取一次管道:

void my_read(int rfd)
{char buf[1024] = {0};while(1){sleep(2);int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)break;printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

可以总结:当管道的部分数据被读取后,写端有重新写入数据了

情况三:写端关闭,当读端读完管道的数据后,读端就会读到管道的末尾(相当于读到文件尾),自动关闭读端

例如,将上述代码的my_write()、my_read()函数修改:

void my_write(int wfd)
{const char* str = "i am child process: ";int cnt = 0;char buf[128] = {0};pid_t id = getpid();while (cnt != 5){snprintf(buf, sizeof(buf), "message: %s, id: %d, cnt: %d\n", str, id, cnt++);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};while(1){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n)	//如果read的返回值为0,说明写端关闭了fd,读端读到了管道末尾{printf("no data be read, read exit\n");break;}printf("%s\n", buf);}
}

重新编译后,运行效果如图:

在这里插入图片描述

情况四:读端关闭,写端还在写,系统就会通过发送信号的方式强制终止写端(kill -13 child_pid)

将整体代码修改如图:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wait.h>void my_write(int wfd)
{const char* str = "i am child process: ";char buf[128] = {0};pid_t id = getpid();while (1){snprintf(buf, sizeof(buf), "message: %s, id: %d\n", str, id);write(wfd, buf, strlen(buf));sleep(1);}
}void my_read(int rfd)
{char buf[1024] = {0};int cnt = 5;while(cnt--){int n = read(rfd, buf, sizeof(buf) - 1);if (0 == n){printf("no data be read, read exit\n");break;}printf("%s\n", buf);}printf("read will close\n");sleep(1);close(rfd);
}int main()
{int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return 1;}pid_t id = fork();if (0 == id){//chile(w)close(pipefd[0]);my_write(pipefd[1]);printf("child close wfd\n\n");close(pipefd[1]);exit(-1);}//father(r)close(pipefd[1]);my_read(pipefd[0]);int status = 0;int rid = waitpid(id, &status, 0);if (rid == id){printf("child process singal code: %d\n", status & 0x7f);}return 0;
}

重新编译后,运行效果如图:

在这里插入图片描述

2.1.2 匿名管道的五大特性

  • 同步机制:管道在处理数据读写时,确保数据的有序性和正确性的一种控制方式
  • 血缘进程通信:由于匿名管道的构建建立在进程继承的基础上,因此匿名管道只允许血缘进程的通信
  • 单向通信(半双工):同一时间,只允许一端读,一端写
  • 文件的生命周期是随进程的:父子进程退出,管道(文件)自动释放
  • pipe是面向字节流的

2.1.3 进程池

进程池的基本概念

进程池是一组预先创建好的进程集合,这些进程处于空闲等待状态,随时准备接收任务并进行处理。父进程可以在任意时候控制子进程的休眠、工作与退出。

进程池的优点

  • 可以复用进程,从而避免了频繁的调用系统函数,节省了资源开销与时间
  • 使用进程池有利于管理,并充分利用系统资源

实现进程池:

进程池程序myprocesspool的程序流程大致如下:

在这里插入图片描述

实现代码:

task.hpp:

#pragma once#include<iostream>
#include<unistd.h>typedef void(*work_t)(pid_t);
typedef void(*task_t)(pid_t);void task_1(pid_t id)
{std::cout << "task_1" << std::endl;
}void task_2(pid_t id)
{std::cout << "task_2" << std::endl;
}void task_3(pid_t id)
{std::cout << "task_3" << std::endl;
}task_t tasks[3] = {task_1, task_2, task_3};void worker(int channel_index)
{while(1){int task_index = 0;int n = read(0, &task_index, sizeof(int));if (0 == n){std::cout << "write close, channel: " << channel_index << " closing…………" << std::endl; break;}std::cout << "channel: " << channel_index << "  pid: " << getpid() << ": i am working: ";tasks[task_index](getpid());}
}

processpool.cc:

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctime>
#include "task.hpp"#define TIME 5//return value
enum
{USAGE_ERROR = 1,PROCESS_NUM_ERROR, PIPE_ERROR,SELECT_CHANNEL_ERROR,SELEDT_TASK_ERROR
};void usage()
{std::cout << "Usage: ./processpool [process_num]" << std::endl;
}class channel
{
public:channel(std::string name, int rfd, int wfd, pid_t child_id): _name(name), _rfd(rfd), _wfd(wfd), _child_id(child_id){}const std::string& get_name(){return _name;}int get_rfd(){return _rfd;}int get_wfd(){return _wfd;}pid_t get_child_id(){return _child_id;}void close_fd(){close(_wfd);}
private:const std::string _name;const int _rfd;const int _wfd;const pid_t _child_id;
};class processpool
{
public:processpool(int process_num): _process_num(process_num){}//创建管道int create_channel(){std::vector<int> fd;for (int i = 0; i < _process_num; i++){int pipefd[2] = {0};int ret = pipe(pipefd);if (-1 == ret){perror("pipe:");return PIPE_ERROR;}pid_t id = fork();if (id == 0){//child: readclose(pipefd[1]);dup2(pipefd[0], 0);//关闭除第一个子进程外的多个写端if (!fd.empty()){for (auto k : fd)close(k);}//workworker(i);std::cout << "channel " << i << " pid: " << getpid() <<  " exit" << std::endl;exit(-1);}//father: writeclose(pipefd[0]);std::string name = std::string("channel: ") + std::to_string(i);channel c = channel(name, pipefd[0], pipefd[1], id);_channels.push_back(c);fd.push_back(pipefd[1]);}return 0;}    int select_channel(){static int c = 0;int ret = c;c = (++c) % _process_num;return ret;}int select_task(){return rand() % 3;}int control_child(){int cnt = TIME;while(cnt--){//选择一个进程(管道),一个任务int channel_index = select_channel();int task_index = select_task();if (channel_index >= _process_num){std::cout << "select channel error" << std::endl;return SELECT_CHANNEL_ERROR;}if (task_index >= 3){std::cout << "select task error" << std::endl;return SELEDT_TASK_ERROR;}//发送任务int wfd = _channels[channel_index].get_wfd();write(wfd, &task_index, sizeof(int));sleep(2);}return 0;}void PrintDebug(){for (auto channel: _channels){std::cout << channel.get_name() << ": rfd: " << channel.get_rfd() << ": child_id: " << channel.get_child_id() << std::endl;}}void clean_wait(){//关闭所有的写端for (auto channel : _channels){channel.close_fd();pid_t id = waitpid(channel.get_child_id(), nullptr, 0);if (-1 == id){perror("waitpid:");}if(id == channel.get_child_id()){std::cout << "wait child: " << channel.get_child_id() << " success\n" << std::endl;}}}
private:const int _process_num;std::vector<channel> _channels;
};//  ./process num
int main(int argc, char* argv[])
{if (1 == argc){usage();return USAGE_ERROR;}int process_num = std::stoi(argv[1]);if (process_num <= 0){std::cout << "process_num should be grearter than 0" << std::endl;return PROCESS_NUM_ERROR;}srand((unsigned int)time(nullptr));std::cout << "process nums: " << process_num << std::endl;//创建进程池processpool* processpool_1 = new processpool(process_num);//创建管道int ret = processpool_1->create_channel();   if (0 != ret){return ret;}std::cout << "create channels complete" << std::endl; //工作ret = processpool_1->control_child();if (0 != ret){return ret;}//回收 processpool_1->clean_wait();return 0;
}

2.2 命名管道

上文提到,匿名管道只适用于血缘进程之间的通信,那么为了解决没有关系进程间通信的问题,操作系统提供了命名管道

2.2.1 创建命名管道

使用命令创建命名管道:

mkfifo [filename]

例如:

在这里插入图片描述

可以看到,通过命令mkfifo fifo在当前目录创建了一个名为fifo的管道文件,同时可以看到,这个管道文件的文件类型为p(pipe)

使用系统调用创建命名管道:

int mkfifo(const char *pathname, mode_t mode);
  • pathname是管道文件的文件名
  • mode是管道文件的权限
  • 返回值:成功返回0;失败返回-1

2.2.2 利用命名管道进行进程间通信

知道了系统调用后,我们就可以利用管道来进行进程间的通信了:

我们用下面的代码进行演示:

头文件:

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define PATH "FIFO"	//默认文件名
#define MODE 0666	//默认权限//命名管道类
class fifo
{
public:fifo(const std::string& name)	//构造函数创建管道文件: _name(name){int ret = mkfifo(_name.c_str(), MODE);if (-1 == ret){std::cerr << "mkfifo error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}std::cout << "fifo made success" << std::endl;}~fifo(){unlink(_name.c_str());	//进程结束时,利用系统调用unlink()删除管道文件}
private:const std::string _name;
};

服务端server:读

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;	fifo named_pipe(PATH);	//服务端创建管道文件,读端int rfd = open(PATH, O_RDONLY);	char buffer[1024] = {0};while (1){int n = read(rfd, buffer, sizeof(buffer) - 1);if (0 == n)	//返回值为0,说明读到文件尾,即写端关闭{std::cout << "client exit, server also exit" << std::endl;break;}else if (-1 == n){std::cerr << "read error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}else{buffer[n] = 0;std::cout << buffer << std::endl;}}close(rfd);return 0;
}

客户端client:写

#include "common.hpp"int main()
{std::cout << "I am client" << std::endl;int wfd = open(PATH, O_WRONLY);	//客户端,写std::string buffer;while(1){printf("message # ");std::getline(std::cin, buffer);if ("quit" == buffer){printf("client exit\n");break;}int n = write(wfd, buffer.c_str(), buffer.size());if (n != buffer.size()){std::cerr << "write error, " << "errno: " << errno << ", errorstring: " << strerror(errno) << std::endl;exit(-1);}}close(wfd);return 0;
}

效果如图:

在这里插入图片描述

2.2.3 命名管道的一个特性

如果我们在服务端server.cc代码中加一行代码:

#include "common.hpp"int main()
{std::cout << "I am server" << std::endl;fifo named_pipe(PATH);int rfd = open(PATH, O_RDONLY);std::cout << "open success" << std::endl;	//查看客户端是否成功打开管道文件//……………………}

并运行服务端(读端)一段时间后再打开客户端(写端):

在这里插入图片描述

可以看到:在服务端(读端)打开到客户端(写端)未打开的这段时间中,服务端(读端)并没有打开管道文件,而是等客户端(写端)启动后,再打开的管道。

通过这个现象,我们可以得出命名管道的一个特性:

当写端未打开而读端打开时,读端会阻塞,直至写端也打开


本篇完

相关文章:

Linux——进程间通信之管道

进程间通信之管道 文章目录 进程间通信之管道1. 进程间通信1.1 为什么要进行进程间的通信1.2 如何进行进程间的通信1.3 进程间通信的方式 2. 管道2.1 匿名管道2.1.1 系统调用pipe()2.1.2 使用匿名管道进行通信2.1.1 匿名管道四种情况2.1.2 匿名管道的五大特性2.1.3 进程池 2.2 …...

java-排序算法汇总

排序算法&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09; 选择排序&#xff08;Selection Sort&#xff09; 插入排序&#xff08;Insertion Sort&#xff09; 快速排序&#xff08;Quick Sort&#xff09; 归并排序&#xff08;Merge Sort&#xff09; 堆排序&…...

Vscode进行Java开发环境搭建

Vscode进行Java开发环境搭建 搭建Java开发环境(Windows)1.Jdk安装2.VsCode安装3.Java插件4.安装 Spring 插件5.安装 Mybatis 插件5.安装Maven环境6.Jrebel插件7.IntelliJ IDEA Keybindings8. 收尾 VS Code&#xff08;Visual Studio Code&#xff09;是由微软开发的一款免费、开…...

算法学习笔记(五):二叉树一遍历、DFS

一.遍历二叉树 二叉树TreeNode类 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, Tree…...

#Verilog HDL# Verilog中的generate用法集锦

生成块允许复制模块实例或有条件地实例化任何模块。它提供了基于Verilog参数构建设计的能力。当相同的操作或模块实例需要重复多次,或者当某些代码需要根据给定的Verilog参数有条件地包含时,这些语句特别方便。 生成块不能包含端口、参数、specparam声明或指定块。但是,允许…...

简述C++map容器

pair键值对 std::pair在很多关联容器&#xff08;如std::map、std::multimap、std::set、std&#xff1a;multiset等&#xff09;中被广泛应用。以std::map为例&#xff0c;std::map是一个键值对的容器&#xff0c;其中每个元素都是一个std::pair&#xff0c;键用于唯一标识元…...

Vue 学习随笔系列十七 -- 表格样式修改

表格样式修改 文章目录 表格样式修改一、表格背景颜色修改1、方法一2、方法二 二、多级表头颜色修改 一、表格背景颜色修改 1、方法一 表格外套一个 div &#xff0c;修改div的背景色&#xff0c;并将表格背景色设置为透明 参考代码&#xff1a; <template><div cl…...

08 —— Webpack打包图片

【资源模块 | webpack 中文文档 | webpack中文文档 | webpack中文网】https://www.webpackjs.com/guides/asset-modules/?sid_for_share99125_3 Webpack打包图片以8KB为临界值判断 大于8KB的文件&#xff1a;发送一个单独的文件并导出URL地址 小于8KB的文件&#xff1a;导出一…...

01.Django快速入门

一、Django 快速入门 使用最新版本 Django4.2LTS 版本,3 年内不需要更换版本由浅入深讲解&#xff0c;浅显易懂课程大纲全面包含 Django 框架知识点&#xff0c;内容丰富全面细致知识点结合项目实战实现全栈项目应用 Django 官网(文档): https://docs.djangoproject.com/zh-h…...

【大数据学习 | Spark-Core】spark-shell开发

spark的代码分为两种 本地代码在driver端直接解析执行没有后续 集群代码&#xff0c;会在driver端进行解析&#xff0c;然后让多个机器进行集群形式的执行计算 spark-shell --master spark://nn1:7077 --executor-cores 2 --executor-memory 2G sc.textFile("/home/ha…...

Modern Effective C++ Item 14 如果函数不抛出异常请使用noexcept

C11 noexcept关键字用于指定函数不会抛出异常&#xff0c;有助于提高程序的异常安全性&#xff0c;还能够使编译器生成更加高效的代码。 noexcept 是函数接口的一部分 函数是否声明为 noexcept 是接口设计的一部分&#xff0c;客户端代码可能会依赖这一点。如果一个函数被声明…...

cudatoolkit安装(nvcc -V错误版本解决)

CudaToolKit安装&#xff08;nvcc&#xff09; cudatoolkit 是 CUDA 开发工具包&#xff08;CUDA Toolkit&#xff09; 的核心部分&#xff0c;包含了一系列用于开发和运行 CUDA 应用程序的软件组件。nvcc 是 NVIDIA CUDA 编译器驱动&#xff0c;用于将 CUDA C/C 代码编译成可…...

DTO和VO的区别及使用场景详解

随着互联网的发展&#xff0c;前后端分离的开发模式越来越流行。在前后端数据交互过程中&#xff0c;为了保证数据的安全性和效率&#xff0c;通常会采用 DTO 和 VO 来封装数据。本篇博客将详细介绍 DTO 和 VO 的区别以及使用场景。 大家可能会有个疑问&#xff0c;既然DTO是展…...

百度在下一盘大棋

这两天世界互联网大会在乌镇又召开了。 我看到一条新闻&#xff0c;今年世界互联网大会乌镇峰会发布“2024 年度中国互联网企业创新发展十大典型案例”&#xff0c;百度文心智能体平台入选。 这个智能体平台我最近也有所关注&#xff0c;接下来我就来讲讲它。 百度在下一盘大棋…...

第十六届蓝桥杯模拟赛第二期题解—Java

第十六届蓝桥杯模拟赛/校赛第二期个人题解&#xff0c;有错误的地方欢迎各位大佬指正 问题一(填空题) 【问题描述】 如果一个数 p 是个质数&#xff0c;同时又是整数 a 的约数&#xff0c;则 p 称为 a 的一个质因数。 请问&#xff0c; 2024 的最大的质因数是多少&#xff1f; …...

驱动开发笔记:关于3588GPIO

1.概要 2.内容 1.3588GPIO 关于RK3588的GPIO&#xff08;General-Purpose Input/Output&#xff0c;通用输入输出引脚&#xff09;&#xff0c;以下是一些关键信息和操作指南&#xff1a; 一、GPIO基本概念 定义&#xff1a;GPIO是嵌入式系统中常见的通信接口&#xff0c;…...

【RK3588 Linux 5.x 内核编程】-内核线程与Mutex

内核线程与Mutex 文章目录 内核线程与Mutex1、Mutex介绍1.1 竞争条件1.2 Mutex特性2、Linux内核中的Mutex2.1 初始化Mutex2.1.1 静态方式初始化2.1.2 动态方式初始化2.2 互斥锁获取2.3 互斥锁释放3、Mutex使用示例4、驱动验证在前面的文章中,介绍了如何Linux内核中的线程,但是…...

【0342】分配并初始化 Proc Signal 共享内存 (1)

1. Proc Signal (procsignal)共享内存 Postgres内核在启动postmaster守护进程时候, 会通过函数 ProcSignalShmemInit() 去为 Proc Signal 分配并初始化指定大小的共享内存空间。整个调用链路如下。 (gdb) bt #0 ProcSignalShmemInit () at procsignal.c:118 #1 0x000000000…...

管家婆财贸ERP BR035.回款利润明细表

最低适用版本: 财贸系列 23.5 插件简要功能说明: 报表统计销售单/销售退货单/销售发票回款情况更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--回款利润明细表 插件详细功能文档: 1. 应用中心增加报表【回款利润明细表】 a. b. 查询条件: ⅰ. 日期区间:…...

数据库MYSQL——表的设计

文章目录 前言三大范式&#xff1a;几种实体间的关系&#xff1a;一对一关系&#xff1a;一对多关系&#xff1a;多对多关系&#xff1a; 前言 之前的博客中我们讲解的是关于数据库的增删改查与约束的基本操作&#xff0c; 是在已经创建数据库&#xff0c;表之上的操作。 在实…...

netstat -tuln | grep 27017(显示所有监听状态的 TCP 和 UDP 端口,并且以数字形式显示地址和端口号)

文章目录 1. 确定占用端口的进程使用 lsof 命令使用 fuser 命令 2. 结束占用端口的进程3. 修改 MongoDB 配置文件4. 检查 MongoDB 日志文件5. 重新启动 MongoDB 服务6. 检查 MongoDB 服务状态总结 [rootlocalhost etc]# netstat -tuln | grep 27017 tcp 0 0 127.0.…...

非线性控制器设计原理

非线性控制器设计原理 非线性控制器设计旨在解决非线性系统的控制问题&#xff0c;克服传统线性控制器在处理非线性现象&#xff08;如饱和、死区、耦合、时变性等&#xff09;时的不足。其核心在于利用非线性数学工具和设计方法&#xff0c;使控制系统在非线性条件下具备良好…...

MySQL数据库6——SQL优化

一.SQL优化 1.插入优化 优化1&#xff1a;批量插入 insert into 表名 values(记录1),(记录2),……;优化2&#xff1a;手动提交事务 start transaction; insert into 表名 values(记录1),(记录2); insert into 表名 values(记录1),(记录2); …… commit;优化3&#xff1a;主键顺…...

IDEA配置本地maven

因为idea和maven是没有直接关系的。所以使用idea创建maven工程之前需要将本地的maven配置到idea环境中&#xff0c;这样才可以在idea中创建maven工程。配置方法如下&#xff1a; 1.1 配置本地maven 第一步&#xff1a;关闭当前工程&#xff0c;回到idea主界面找到customize--…...

学习日记_20241123_聚类方法(高斯混合模型)续

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…...

SpringMVC——简介及入门

SpringMVC简介 看到SpringMVC这个名字&#xff0c;我们会发现其中包含Spring&#xff0c;那么SpringMVC和Spring之间有怎样的关系呢&#xff1f; SpringMVC隶属于Spring&#xff0c;是Spring技术中的一部分。 那么SpringMVC是用来做什么的呢&#xff1f; 回想web阶段&#x…...

文件操作完成后,为什么要关闭文件

原因包括&#xff1a; 释放系统资源&#xff1a;打开文件时&#xff0c;操作系统会分配资源&#xff0c;如文件描述符或句柄&#xff0c;用于管理文件访问。如果文件保持打开状态&#xff0c;这些资源就不会被释放&#xff0c;可能导致资源耗尽。 确保数据完整性&#xff1a;写…...

vue3+echarts+ant design vue实现进度环形图

1、代码 <div> <!-- 目标环形图 --><div id"main" class"chart_box"> </div><div class"text_target">目标</div> </div>// 目标环形图 const onEcharts () > {// 基于准备好的dom&#xff0c;初…...

使用argo workflow 实现springboot 项目的CI、CD

文章目录 基础镜像制作基础镜像设置镜像源并安装工具git下载和安装 Maven设置环境变量设置工作目录默认命令最终dockerfile 制作ci argo workflow 模版volumeClaimTemplatestemplatesvolumes完整workflow文件 制作cd argo workflow 模版Workflow 结构Templates 定义创建 Kubern…...

C++知识点总结(58):序列型动态规划

动态规划Ⅰ 一、基础1. 意义2. 序列 dp 解法 二、例题1. 最大子段和2. 删数最大子段和&#xff08;数据强度&#xff1a;pro max&#xff09;3. 最长上升子序列&#xff08;数据强度&#xff1a;pro max&#xff09;4. 3 或 5 的倍数序列5. 数码约数序列 一、基础 1. 意义 动…...