『 Linux 』 进程间通信 - 匿名管道

文章目录

    • 什么是管道
    • 匿名管道的直接原理
    • pipe( )系统调用接口
    • 匿名管道代码示例
    • 匿名管道的特征
    • 总结


什么是管道

请添加图片描述

管道(Pipe) 是一种基本的进程间通信(IPC)机制,允许一个进程与另一个进程之间进行数据传输;

管道工作方式类似于生活中的水管因此命名为管道,数据从一端流入另一段流出,数据流为单向;

Linux中可以使用who | wc -l查看当前登入系统的用户数;

who命令用与显示当前登入系统的用户信息,其中一条会话代表一个用户;

wc -l命令统计当前行数;

两条命令通过管道符|进行连接,即将显示的信息通过管道符传输给wc命令再进行统计行数;

|符号即为一种管道;

管道存在两种基本类型:

  • 匿名管道(Anonymous Pipes)

    不存在命名的管道,用于有亲缘关系的进程之间的通信(如父子进程或兄弟进程等);

    匿名管道通常用于单个系统内部的进程通信;

  • 命名管道(Named Pipes)

    也称为FIFO(First In First Out),拥有命名并存在于文件系统中;

    命名管道允许没有亲缘关系的进程之间进行通信;


匿名管道的直接原理

请添加图片描述

每个进程需要维护其task_struct结构体,对应的内核数据结构中存在一个struct file_struct*指针指向一个file_struct结构体,这个结构体中存在一个struct file* fd_array[]指针数组,数组的下标为文件描述符;

对应的struct file结构体存放打开的文件的基本信息;

这些信息包括但不限于:

  • Inode

    文件的Inode编号;

  • file_operators

    提供给上层的读写接口方法集;

  • 缓冲区

    对于普通文件而言这个缓冲区通常为 页缓冲区 ;

    缓冲区与文件系统配合实现数据的写入与读取;

匿名管道是一种区别于普通文件的内存级文件;

不存在于磁盘中不基于文件系统,操作系统不会为匿名管道文件分配Inode与对应的数据块;

对应的读写方法file_operators是针对于缓冲区的读写;

当进程创建子进程时子进程为父进程的一个拷贝;

操作系统会为子进程单独维护一个task_struct结构体以及其对应的内容包括file_struct结构体与文件描述符表;

文件系统与进程管理之间为并列关系,文件不会因为创建子进程而单独为其拷贝一份新文件;

子进程文件描述符对应的结构体指针所指向的文件与父进程相同;

进程间通信的本质是 “让不同的进程看到同一份资源” ;

在创建子进程时即可实现两个不同的进程看到同一份"资源",即管道文件的缓冲区;

在打开文件时通常会记录打开文件的方式 (读/写),在创建子进程时打开方式也会连同一起拷贝,这意味着单独以读或是写的方式打开文件不能使得两个不同进程进行通信;

在进行匿名管道通信时进程将占用两个文件描述符分别以读和写的方式打开管道文件,在创建子进程后根据需求关闭另一个文件描述符从而实现单向通信;

系统并不会在使用管道时为用户关闭某个文件描述符,该操作由用户自行决定;

为确保管道的正常使用与进程间通信,用户需要手动关闭不需要的文件描述符;

单个管道只能进行单向通信,若是使单个管道进行双向通信可能会因为读写位置不同或数据覆盖,数据碎片等问题造成通信错误;

需要利用管道进行双向通信时可采用两个管道的方式,其中每个管道负责一个方向的通信;


pipe( )系统调用接口

请添加图片描述

使用 open()等系统调用接口创建的文件是一种磁盘级文件,在文件系统中存在自身的文件名,Inode与数据块且将被文件系统管理;

管道是一种内存级文件,不存在对应的文件名与Inode,数据块分配,内存级文件不被文件系统所管理,不可使用open()等接口函数创建;

匿名管道文件的创建需要通过系统调用接口pipe()进行创建;

PIPE(2)                               Linux Programmer's Manual                               PIPE(2)

NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

       int pipe(int pipefd[2]);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.
  • 头文件

    pipe()系统调用接口存在于<unistd.h>头文件中;

  • 参数

    int pipefd[2]参数代表使用该系统调用接口时需要传递一个数组,数组中只需要包含两个int类型的元素;

    该参数是一种输出型参数;

    • 传入型参数

      这类参数用于向函数提供需要的数据或信息,函数通过这些参数读取传入的值,但不会修改它们;

    • 输出型参数

      输出参数则用于从函数内部向外部返回额外的数据,函数通过修改这些参数的值来传递数据给调用者;

    pipefd[0]pipefd[1]分别代表创建的管道文件对应的文件描述符,其中 0号下标对应的int类型数据代表读,1号下标代表写;

  • 返回值

    函数调用成功时返回0;

    函数调用失败时返回-1并设置errno;

匿名管道的使用方式一般为:

  • 父进程调用pipe()系统调用接口创建管道文件

  • 父进程调用fork()系统调用接口创建子进程(或间接创建具有亲缘关系的进程)

  • 判断数据流方向( 父流向子/子流向父 )

  • 根据需求(数据流向)关闭父子进程的另一个文件描述符

  • 进行通信

    通信一般采用write()系统调用接口与read()系统调用接口;

  • 关闭剩余文件描述符

    在通信过后可以根据条件关闭通信过的文件描述符,也可不关闭(最终管道文件将会自动被操作系统回收);


匿名管道代码示例

请添加图片描述

  • 头文件与宏定义

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #include <cstdlib>
    #include <cstring>
    #include <iostream>
    #include <string>
    
    #define N 2
    
    #define NUM 1024
    using namespace std;
    
    • Npipe()系统调用接口参数的数组大小
    • NUM为用户层缓冲区的大小
  • main函数

    int main() {
      int pipefd[N] = {0};
      int n = pipe(pipefd);
      if (n < 0) {
        cerr << "pipe error" << endl;
        exit(-1);
      }
    
      pid_t id = fork();
      if (id < 0) {
        cerr << "fork error" << endl;
        exit(-1);
      }
      /*
        child -> parent
        pipefd[0] -- 'r'
        pipefd[1] -- 'w'
      */
    
      if (id == 0) {
        // child - `w`
        close(pipefd[0]);
        // IPC code
        PIC::Write(pipefd[1]);
        exit(0);
      }
    
      // parent - `r`
      close(pipefd[1]);
      // IPC code
      PIC::Read(pipefd[0]);
      pid_t rid = waitpid(id, nullptr, 0);
      if (rid < 0) return 3;
    
      return 0;
    }
    

    该示例为子进程向父进程进行单向通信;

    设置int类型数组pipefd[N]并调用pipe()系统调用接口创建匿名管道文件;

    利用fork()系统调用接口创建子进程并根据需求调用close()系统调用接口关闭父子进程中不需要的文件描述符;

    父进程调用waitpid()系统调用接口进行进程等待;

    Read()Write()接口用来完成具体的通信过程;

    子进程调用Write(),父进程调用Read()完成通信;

  • Read()Write()实现

    namespace PIC {
    
    void Write(int wfd) {
      char buff[NUM];
      pid_t self = getpid();
      string s = "I am a child";
      int number = 0;
      while (true) {
        buff[0] = 0;  // 字符串清空 (为读者展示该数组将被视为一个字符串)
    
        snprintf(buff, sizeof(buff), "%s-%d-%d", s.c_str(), self, number++);
        // cout << buff << endl;
    
        // 向父进程进行通信
        write(wfd, buff, strlen(buff));  // 进行写入时数据不作为一个字符串
        sleep(1);
      }
    }
    void Read(int rfd) {
      char buff[NUM];
      while (true) {
        buff[0] = 0;  // 字符串清空 (为读者展示该数组将被视为一个字符串)
        ssize_t n = read(rfd, buff, sizeof(buff));
        if (n > 0) {
          buff[n] = 0;  // 需要打印时将需要称为一个字符串 需要添加'\0'
          cout << "parent process get a massage [" << getpid() << "]# " << buff
               << endl;
        }
          //忽略read()调用失败
      }
    }
    
    }  // namespace PIC
    

    创建命名空间PIC避免出现命名冲突;

    • Write()

      创建用户层缓冲区buff[NUM]用于存储子进程需要向父进程写入的内容;

      调用snprintf()C标准接口用于将字符串格式化后写入用户层缓冲区buff[](snprintf()具体调用查看手册);

      调用write()系统调用接口将用户层缓冲区内容buff[]写入至内核缓冲区(匿名管道文件的缓冲区,写入过程中文件数据不当做字符串看待);

      调用sleep()使每向内核缓冲区写入一条数据后休眠1s;

    • Read()

      创建用户层缓冲区buff[NUM]用于存储接收的由子进程写给父进程的内容;

      调用read()系统调用接口从内核缓冲区(匿名管道文件的缓冲区)读取内容并写入至用户层缓冲区buff[]中并用n接收返回值;

      子进程在向父进程写入时将数据以字符串形式看待,此处需要打印需要将数据以字符串看待需要buff[n]处添加字符串结束符\0;

      利用std::cout打印接收的内容;

缓冲区为内核的空间,用户层必须通过系统调用接口才能间接对内核缓冲区进行操作;


匿名管道的特征

请添加图片描述

  • 匿名管道只能为具有血缘关系的进程间进行通信

    不具有血缘关系的进程无法利用匿名管道进行通信;

  • 管道只能单向通信

    单个管道进行双向通信将会因为读写位置不同或数据覆盖,数据碎片等问题造成通信错误;

  • 父子进程通信时会进行协同

    在上段代码中子进程向父进程写入数据时利用sleep()每隔1s进行一次写入;

    父进程在读取子进程数据时不进行休眠;

    运行上段代码并在另一个会话窗口利用脚本观察父子进程情况;

    $ while :; 
    do ps axj | head -1 && ps axj | grep mytest | grep -v grep ; 
    echo"#####################" ; 
    sleep 1 ;
    done
    

    脚本结果如下:

    #####################
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    29927 30127 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    30127 30128 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    #####################
     PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    29927 30127 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    30127 30128 30127 29927 pts/2    30127 S+    1002   0:00 ./mytest
    

    观察结果父子进程都进行了休眠;

    当子进程向父进程传输数据时父进程会直接接收;

    若是子进程未向父进程传输数据时为了避免读取"脏数据"(即错误或无效数据),父进程将会等待子进程进行下一次的通信;

    进程间通信的本质是 “使不同进程看到同一个资源” ,这意味着这份资源将被多个执行流共享;

    因此可能会出现 访问冲突临界资源竞争 等问题;

    父子进程间协同的方式一般采用 同步与互斥 ,主要保护管道文件资源的数据安全;

  • 匿名管道通信时的四种情况

    • 管道为空时读端将进行阻塞

      参考上文 父子进程通信时会进行协同 ;

    • 管道为满时写端将进行阻塞

      在原代码基础上取消子进程的sleep()并在父进程开头处sleep(5),结尾处sleep(100)进行等待(只进行一次读取,sleep(100)为与一次读取进行分割避免第二次read());

      在两个会话中分别运行程序与监控脚本;

      结果如下:

      • 监控脚本

        $ while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep ; echo "#####################" ; sleep 1 ;done
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        #####################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        29927 32142 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        32142 32143 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        #####################
         PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
        29927 32142 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        32142 32143 32142 29927 pts/2    32142 S+    1002   0:00 ./mytest
        
        #...
        
        
      • 程序结果

        $ ./mytest 
        parent process get a massage [32142] # I am a child-32143-0I am a child-32143-1I am a child-32143-2I am a child-32143-3I am a child-32143-4I am a child-32143-5I am a child-32143-6I am a child-32143-7I am a child-32143-8I am a child-32143-9I am a child-32143-10I am a child-32143-11I am a child-32143-12I am a child-32143-13I am a child-32143-14I am a child-32143-15I am a child-32143-16I am a child-32143-17I am a child-32143-18I am a child-32143-19I am a child-32143-20I am a child-32143-21I am a child-32143-22I am a child-32143-23I am a child-32143-24I am a child-32143-25I am a child-32143-26I am a child-32143-27I am a child-32143-28I am a child-32143-29I am a child-32143-30I am a child-32143-31I am a child-32143-32I am a child-32143-33I am a child-32143-34I am a child-32143-35I am a child-32143-36I am a child-32143-37I am a child-32143-38I am a child-32143-39I am a child-32143-40I am a child-32143-41I am a child-32143-42I am a child-32143-43I am a child-32143-44I am a child-32143-45I am a child-32143-46I am a child-32143-47I am a child-32143-48I am 
        

      结果来看在运行程序时父进程因sleep(5)并未接收到子进程传输的数据;

      当休眠结束时将管道内的所有数据进行一次性读取;

      从读取的数据来看最终写入的数据停留在了I am a child-32143-48I am ;

      这意味着管道此时已经被写满了;

      当管道满了时写端将进行阻塞,等待读端读取数据后才能进行下一次写入;

    • 读端正常,写端关闭

      利用man查看read()系统调用接口返回值;

      RETURN VALUE
             On  success, the number of bytes read is returned (zero indicates end
             of file), and the file position is advanced by this  number.  
             ...
      

      读端正常写端关闭时当管道中的数据被读端读完后读端将会读取到0表示已经读到文件(管道Pipe)结尾且不会被阻塞;

      故为防止该种情况需要在父进程中进行特殊处理(上述原文件并未对该情况进行处理);

    • 写端正常,读端关闭

      当一个进程尝试向管道的写端写入数据,而管道的读端已经被所有相关进程关闭时,该进程会收到SIGPIPE信号;

      默认情况下SIGPIPE信号会终止该进程;

      这是因为如果没有任何进程能够从管道读取数据,继续写入数据就没有意义;

      操作系统通过发送SIGPIPE信号来通知这一点;

  • 匿名管道具有固定大小

    利用ulimit命令带-a选项查看操作系统的限制;

    $ ulimit -a
    core file size          (blocks, -c) 0
    data seg size           (kbytes, -d) unlimited
    scheduling priority             (-e) 0
    file size               (blocks, -f) unlimited
    pending signals                 (-i) 7269
    max locked memory       (kbytes, -l) 64
    max memory size         (kbytes, -m) unlimited
    open files                      (-n) 65535
    pipe size            (512 bytes, -p) 8
    POSIX message queues     (bytes, -q) 819200
    real-time priority              (-r) 0
    stack size              (kbytes, -s) 8192
    cpu time               (seconds, -t) unlimited
    max user processes              (-u) 4096
    virtual memory          (kbytes, -v) unlimited
    file locks                      (-x) unlimited
    

    其中pipe size大小为512bytes * 84kb;

    修改原代码中Write()为:

    void Write(int wfd) {
      int number = 0;
      while (true) {
        char c = 'c';
        write(wfd, &c, 1);  
        number++;
        cout << number << endl;
      }
    }
    
    //读端对应进行阻塞(该测试中用不到读端)
    

    即每次向管道文件内核缓冲区中写入一个字节;

    重新编译运行代码结果为:

    ...
    65534
    65535
    65536
    ^C #进行阻塞时 Ctrl+C 停止继续执行
    

    最终结果为65536字节,约为64kbulimit中结果不符;

    • 原因是匿名管道的缓冲区大小是固定的,但这个大小由操作系统决定,不同的操作系统和配置可能会有不同的管道缓冲区大小;
  • 管道是面向字节流的

    参考上文 “管道的四种情况 > 管道为满时写端将进行阻塞” ,写端向读端进行数据传输时只按照字节进行写入;

    最终的格式将取决于读端采用什么方式对写端的内容进行接收;

    写端可以连续写入任意数量的字节,而读端可以根据需要读取任意数量的字节;

    故写端不需要考虑读端采用何种方式进行接收,读端不需要考虑写端采用何种方式进行写入;

  • 匿名管道是基于文件的

    匿名管道不属于文件系统(不存在文件名,Inode与对应的数据块),但匿名管道是基于文件的,其必须通过系统调用接口与文件描述符的配合才能进行使用;

    同时匿名管道的生命周期是由进程决定的(引用计数);


总结

请添加图片描述

  • 管道是一种基于文件描述符的进程间通信(IPC)机制,允许单向数据传输;

  • 有两种基本类型的管道

    匿名管道用于有亲缘关系的进程间通信;

    命名管道(FIFO) 允许没有亲缘关系的进程间通信;

  • 管道存在固定大小的缓冲区,由操作系统决定,面向字节流,支持连续字节的读写操作;

  • 匿名管道的生命周期与创建它的进程相关,不属于文件系统,但通过文件描述符进行操作;

  • 进程间通信通过管道需要正确管理文件描述符,如关闭不需要的端以避免阻塞和SIGPIPE信号;

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

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

相关文章

技术分享 | 基于 API 解析的 Python 爬虫

最近各大高校纷纷翻拍 Coincidence 抖肩舞&#xff0c;需要对这种流行现象进行数据分析。数据分析首先需要有数据&#xff0c;本文介绍了爬取 B 站相应视频的评论、弹幕、播放量、点赞数等数据的方法。爬虫有多种实现方法&#xff0c;大型的网络爬虫多基于成熟的爬虫框架&#…

2-12 基于CV模型卡尔曼滤波、CT模型卡尔曼滤波、IMM模型滤波的目标跟踪

基于CV模型卡尔曼滤波、CT模型卡尔曼滤波、IMM模型滤波的目标跟踪。输出跟踪轨迹及其误差。程序已调通&#xff0c;可直接运行。 2-12 CV模型卡尔曼滤波 CT模型卡尔曼滤波 - 小红书 (xiaohongshu.com)

基于jeecgboot-vue3的Flowable流程-自定义业务表单处理(一)支持同一个业务多个关联流程的选择支持

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 这部分先讲讲支持自定义业务表单一个业务服务表单多个流程的支持处理 1、后端mapper部分 如下&#xff0c;修改selectSysCustomFormByServiceName为list对象&#xff0c;以便支持多个 &…

卫星导航与gazebo仿真

全球卫星导航系统(Global Navigation Satelite System,GNSS)&#xff0c;简称卫星导航&#xff0c;是室外机器人定位的一个主要信息来源。 卫星导航能给机器人提供什么信息&#xff1f; 正常工作时&#xff0c;实际上可以提供机器人所需的所有定位信息&#xff0c;包括&#x…

【例子】webpack配合babel实现 es6 语法转 es5 案例 [通俗易懂]

首先来说一下实现 es6 转 es5 的一个简单步骤 1、新建一个项目&#xff0c;并且在命令行中初始化项目 npm init -y2、安装对应版本的 webpack webpack-cli(命令行工具) "webpack""webpack-cli"3、安装 Babel 核心库和相关的 loader "babel-core&qu…

K8s 如何集成ChatGPT?

文章目录 1. 什么是K8s&#xff1f;2. 集成K8s和大模型的效果3. ChatGPT监测K8s集群Demo4.可预想的实践用例5. 结论 1. 什么是K8s&#xff1f; 熟悉云原生领域的朋友对 K8s 一定不会陌生。K8s&#xff08;Kubernetes&#xff09;是一个开源的容器编排平台&#xff0c;用于自动…

《华为项目管理之道》第1章笔记

《华为项目管理之道》&#xff0c;是新出的华为官方的项目管理书&#xff0c;整个书不错。第1章的精华&#xff1a; 1.2.2 以项目为中心的机制 伴随着项目型组织的建立&#xff0c;华为逐步形成了完备的项目管理流程和制度&#xff0c;从而将业务运 作构建在项目经营管理之…

生成模型的两大代表:VAE和GAN

生成模型 给定数据集&#xff0c;希望生成模型产生与训练集同分布的新样本。对于训练数据服从\(p_{data}(x)\)&#xff1b;对于产生样本服从\(p_{model}(x)\)。希望学到一个模型\(p_{model}(x)\)与\(p_{data}(x)\)尽可能接近。 这也是无监督学习中的一个核心问题——密度估计…

STM32——温湿度采集与显示

一、I2C协议 关于I2C协议的基本原理和时序协议 12C协议使用两条线&#xff1a;SDA&#xff08;Serial Data Line&#xff0c;串行数据线&#xff09;和SCL&#xff08;Serial Clock Line&#xff0c;串行时钟线&#xff09;。这两条线都是开漏输出&#xff0c;意味着它们需要上…

智能网联汽车实训教学“好帮手”——渡众机器人自动驾驶履带车

智能网联汽车实训教学“好帮手”——渡众机器人自动驾驶履带车 人工智能技术的兴起&#xff0c;为传统汽车行业注入了强有力的变革基因&#xff0c;以AI技术为驱动的无人驾驶成为汽车产业的未来&#xff0c;同样也面临诸多机遇和挑战。 一方面智能网联汽车的发展&#xff0c;为…

基于TensorFlow和OpenCV的物种识别与个体相似度分析【附完整源码】Flask网页版

一、OpenCV与TensorFlow介绍 1. 什么是OpenCV OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。OpenCV由英特尔公司在1999年发起&#xff0c;并在2000年以开源的方式发布。该库被设计为高效的计算机视觉应用程序…

OpenHarmony-HDF驱动框架介绍及加载过程分析

前言 HarmonyOS面向万物互联时代&#xff0c;而万物互联涉及到了大量的硬件设备&#xff0c;这些硬件的离散度很高&#xff0c;它们的性能差异与配置差异都很大&#xff0c;所以这要求使用一个更灵活、功能更强大、能耗更低的驱动框架。OpenHarmony系统HDF驱动框架采用C语言面…

UE5近战对抗系统Tutorial

文章目录 BP_Character 组合攻击Notify State 检测攻击BP_Character 攻击反馈BP_Character 生命系统BP_Character 死亡效果BP_Character 武器系统BP_Enemy 初始化和行为树 BP_Character 组合攻击 首先我们获取攻击动画&#xff0c;在这里使用的是 Easy Combo Buffering 的攻击…

Nature推荐:快速完成一篇论文,你只需要这 12 个技巧!

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 当你面对空白屏幕&#xff0c;苦思冥想数小时、数日甚至数月后&#xff0c;仍然没办法搞定论文。你该怎么办&#xff1f; 写作障碍虽然普遍存在&#xff0c;但对需要发表论文…

PyMuPDF 操作手册 - 05 PDF的OCR识别等

文章目录 六、PyMuPDF的OCR识别6.1 使用 Tesseract进行OCR6.2 使用MuPDF进行OCR6.3 使用 Python 包easyocr进行OCR识别6.4 使用 Python ocrmypdf包进行OCR识别6.5 将图像批量OCR并转换为PDF七、PDF附加、嵌入、批注等7.1 附加文件7.2 嵌入文件7.3 从文档中获取所有批注六、PyMu…

如何恢复未格式化分区数据?看这里!

什么是未格式化分区&#xff1f; 未格式化或RAW文件系统的分区无法被Windows操作系统识别和挂载&#xff0c;因此&#xff0c;Windows会提示你进行格式化以创建新的文件系统。注意&#xff0c;不要进行格式化。通常&#xff0c;文件系统变为未格式化或RAW会出现以下常见错误消…

又发现一款独立清理神器,界面清爽,功能强大,没有广告!

360清理Pro独立提取版是360公司推出的一款清理软件&#xff0c;主要用于清理系统垃圾和优化系统性能&#xff0c;涵盖了四大类型的清理场景&#xff0c;分别为&#xff1a;微信、QQ的垃圾扫描及清理&#xff0c;系统盘中的大文件、重复文件扫描及清理以及系统软件使用痕迹的清理…

Docker 可用镜像源

当使用 docker 发现拉取不到镜像时&#xff0c;可以编辑 /etc/docker/daemon.json 文件&#xff0c;添加如下内容&#xff1a; 这文章不涉及政治&#xff0c;不涉及敏感信息&#xff0c;三番五次的审核不通过&#xff0c;一删再删&#xff0c;只好换图片了。 重新加载服务配置…

视频监控平台功能介绍:内部设备管理(rtsp、sdk、onvif、ehome/ISUP、主动注册协议等)

一、功能概述 AS-V1000视频平台是一套集成了用户设备权限管理、视音频监控、大容量存储、电子地图的系统平台软件。它结合了现代视频技术、网络通讯技术、计算机控制技术、流媒体传输技术的综合解决方案&#xff0c;为用户提供了强大的、灵活的组网和应用能力。 AS-V1000管理端…

HarmonyOS 开发知识:一个基于 emitter 封装了一个便捷的 EventBus 事件通知

引言 鸿蒙提供提供了在同一进程不同线程间&#xff0c;或同一进程同一线程内&#xff0c;发送和处理事件的能力&#xff0c;包括持续订阅事件、单次订阅事件、取消订阅事件&#xff0c;以及发送事件到事件队列的能力。 ohos.events.emitter Emitter 封装前使用&#xff1a;e…