Linux之进程间通信——管道

news2024/11/29 9:43:31

文章目录

  • 前言
  • 一、进程间通信
    • 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.coloradmin.cn/o/626336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【openframework】实时路径规划(RTRRTstar算法)

程序框架 视频演示 实时RRT-star算法介绍 实时RRT-star算法是一种基于采样的运动规划算法&#xff0c;它可以在有限的时间内找到一条渐进最优的路径。实时RRT-star算法是在RRT-star算法的基础上进行了改进&#xff0c;主要有两个方面&#xff1a; - 实时更新起始点。实时RRT-st…

那些漏洞挖掘高手都是怎么挖漏洞的?

前言 说到安全就不能不说漏洞&#xff0c;而说到漏洞就不可避免地会说到三座大山&#xff1a; 漏洞分析 漏洞利用 漏洞挖掘 从个人的感觉上来看&#xff0c;这三者尽管通常水乳交融、相互依赖&#xff0c;但难度是不尽相同的。本文就这三者分别谈谈自己的经验和想法。 漏洞分析…

Mac图片批处理工具

PhotoMill X是一款强大的图像处理软件&#xff0c;它可以帮助用户快速地对照片进行编辑、调整和转换。它支持在单个或批量模式下处理大量的图像文件&#xff0c;并具有直观的用户界面和易于使用的工具。 PhotoMill X具有广泛的编辑功能&#xff0c;包括裁剪、缩放、旋转、调整明…

记录基于Vue.js的Tree组件_Liquor Tree

酒树 (Liquor Tree) A Vue tree component that allows you to present hierarchically organized data in a nice and logical manner. Vue 树组件&#xff0c;可让您以美观和逻辑的方式呈现层次结构的数据。 supports mobile, and has a variety of response events. Flex…

软考A计划-电子商务设计师-模拟试题卷三

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

springboot+websocket客服聊天

续&#xff1a;spring boot 完整后端接口案例_cc&的博客-CSDN博客 后台代码 1.在pom.xml中添加spring-boot-starter-websocket <!--WebSocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter…

浅谈数明深力科隔离驱动器SLMi8235BD 车载充电器(OBC)方案

数明深力科SLMi823x系列SLMi8235BD隔离驱动技术优势&#xff1a;具有高性能、高性价比、高可靠性的产品特性&#xff0c;应用覆盖UPS、充电桩、服务器电源、通信电源、新能源汽车动力总成系统的车载 OBC 领域。通过CQC认证的。 车载充电器&#xff08;OBC&#xff09;是电动汽…

在线电子书创建:MkDocs + Github + ReadTheDocs

MkDocs是一个静态站点生成器&#xff0c;可用于构建项目文档。文档文件使用Markdown语法编写&#xff0c;本文记录如何使用MkDocs生成项目文档&#xff0c;并部署到Read the Docs上。 目录 安装mkdocs搭建文档项目创建项目启动项目编写文档Markdown语法站内链接图片 文档结构 配…

聚观早报 | 苹果收购AR头显初创公司Mira;抖音将尝试自营电商

今日要闻&#xff1a;苹果收购AR头显初创公司Mira&#xff1b;抖音将尝试自营电商&#xff1b;特斯拉股价实现九连涨&#xff1b;《黑镜》主创曾试用ChatGPT写剧本&#xff1b;携程集团发布2023Q1财报 苹果收购AR头显初创公司Mira 6 月 7 日消息&#xff0c;据 The Verge 报道…

2023免费电脑录视频软件Camtasia

Camtasia 2023是专业又好用的录屏软件&#xff0c;既可以用来录视频、ppt、直播课程等&#xff0c;也能够处理视频&#xff0c;添加各种效果&#xff0c;制作出更加满意的视频作品。Camtasia凭借愈发强大的功能收获了一大批忠实用户和如潮好评&#xff0c;已成为屏幕录制的必备…

6.16 @ 广州丨相约泛娱乐出海论坛,探寻市场突围的核心要义

1979 年&#xff0c;“出国办企业”第一次被有关部门提出&#xff0c;开启了中国企业对外投资的积极探索与尝试。经过 40 多年的耕耘&#xff0c;不少出海品牌已经站稳了脚跟&#xff0c;品牌影响力与日俱增&#xff0c;甚至在部分核心细分赛道上&#xff0c;中国企业凸显出了明…

TiDB 7.1 LTS 发版:为关键业务提供业务稳定性和多租户场景支持

近日&#xff0c;企业级开源分布式数据库厂商 PingCAP 正式发布 TiDB 7.1 版本&#xff0c;这也是 2023 年度发布的首个 LTS&#xff08;Long Term Support&#xff09; 版本&#xff0c;汇集了来自 20 个真实场景带来的功能增强&#xff0c;累计优化和修复 140 功能&#xff0…

easyui5

1.1准备工作. 今天我们的主要任务是完成分页功能&#xff1b; 导入数据表 t_book&#xff0c;数据文件在课件&#xff1b; 介绍拼音字段&#xff0c;拼音工具类&#xff0c;拼音jar包&#xff1b; 模糊查询还要实现拼音模糊查询&#xff0c;如&#xff1a;百度输入拼音也可以…

飞凌嵌入式AM62x系列核心板配置新增,单核/双核/四核随心选

新时代&#xff0c;工业、电力、交通、医疗等众多行业纷纷从信息化向着智能化转型&#xff0c;这就对主控的综合性能提出了全新的要求。在这样的背景下 TI AM62x Sitara™处理器应运而生&#xff0c;为AIoT时代带来了新的突破可能。作为TI官方合作伙伴&#xff0c;飞凌嵌入式于…

RabbitMQ - 发布确认高级

RabbitMQ - 发布确认高级 发布确认 springboot 版本回退消息备份交换机 在生产环境中由于一些不明原因&#xff0c;导致 RabbitMQ 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c; 导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0c;我们开始…

Steemit 会颠覆 Quora/知乎 甚至 Facebook 吗?

Steemit是基于区块链技术的社交媒体平台&#xff0c;其独特的激励机制吸引了众多用户。然而&#xff0c;是否能够真正颠覆Quora、知乎甚至Facebook这些已经成为社交巨头的平台&#xff0c;仍然存在着许多未知因素。本文将探讨Steemit的优势和挑战&#xff0c;以及其在社交领域中…

数据分析第15课pandas和matplotlib实战

01实战:911 导入: 读取数据: 转换类型:

java html导出添加空行和空格

情景&#xff1a; 要求导出签批单&#xff1a; 格式如下&#xff0c; 要获取“主办处室负责人”和“相关处室会签”环节的处理意见、处理人员和处理日期进行替换&#xff0c;导出word文档。 处理&#xff1a; 主要是如何拼接内容&#xff1f; 方法一&#xff1a; 导出word&…

爬虫如何发送 HTTP 请求

爬虫可以使用 Python 中的 requests 库来发送 HTTP 请求。requests 库提供了简单易用的 API&#xff0c;可以方便地发送 GET、POST 等请求&#xff0c;并且支持设置请求头、请求参数、代理等功能。 以下是一个使用 requests 库发送 GET 请求的示例代码&#xff1a; import re…

国产触控笔哪个牌子好?第三方电容笔推荐

对于那些把iPad当做学习工具的人来说&#xff0c;这已经成为了他们生活中不可缺少的一部分。但没有人买得起苹果原装电容笔&#xff0c;因为苹果电容笔的售价太贵了。因此&#xff0c;最好还是用一支普通的电容笔。我是个一个苹果粉&#xff0c;同时也是个数字发烧友&#xff0…