进程间通信(一)管道

文章目录

  • 进程间通信
    • 进程间通信概述
    • 进程间通信的方式
      • 管道通信
        • 示例--基于管道的父子进程通信
        • 示例--使用管道进程兄弟进程通信
      • 管道的读写特性
        • 示例--不完整管道(读一个写端关闭的管道)
        • 示例--不完整管道(写一个读端关闭的管道)
      • 标准库中的管道操作
        • 示例--使用popen函数进行管道的读写
      • 命名管道(FIFO)的创建
        • 示例--使用FIFO进行进程间通信
      • 匿名管道和命名管道的异同

进程间通信

进程间通信概述

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间(例如一个进程要将某一个计算结果发送给另外一个进程使用,这时候就会用到进程间通信)。
  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立即看到(例如就像线程中的全局变量一样,一个线程修改别的线程立马就能看到)。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件(例如子进程在结束时会产生一个SIGCHILD信号通知父进程去回收子进程的资源)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供锁和同步机制(例如多个进程在操作同一个文件的时候就会涉及到资源共享的问题,需要靠文件锁等机制来实现进程之间的同步和互斥).
  • 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变(例如常用的gdb调试)

进程间通信的方式

  1. 匿名管道(pipe)和命名管道(FIFO)
  2. 信号(signal)
  3. 消息队列
  4. 共享内存
  5. 信号量(进程的信号量和线程信号量不一样)
  6. 套接字(socket)

管道通信

  • 管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际获得两个文件描述符,一个用于读取一个用于写入。后续对管道的操作都可以像对普通文件一样使用readwrite函数对管道进行读取和写入。
  • 最常见的IPC机制,通过pipe系统调用
  • 管道是单工的,也就是说数据只能向一个方向流动,需要双工通信时,需要建立起两个管道。
  • 数据的读出和写入:一个进程向管道中写的内容被另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据(管道实际上就是位于内核中的一个缓冲区,当数据被从缓冲区读走以后,数据会从缓冲区移除,即管道中的数据读走即无)。

管道分类

  • 匿名管道

    • 通过系统调用pipe()函数创建管道
    • 在有血缘关系的进程中进行(例如父进程和子进程、兄弟进程之间
    • 管道位于内核空间,实质是一块缓存
  • 命名管道

    • 两个没有任何关系的进程之间通信可通过命名管道进行数据传输,本质是内核中的一块缓存,另外文件系统中以一个特殊的设备文件(管道文件,前边将文件系统的时候有讲过)存在。只要对管道文件进行读写就能够同步到内核空间的那片缓存中去。

    • 通过指令mkfifo或者系统调用mkfifo()函数创建

      image-20241011083824639

    匿名管道的创建

    #include <unistd.h>int pipe(int fd[2]);/*功能:创建一个管道,用于进程间的通信参数:一个数组,用于存储读端文件描述符和写端文件描述符返回值:成功执行返回0,失败返回-1fd[0]用于从管道中读取数据,fd[1]用于向管道中写入数据
    */
    

    image-20241011084806491

    管道的读写

    匿名管道主要用于有血缘关系的进程,尤其是父子进程之间的关系。父进程调用pipe()函数创建一个管道,然后调用fork()函数创建一个子进程。

    这里有两点需要注意:

    • 通过之前的章节可知当通过fork()函数创建子进程的时候,子进程会继承父进程的代码段、数据段、堆、栈等内容,所以当在父进程中定义了一个数组用于存储读端和写端的文件描述符的时候,由于定义的数组位于栈区,所以这个数组也会被子进程继承。而父子进程中的数组是相同的内容,它们指向了管道的读端和写端,所以父子进程可以通过管道进行通信。
    • 根据管道的特性可知,管道是单工的。所以即使这里父子进程都有读端和写端,数据也只能从一个方向进行传输。也就是说同一时间只能有父进程向管道中写入子进程从管道中读取或者子进程向管道中写入父进程从管道中读取,所以通过fork()函数创建子进程后父子进程必须关闭一端然后参能进行通信。如果想要同一时间进行双方通信,必须要建立两个管道。
示例–基于管道的父子进程通信
#include "header.h"int main(void)
{int pipe_fd[2];pid_t pid;//父进程通过pipe函数创建管道用于父子进程间通信if(pipe(pipe_fd) != 0){perror("pipe error");exit(EXIT_FAILURE);}if((pid = fork()) < 0){perror("fork error");exit(EXIT_FAILURE);}//父进程向管道中写入,关闭读端else if(pid > 0){int start = 1, end = 100;close(pipe_fd[0]);			//父进程关闭读端if(write(pipe_fd[1], &start, sizeof(int)) != sizeof(int)){perror("write error");exit(EXIT_FAILURE);}if(write(pipe_fd[1], &end, sizeof(int)) != sizeof(int)){perror("write error");exit(EXIT_FAILURE);}close(pipe_fd[1]);		//写入完成后关闭写端wait(NULL);			//等待子进程退出并回收它的资源}//子进程从管道中读取,关闭写端//父进程从尾部写入,子进程从头部读取else{int start, end;close(pipe_fd[1]);if(read(pipe_fd[0], &start, sizeof(int)) < 0){perror("read error");exit(EXIT_FAILURE);}if(read(pipe_fd[0], &end, sizeof(int)) < 0){perror("read error");exit(EXIT_FAILURE);}close(pipe_fd[0]);			//读取完成后关闭读端printf("child process read data start:%d end:%d\n",start,end);}return 0;
}

image-20241011092657080

示例–使用管道进程兄弟进程通信

在Linux系统中经常会用到grep这个指令,grep指令的作用是在文件中搜索文本或者字符串,并打印出匹配的行。它经常会配合别的指令一起使用,例如:cat /etc/passwd | grep root,这个指令的作用是查看passwd这个文件里边有关root的文本。catgrep是两个指令,前边有讲过在shell上执行的指令都属于这个shell的子进程,所以它其实相当于执行了两个进程,进程1使用cat指令来将passwd这个文件里边的内容全部输出出来,进程2使用grep指令根据这些内容过滤出有关root字样的文本。在中间有一个字符|表示的就是一个管道,因为cat指令默认是输出到标准输出(屏幕),而grep指令默认是从标准输入(键盘)中获取,所以这里加一个管道|,将cat指令执行的结果写入到管道里,然后grep指令从管道中读取并将与root字样有关的文本打印出来

#include "header.h"int main(void)
{int pipe_fd[2];int i = 0;pid_t pid;char *cmd1[] = {"cat", "/etc/passwd", NULL};char *cmd2[] = {"grep", "root", NULL};//父进程创建管道用于在兄弟进程中通信if(pipe(pipe_fd) < 0){perror("pipe error");	exit(EXIT_FAILURE);}	//父进程创建子进程for(; i < 2; i++){if((pid = fork()) < 0){perror("fork error");exit(EXIT_FAILURE);}else if(pid == 0){//子进程1用于获取cat指令执行的结果并写到管道中去if(i == 0){close(pipe_fd[0]);			//子进程1用于向管道中写入,关闭读端//将标准输出重定向到管道的写入端,将cat的内容写入到管道中去//重定向后标准输出就指向了管道的写入端if(dup2(pipe_fd[1], STDOUT_FILENO) != STDOUT_FILENO){perror("dup2 error");exit(EXIT_FAILURE);}close(pipe_fd[1]);			//此时标准输出的指向和之歌相同,所以关闭原来的文件描述符if(execvp(cmd1[0], cmd1) == -1){perror("execvp error");exit(EXIT_FAILURE);}				}//子进程2用于从管道中获取并过滤相应的关键字	if(i == 1){close(pipe_fd[1]);			//子进程2用于从管道中读取,关闭写端//grep指令默认会从标准输入中读取,所以要将标准输入重定向到管道的读端//然后利用grep过滤出来if(dup2(pipe_fd[0], STDIN_FILENO) != STDIN_FILENO){perror("dup2 error");exit(EXIT_FAILURE);}close(pipe_fd[0]);			//经过重定向后标准输入和管道的读端文件描述符指向相同,所以关闭原来的文件描述符if(execvp(cmd2[0], cmd2) == -1){perror("execvp error");exit(EXIT_FAILURE);}}break;}else {if(i == 1){//父进程只做创建管道和创建子进程的事情,不对管道进行操作//所以这里关闭父进程中的文件描述符close(pipe_fd[0]);close(pipe_fd[1]);//等待两个子进程退出并回收它的资源wait(NULL);wait(NULL);}}}	return 0;
}
cat /etc/passwd | grep root

image-20241012103740950

通过编译执行可以看到两个执行结果是相同的,在shell终端执行的命令|就等同于pipe()系统调用,通过管道配合grep指令可以过滤出用户所需要的文本。

代码中有几点需要注意:

  1. cat指令它默认是输出到标准输出里的,而grep指令它默认是从标准输入中去获取的,所以这里要使用dup2函数将标准输出重定向到管道的写端,将标准输入重定向到管道的读端。通过这个操作两个兄弟进程就能够通过管道进行通信。
  2. 在这个代码中,父进程的作用是建立管道和创建子进程,所以它并没有对管道进行操作。在父进程中就要把管道的读端和写端关闭,但是一定要等待创建子进程全部创建完成后才能关闭文件描述符,若子进程还没有创建完成就关闭文件描述符,那么后来的子进程它的文件描述符就是无效的。

管道的读写特性

  • 通过打开的两个管道来创建一个双向的管道(管道是单工通信的,若想要实现双方之间的通信,就要建立两个管道实现双方之间的收发)

  • 管道是阻塞性的,当进程从管道中读取数据,若没有数据进程会阻塞(和之前的普通文件不一样,若普通文件中没有数据,读取的时候会直接返回。若是一个进程读取一个没有数据的管道时它会阻塞知道管道中有数据写入它才会退出阻塞状态)

  • 当一个进程往管道中不断地写入数据但是没有进程去读取数据,此时只要管道没有满是可以的,但如果管道中放满数据则会报错。

  • 不完整管道

    • 完整性管道指的就是写端和读端都打开的管道(上边说的特性都是针对完整性管道的),而不完整管道就是读端或者写端中的任意一端被关闭的管道。
    • 当读一个写端已经被关闭的管道时,在所有数据被读取后,read返回0,以表示到达了文件尾部。
    • 如果写一个读端已经被关闭的管道,则产生信号SIGPIPE,如果忽略或者捕捉该信号并从处理程序中返回,则write返回-1,同时errno设置为EPIPE
    • 不完整管道的应用场景:后边的网络编程它是两个进程(客户端和服务器端)基于网络的通信,通信的方式类似于管道。当其中的某一方出现问题导致网络通信出现异常的时候,就可以像判断不完整管道的方式来判断网络通信是否出现了问题,然后基于此问题做相应的操作
示例–不完整管道(读一个写端关闭的管道)
#include "header.h"/**创建不完整管道:读一个写端关闭的管道*/int main(void)
{int pipe_fd[2];pid_t pid;if(pipe(pipe_fd) < 0){perror("pipe error");exit(EXIT_FAILURE);}if((pid = fork()) < 0){perror("fork error");exit(EXIT_FAILURE);}else if(pid > 0)		//父进程等待子进程写入,然后从管道中读取{sleep(2);			close(pipe_fd[1]);			//关闭管道的写端char c;while(1){if(read(pipe_fd[0], &c, 1) != 0){printf("%c",c);}else{printf("\nread the end of pipe\n");break;}}wait(NULL);}else			//子进程关闭读端,先向管道中写入数据后关闭写端 {close(pipe_fd[0]);char *s = "1234";if(write(pipe_fd[1], s, strlen(s)) != strlen(s))		//向管道中写入数据{perror("write error");exit(EXIT_FAILURE);}close(pipe_fd[1]);		//关闭管道的写端}return 0;}

image-20241014112337558

通过编译执行可以发现读一个写端已经被关闭的管道,当读到管道的末尾会返回0

示例–不完整管道(写一个读端关闭的管道)
#include "header.h"/**	创建不完整管道:写一个读端关闭的管道*/void sig_handler(int signum)
{if(signum == 13){printf("receive a signal is SIGPIPE\n");}
}int main(void)
{int pipe_fd[2];pid_t pid;//向内核注册信号和信号处理函数,如果发生信号就去执行相应的处理函数if(signal(SIGPIPE, sig_handler) == SIG_ERR){perror("signal error");}//父进程创建管道if(pipe(pipe_fd) < 0){perror("pipe error");exit(EXIT_FAILURE);}if((pid = fork()) < 0){perror("fork error");exit(EXIT_FAILURE);}else if(pid > 0)		//父进程等待子进程关闭读端然后再向管道中写入{sleep(2);			//睡眠2秒,保证子进程已经将读端关闭close(pipe_fd[0]);		//父进程关闭读端char *s = "1234";if(write(pipe_fd[1], s, strlen(s)) != strlen(s)){fprintf(stderr,"%s:%s\n",strerror(errno),(errno == EPIPE)?"EPIPE":"unknown");		//当向已经关闭读端的管道中写入的时候会产生SIGPIPE信号,同时errno被设置为EPIPE}close(pipe_fd[1]);		//写完后关闭写端wait(NULL);						}else					//子进程将读端和写端的管道都关闭{close(pipe_fd[0]);close(pipe_fd[1]);}return 0;
}

image-20241014112538968

通过编译执行可以看到当写一个读端被关闭的管道时会产生一个SIGPIPE信号,同时errno会被置为EPIPE

标准库中的管道操作

通过上边的案例可以看出基于匿名管道的进程间通信较为复杂,在标准库中有一个将管道的操作封装成一个函数popen()

标准库中的管道操作

#include <stdio.h>FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);/*功能:popen函数创建一个管道以启动新进程并与其进行通信参数:command		要执行的命令字符串,该字符串包含一个shell命令,	以NULL结尾mode		指向一个以NULL结尾的字符串的指针,该字符串必须是r(只读)或w(只写)之一stream		文件流指针返回值:popen()如果执行成功,popen返回一个文件流指针,如果失败返回
NULLpclose 如果执行成功返回传递给popen()的参数cmd指定的子进程的终止状态,如果失败,则pclose返回-1,并设置errno来指示错误
*/

popen函数的内部实现流程

popen函数的内部实现原理.drawio

示例–使用popen函数进行管道的读写
#include "header.h"int main(void)
{FILE *fp;	char buffer[128];//命令执行的结果放置在fp指向的结构体指针中//内部实现流程:子进程去执行popen传入的cmd,然后将执行的结果重定向到管道的写端//父进程然后使用标准库函数从管道中读取,然后将数据存放到fp所指向的缓存中去fp = popen("cat /etc/passwd | grep root", "r");if(fp == NULL){perror("fopen error");exit(EXIT_FAILURE);}memset(buffer, '\0', sizeof(buffer));		while(fgets(buffer, sizeof(buffer), fp)){printf("%s",buffer);}pclose(fp);printf("--------------------------------------\n");//父进程将内容写入到文件流指针所指向的缓存区域,然后popen函数会将内容写入到管道中,子进程会做一个将标准输入重定向到管道的读端的操作然后从管道中获取数据,最后根据cmd指令指向x应的fp = popen("wc -l", "w");fprintf(fp, "%s", "hello\nhaha\nlala\n");pclose(fp);return 0;
}

image-20241014202353042

通过编译执行可以发现这个函数的执行类似于system函数,但是system函数它的运行是直接输出到标准输出去的,并没有输入到管道。但是如果需要将指令的执行结果存放到某个地方的话,使用popen函数比较适合。

命名管道(FIFO)的创建

#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);/*功能:创建命名管道用于进程间的通信参数:pathname		要创建管道的路径mode				创建管道的权限返回值:若成功执行返回0,出错返回-1
*/
  • 命令mkfifo创建命名管道(命令内部调用mkfifo函数)
  • 命名管道和 匿名管道一样,实质上是内核中的一块缓存,但是命名管道会在文件系统中存在一种特殊的设备文件(管道文件)。用户进程对管道文件进行读写操作会直接同步到内核中的缓存。
  • 只要对FIFO有适当的权限,FIFO可用在任何两个没有血缘关系的进程之间通信,和匿名管道不一样,它通过系统调用pipe()函数创建只能用于有血缘关系的进程(父子进程和兄弟进程)。
  • 管道文件系统中只有一个索引块存放文件的路径,没有数据块,所有的数据存放在内核中。和之前的普通文件系统不一样,之前的文件有一个索引和数据块,数据就存放在数据块中。
  • 命名管道必须读和写同时打开,否则单独读或者单独写会引发阻塞
  • FIFO的操作与操作普通文件一样,使用mkfifo创建了一个FIFO,就可以用open打开它,一般的文件I/O函数(readwriteclose…)等都可用于FIFO
  • FIFO相关出错信息
    • EACCESS(无存取权限)
    • EEXIST(指定文件不存在)
    • ENAMETOOLONG(路径名太长)
    • ENOENT(包含的目录不存在)
    • ENOSPC(文件系统剩余空间不足)
    • ENOTDIR(文件路径无效)
    • EROFS(指定的文件只存在于只读文件系统中)
示例–使用FIFO进行进程间通信
//fifo_r.c#include "header.h"int main(int argc, char **argv)
{if(argc < 2){fprintf(stderr,"usage:%s pathname\n",argv[0]);exit(EXIT_FAILURE);}//创建管道,管道的权限为文件的拥有者和同组人有可读可写可执行的权限,其他人只有可读权限if(mkfifo(argv[1], S_IRWXU | S_IRWXG | S_IROTH) < 0){perror("mkfifo error");exit(EXIT_FAILURE);}int fd;char buffer[32];fd = open(argv[1], O_RDONLY);	//以只读的权限打开管道if(fd < 0){perror("open file error");exit(EXIT_FAILURE);}printf("open pipe read....\n");memset(buffer, '\0', sizeof(buffer));if(read(fd, buffer, sizeof(buffer)) < 0){perror("read error");exit(EXIT_FAILURE);}printf("%s\n",buffer);close(fd);				//操作完文件后将文件描述符关闭return 0;
}
//fifo_w.c#include "header.h"int main(int argc, char **argv)
{if(argc < 2){fprintf(stderr,"usage:%s pathname\n",argv[0]);exit(EXIT_FAILURE);}int fd = open(argv[1], O_WRONLY);char *str = "hello world";if(fd < 0){perror("open file error");exit(EXIT_FAILURE);}printf("open file write....\n");if(write(fd, str, strlen(str)) != strlen(str)){perror("write error");exit(EXIT_FAILURE);}close(fd);return 0;
}

image-20241015234610708

通过编译执行可以发现对于命名管道的操作实际上和操作普通文件无异,对于管道的来说它是阻塞性的,所以当管道里没有数据的时候读端会阻塞。而对于命名管道来说,当只有读端或者写端执行的时候它会阻塞,必须读端和写端都运行才能够从管道里获取数据。

匿名管道和命名管道的异同

  • 相同点

    • 作用相同:都用于不同进程间的通信

    • 数据流向:匿名管道创建两个管道后允许数据双向传输,命名管道也支持数据的双向传输

    • 缓冲区:都可以使用内核缓冲区来存储传输的数据,允许发送和接收过程之间的解耦。

  • 不同点

    • 创建与管理:
      • 命名管道:通过系统调用mkfifo()或者使用指令mkfifo创建,创建后会生成一个管道文件,后续都可以基于这个文件进行操作(和操作普通文件一样,使用open,read,write,close等API进行操作),后续使用完管道以后还需要使用close函数关闭文件描述符。
      • 匿名管道:通过系统调用pipe()函数创建,创建后会返回两个文件描述符,其中fd[0]表示管道的读端,fd[1]表示管道的写端。通过这两个文件描述符来实现基于匿名管道的进程间通信。使用完后会自动将匿名管道销毁。
    • 命名
      • 命名管道:由于创建命名管道后会在文件系统中存在一个特殊的设备文件(管道文件),所以叫做命名管道,它们的生命周期可以独立于创建它们的进程直到用户显式操作将其删除。
      • 匿名管道:没有名字
    • 使用范围:
      • 命名管道:可用于任何两个进程之间的通信,通常在不同的用户会话或系统的进程之间使用。
      • 匿名管道:只能用于有血缘关系的进程,例如兄弟进程和父子进程。

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

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

相关文章

PyQt 入门教程(3)基础知识 | 3.1、使用QtDesigner创建.ui文件

文章目录 一、使用QtDesigner创建.ui文件1、创建.ui文件2、生成.py文件3、使用新生成的.py文件4、编辑新生成的.py文件 一、使用QtDesigner创建.ui文件 1、创建.ui文件 打开PyCharm&#xff0c;使用自定义外部工具QtDesigner创建mydialog.ui文件&#xff0c;如下&#xff1a; …

大话网络协议:HTTPS协议和HTTP协议有何不同?为什么HTTPS更安全

大家现在访问网络,浏览网页,注意一下的话,网址前面基本上都是一个 https:// 的前缀,这里就是说明这个网址所采用的协议是 https 协议。那么具体应该怎么理解 https 呢? 本文我们就力争能清楚地解释明白这个我们目前应该最广的协议。 理解HTTP协议 要解释 https 协议,当…

[mysql]聚合函数GROUP BY和HAVING的使用和sql查询语句的底层执行逻辑

#GROUP BY的使用 还是先从需求出发,我们现在想求员工表里各个部门的平均工资,最高工资 SELECT department_id,AVG(salary) FROM employees GROUP BY department_id 我们就会知道它会把一样的id分组,没有部门的就会分为一组,我们也可以用其他字段来分组,我们想查询不同jb_id…

动力电池中的基础知识总结

动力电池基础 基本定义 电池的分类方式多样&#xff0c;按工作特性和储存方式分类 一次电池&#xff08;Primary Battery&#xff09;&#xff1a;只能进行一次放电&#xff08;disposable or single-use batteries&#xff09;&#xff0c;放电后不能通过充电的方式使其恢复…

Postgresql pgsql 插件之postgis 安装配置

相关链接&#xff1a; pgsql编译安装 一、说明 postgis是pgsql最强大的几个插件之一&#xff0c;可以用于地理信息系统&#xff08;gis&#xff09;的搭建 二、插件安装启动 由于我的pgsql是编译安装的&#xff0c;所以插件也是编译安装&#xff0c;更加灵活。 1.进入到源…

草地杂草数据集野外草地数据集田间野草数据集YOLO格式VOC格式目标检测计算机视觉数据集

一、数据集概述 数据集名称&#xff1a;杂草图像数据集 数据集是一个包含野草种类的集合&#xff0c;其中每种野草都有详细的特征描述和标记。这些数据可以包括野草的图片、生长习性、叶片形状、颜色等特征。 1.1可能应用的领域 农业领域: 农业专家和农民可以利用这一数据集来…

IDEA无法生成自动化序列serialVersionUID及无法访问8080端口异常的解决方案

作者&#xff1a;CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境&#xff1a;IDEA 今天是1024程序员节&#xff0c;先祝大家节日快乐&#xff01; 无法生成自动化序列serialVersionUID 如果我们在idea当中想要通过generate来生成自动化序列&#xff0c;如下图…

Nest.js 实战 (十五):前后端分离项目部署的最佳实践

☘️ 前言 本项目是一个采用现代前端框架 Vue3 与后端 Node.js 框架 Nest.js 实现的前后端分离架构的应用。Vue3 提供了高性能的前端组件化解决方案&#xff0c;而 Nest.js 则利用 TypeScript 带来的类型安全和模块化优势构建了一个健壮的服务端应用。通过这种技术栈组合&…

智慧升级,知识无界:十大搭建知识库软件助你前行

在知识爆炸的时代&#xff0c;如何高效地管理、整合与利用信息&#xff0c;成为了个人与企业发展的核心竞争力。智慧升级&#xff0c;意味着我们不仅要掌握丰富的知识&#xff0c;更要学会运用工具&#xff0c;让知识无界流通&#xff0c;助力个人成长与企业创新。以下是精心挑…

全网最全开放式自动猫砂盆测评!魔铲、cewey、萌娃有什么区别?

最近我发现很多铲屎官在购买开放式自动猫砂盆时&#xff0c;总是会在cewey、魔铲、萌娃之间犹豫&#xff0c;不知道这三款自动猫砂盆到底有什么不同&#xff0c;盲选又怕选错&#xff0c;买了个祖宗回去&#xff0c;今天我就给大家好好说说&#xff0c;cewey、魔铲、萌娃之间&a…

SL3160 dcdc150V降压5.1V/1A 车载GPS定位器供电芯片

一、主要特性 宽输入电压范围&#xff1a;SL3160支持10~150V的宽输入电压范围&#xff0c;使其能够适应各种电源电压波动&#xff0c;确保稳定输出。 高效降压转换&#xff1a;该芯片采用先进的电源管理技术&#xff0c;转换效率高达90%以上&#xff0c;降低了散热压力和整体…

解决xhell连接虚拟机导致小键盘无法使用

我们在使用xhell连接虚拟机的时候经常会出现小键盘输入导致一些乱的字母输入&#xff0c;当然会解决方法也简单只需要在连接的时候调试下设置就好 1打开xhell&#xff08;我的版本是xhell6&#xff09; 2.创建连接3&#xff0c;选择vt模式-初始数字键盘模式-设置为普通 4.这些…

flutter 使用三方/自家字体

将字体放入assets/fonts下 在pubspec.yaml文件中flutter下添加如下代码&#xff1a; flutter:fonts:- family: MyCustomFontfonts:- asset: assets/fonts/MyCustomFont.ttf 在flutter Text widget中使用字体 import package:flutter/material.dart;void main() > runApp(…

【计网】深入理解网络通信:端口号、Socket编程及编程接口

目录 1.端口号 1.1.理解源 IP 地址和目的 IP 地址 1.2.认识端口号 1.3.端口号范围划分 1.4理解 "端口号" 和 "进程 ID" 2.socket编程 2.1.理解 socket 2.2.socket编程的概念 2.3. 传输层的典型代表 认识 TCP 协议 认识 UDP 协议 2.3 网络字节序…

常见的材料力学特性

材料特性参数 目录 一、弹性指标 1. 正弹性模量 2. 切变弹性模量 3. 比例极限 4. 弹性极限 二、强度性能指标 1. 强度极限 2. 抗拉强度 3. 抗弯强度 4. 抗压强度 5. 抗剪强度 6. 抗扭强度 7. 屈服极限&#xff08;或者称屈服点&#xff09; 8. 屈服强度 9. 持久…

【OpenAI】第六节(语音生成与语音识别技术)从 ChatGPT 到 Whisper 的全方位指南

前言 在人工智能的浪潮中&#xff0c;语音识别技术正逐渐成为我们日常生活中不可或缺的一部分。随着 OpenAI 的 Whisper 模型的推出&#xff0c;语音转文本的过程变得前所未有的简单和高效。无论是从 YouTube 视频中提取信息&#xff0c;还是将播客内容转化为文本&#xff0c;…

WPF+Mvvm项目入门完整教程-基于SqlSugar的数据库实例(三)

目录 数据库实现创建数据库类库资源获取 在上一节中&#xff0c;我们实现了主页UI框架和基础菜单功能&#xff0c;本节主要实现数据库的类库创建、数据功能接口以及泛型方法实现。本例使用的数据库为 MySql数据库&#xff0c;ORM框架采用 SqlSugar 实现。 数据库实现 创建数据…

Socket通信基础

1 基本概念 socket是操作系统提供的一套标准化网络编程接口&#xff0c;应用程序调用这些接口&#xff0c;可以编写出服务端&#xff08;Server&#xff09;和客户端&#xff08;Client&#xff09;的socket程序&#xff0c;两端的socket通过特定的IP地址和端口连接起来&#…

短视频账号矩阵系统源码---独立saas技术部署

#短视频账号矩阵系统# #短视频矩阵源码# #短视频账号矩阵系统技术开发# 抖音seo账号矩阵系统&#xff0c;短视频矩阵系统源码&#xff0c; 短视频矩阵是一种常见的视频编码标准&#xff0c;通过多账号一键授权管理的方式&#xff0c;为运营人员打造功能强大及全面的“矩阵式“…

html 轮播图效果

轮播效果&#xff1a; 1、鼠标没有移入到banner,自动轮播 2、鼠标移入&#xff1a;取消自动轮播、移除开始自动轮播 3、点击指示点开始轮播到对应位置 4、点击前一个后一个按钮&#xff0c;轮播到上一个下一个图片 注意 最后一个图片无缝滚动&#xff0c;就是先克隆第一个图片…