Linux--进程间通信

1.进程间通信

进程间通信的背景:

进程之间是相互独立的,进程由内核数据结构和它所对应的代码和数据,通信成本比较高。

进程间通信目的:

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

进程间通信本质:

进程间通信的前提就是让不同的进程看到相同的一份“内存”,这块“内存”不属于任何一个进程,属于操作系统。

2.进程间通信的方式

1.管道 (匿名管道 命名管道)

2.System V通信 (多进程 单机通信)

3.POSIX 通信 (多线程 网络结构)(在这里不讲)

3.管道讲解

1.管道分类:匿名管道,命名管道。

2.什么是管道

        管道是一种古老的传输资源的方式,是UNIX中过来的传输方式,是从一个进程传递到另一个进程的方法。管道是单向通信的,传输的都是资源,不能同时完成双向通信。

3.实现原理:

匿名管道:

  如何做到不同进程看到相同的内存呢?

fork()函数让具有血缘关系的进程进行进程间通信,常用于父子进程。

创建管道文件,int pipe(int pipefd[2]);

具体实现看代码:

#include <iostream>
#include <cstdio>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>using namespace std;int main()
{int pipefd[2]={0}; pipefd[0(嘴巴,读书)]: 读端 , pipefd[1(钢笔,写)]: 写端int n = pipe(pipefd);assert(n != -1); // debug assert, release assert(void)n;#ifdef DEBUGcout << "pipefd[0]: " << pipefd[0] << endl; // 3cout << "pipefd[1]: " << pipefd[1] << endl; // 4#endifpid_t id=fork();if(id==0){close(pipefd[1]);char buff[1024*8];while(true){ssize_t s = read(pipefd[0], buff, sizeof(buff) - 1);if(s>0){buff[s] = 0;cout << "child get a message[" << getpid() << "] Father# " << buff << endl;}else{cout << "writer quit(father), me quit!!!" << endl;break;}}close(pipefd[0]);//关闭文件,可以不用exit(0);//return 0;}else if(id>0){close(pipefd[0]);string message = "我是父进程,我正在给你发消息\n";int count = 0;char send_buffer[1024 * 8];while(true){//构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",message.c_str(), getpid(), count++);//写到一个字符串write(pipefd[1], send_buffer, strlen(send_buffer));if(count==5){cout<<count<<endl;break;}}close(pipefd[1]);//进程等待//int status;pid_t ret = waitpid(id,NULL,0);cout << "id : " << id << " ret: " << ret <<endl;assert(ret > 0); (void)ret;}return 0;
}

结论:

  • 管道是用来进行具有血缘关系的·进程实现进程间通信--常用于父子进程。
  • 具有让进程间协同通信,提供了访问控制。
  • 提供面向流的听信服务,面向字节流的服务。
  • 管道是基于文件的,文件的生命周期是基于进程的,所以管道的生命周期是基于进程的。
  • 管道是单向通信的,就是半双工通信的一种特殊形式。

如果写得快,读得慢,则写满管道之后不再写入。

如果写的慢,读得快,则管道没有数据是,则停下来等待。

如果写入端关闭,则读到文件末尾结束

关闭读,则OS会关闭写进程。

命名管道:

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

命名管道的创建:

直接在命令行上创建:mkfifo filename

也可以在程序中创建:int mkfifo(const char *filename,mode_t mode);

第一个参数为文件名,第二个为权值

创建命名管道:

int main(int argc, char *argv[])
{mkfifo("p2", 0644);return 0;
}

匿名管道与命名管道之间的区别:

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

读写规则:

如果当前打开操作是为读而打开FIFO时:
        O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
        O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时:
        O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
        O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

代码实现:

#ifndef _COMM_H_
#define _COMM_H_
//公共文件
#include <iostream>
#include <cstdio>
#include <string>
#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 128string ipcPath = "./fifo.ipc";#endif
//日志文件#include <iostream>
#include <ctime>
#include <string>#define Debug   0
#define Notice  1
#define Warning 2
#define Error   3//创建方法
const 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//读文件
#include "comm.hpp"
#include "log.hpp"static void getMessage(int fd)
{char buffer[SIZE];while(true){memset(buffer,'\0',sizeof buffer);int s=read(fd,buffer,sizeof(buffer)-1);if(s>0){cout <<"["  << getpid() << "] "<< "client say> " << buffer << endl;//还有文件}else if(s=0){cerr <<"["  << getpid() << "] " << "read end of file, clien quit, server quit too!" << endl;//读到文件末尾break;}else if(s<0){perror("read s");break;}}
}
int main()
{int id=mkfifo("textfifo.txt",0666);//创建命名管道文件if(id<0)//创建失败{perror("mkfifo id");return 0;}Log("创建管道文件成功", Debug) << " step 1" << endl;//打印日志int fd = open(ipcPath.c_str(), O_RDONLY);//打开文件,以读的方式if (fd < 0){perror("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){getMessage(fd);exit(1);}}//父进程阻塞等待for(int i=0;i<nums;i++){waitpid(-1, nullptr, 0);}close(id);//关闭文件Log("关闭文件",Debeg) << "step 3" << endl;unlink(ipcPath.c_str()); //删除文件Log("删除文件",Debeg) << "step 4" << endl;return 0;
}//写文件
#include "comm.hpp"
#include "log.hpp"int main()
{int id=open("ipcPath.c_str",O_WRONLY);//获取文件if(id<0){perror("open file");}string buffer;while(true){cout << "Please Enter Message Line :> ";std::getline(std::cin, buffer);//写文件write(id,buffer.c_str(),buffer.size());}close(id);//关闭//unlink(id);return 0;
}

结论:双方进程,可以看到同一份文件,,该文件一定在系统路径中,路径具有唯一性,管道文件可以被打开,但是不会将内存中的数据刷新到磁盘中。且有名字。

3.System V 通信

共享内存:共享内存区是最快的IPC形式。共享内存是在物理内存上申请一块空间,再让两个进程各自在页表建立虚拟地址和这块空间的映射关系。这样两个进程看到的就是同一份资源,这一份资源就叫做共享内存。

原理:

共享内存的提供者是操作系统,操作系统通过先描述再组织的方式管理共享内存。

共享内存=共享内存块+对应的共享内存的内核数据结构。

两个进程创建共享内存需要以下步骤:

  1. 创建共享内存
  2. 将两个进程关联到共享内存
  3. 取消两个进程和共享内存的关联
  4. 删除共享内存

注意: 前两个步骤是为了让两个进程实现通信,后面两个步骤是释放共享内存空间,要不然就会内存泄漏了。(与我们之前用的malloc是类似的)。

创建共享内存所需要的函数:

1.ftok——获取一个共享内存的唯一标识符

函数:key_t ftok(const char *pathname, int proj_id);

功能:获取一个共享内存的唯一标识符 key

参数: pathname 文件名 ;proj_id 只有是一个非0的数都可以 .

返回值:成功返回key;失败返回 -1

2..shmget——创建共享内存

函数:int shmget(key_t key, size_t size, int shmflg);

key:传入ftok函数获取的共享内存唯一标识符
size:共享内存的大小(页(4kb)的整数倍)
shmflg:权限,由9个权限标准构成

这里介绍两个选项
IPC_CREAT: 如果底层存在这个标识符的共享内存空间,就打开返回,不存在就创建
IPC_EXCL: 如果底层存在这个标识符的共享内存空间,就出错返回
两个选项合起来用就可以穿甲一个权限的共享内存空间
返回值:
成功返回共享内存标识码值(给用户看的),失败返回-1.

3.shmat——将共享内存空间关联到进程地址空间

函数:void *shmat(int shmid, const void *shmaddr, int shmflg);

功能: 将共享内存空间关联到进程地址空间
参数:
shmid:共享内存标识符
shmaddr:指定连接地址。
shmfig:两个可能取值是SHM_RND和SHM_RDONLY
返回值: 成功返回一个指针(虚拟地址空间中共享内存的地址,是一个虚拟地址),失败返回-1
说明:
shmaddr为NULL,核心自动选择一个地址。
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整> 数倍。公式:shmaddr -(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

4.shmdt——取消关联

函数:int shmdt(const void *shmaddr);

功能: 取消共享内存空间和进程地址空间的关联
参数:
shmaddr:共享内存的起始地址(shmat获取的指针)
返回值: 成功返回0,失败返回-1

5.shmctl——控制共享内存

函数:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能: 控制共享内存
参数:
shmid:共享内存标识符
cmd:命令,有三个
IPC_STAT: 把shmid_ds结构中设置为共享内存当前关联值
IPC_SET: 把共享内存的当前关联值设置为shmid_ds数据结构中的值
IPC_RMID:删除共享内存段
buf:指向一个报错这共享内存的模式状态和访问权限的数据结构
返回值: 成功返回0,失败返回-1。

共享内存的特性:

对于共享内存来说,它与管道有不同的特性,导致共享内存不同的使用方法

1.管道需要使用系统接口来调用,但是共享内存可以不用经过系统调用,直接可以访,双方进程如果要进行通信,直接进行内存级的读和写即可。共享内存实在堆栈之间的区域的,堆栈相对而生,中间区域为共享内存,不用经过操作系统。

共享内存是最快的,为什么呢?

因为如果是管道,需要从键盘写入,然后再拷贝到自己定义的缓冲区中,然后再次拷贝到内存中,再从内存中拷贝到用户级缓冲区中,最后再拷贝到屏幕中,需要经历最少4词的拷贝过程。

共享内存直接面向用户,所以从键盘中输入的内容直接进入到内存中,然后经过内存到达显示器中,最少只有2次拷贝,所以他的速度是最快的。

对于共享内存的理解:

为了进行进程间通信,需要让不同的进程看到相同的一份资源,所以之前的管道,本质都是优先解决一个问题,让不同的进程看到同一份资源!!!

让不同的进程看到相同的内存,带来了有些时序问题,造成数据不一致问题。

结果:

我们把多个进程(执行流)看到的同一份资源称为临界资源。

我们把自己的进程,访问临界资源的代码,称为临界区。

为了更好地进行临界区的保护,可以让多执行流在任何时刻都只有一个进程进入临界区。即互斥!!!

原子性:要么不做,要么做完,没有中间状态,即为原子性!!

所以,多个执行流,互相运行的时候互相干扰,主要是我们不加保护的访问了相同的资源(临界资源),在非临界区多个执行流是互不干扰的。

代码演示:

#pragma once#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cassert>
#include "Log.hpp"using namespace std; //不推荐#define PATH_NAME "/home/SSS"//环境变量
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //共享内存的大小,最好是页(PAGE: 4096)的整数倍 #define FIFO_NAME "./fifo"
//创建一个管道,形成访问控制
class Init
{
public:Init(){umask(0);int n = mkfifo(FIFO_NAME, 0666);assert(n == 0);(void)n;Log("create fifo success",Notice) << "\n";}~Init(){unlink(FIFO_NAME);Log("remove fifo success",Notice) << "\n";}
};#define READ O_RDONLY
#define WRITE O_WRONLYint OpenFIFO(std::string pathname, int flags)
{int fd = open(pathname.c_str(), flags);assert(fd >= 0);return fd;
}void Wait(int fd)
{Log("等待中....", Notice) << "\n";uint32_t temp = 0;ssize_t s = read(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;
}void Signal(int fd)
{uint32_t temp = 1;ssize_t s = write(fd, &temp, sizeof(uint32_t));assert(s == sizeof(uint32_t));(void)s;Log("唤醒中....", Notice) << "\n";
}void CloseFifo(int fd)
{close(fd);
}#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#include "comm.hpp"
#include "log.hpp"string TransToHex(key_t k)
{char buffer[32];snprintf(buffer, sizeof buffer, "0x%x", k);return buffer;
}int main()
{key_t key=ftok(PATH_NAME,PROJ_ID);assert(key!=-1);Log("create key done", Debug) << " server key : " << TransToHex(k) << endl;//创建共享内存int shmid=shmget(key,SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);assert(shmid!=-1);Log("create shm done", Debug) << " shmid : " << shmid << endl;sleep(10);// 3. 将指定的共享内存,挂接到自己的地址空间char *shmaddr = (char *)shmat(shmid, nullptr, 0);Log("attach shm done", Debug) << " shmid : " << shmid << endl;sleep(10);// 这里就是通信的逻辑了// 将共享内存当成一个大字符串// char buffer[SHM_SIZE];// 结论1: 只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方,就可以立马看到对方写入的数据。//         共享内存是所有进程间通信(IPC),速度最快的!不需要过多的拷贝!!(不需要将数据给操作系统)// 结论2: 共享内存缺乏访问控制!会带来并发问题 【如果我想一定程度的访问控制呢? 能】int fd = OpenFIFO(FIFO_NAME, READ);for(;;){Wait(fd);// 临界区printf("%s\n", shmaddr);if(strcmp(shmaddr, "quit") == 0) break;// sleep(1);}// 4. 将指定的共享内存,从自己的地址空间中取消关联int n = shmdt(shmaddr);assert(n != -1);(void)n;Log("detach shm done", Debug) << " shmid : " << shmid << endl;sleep(10);// 5. 删除共享内存,IPC_RMID即便是有进程和当下的shm挂接,依旧删除共享内存n = shmctl(shmid, IPC_RMID, nullptr);assert(n != -1);(void)n;Log("delete shm done", Debug) << " shmid : " << shmid << endl;return 0;
}#include "comm.hpp"int main()
{key_t k = ftok(PATH_NAME, PROJ_ID);//创建key值if (k < 0){Log("create key failed", Error) << " client key : " << k << endl;exit(1);}Log("create key done", Debug) << " client key : " << k << endl;// 获取共享内存int shmid = shmget(k, SHM_SIZE, 0);if(shmid < 0){Log("create shm failed", Error) << " client key : " << k << endl;exit(2);}Log("create shm success", Error) << " client key : " << k << endl;sleep(10);char *shmaddr = (char *)shmat(shmid, nullptr, 0);//将共享内存关联到进程地址空间if(shmaddr == nullptr){Log("attach shm failed", Error) << " client key : " << k << endl;exit(3);}Log("attach shm success", Error) << " client key : " << k << endl;sleep(10);int fd = OpenFIFO(FIFO_NAME, WRITE);//使用// client将共享内存看做一个char 类型的bufferwhile(true){ssize_t s = read(0, shmaddr, SHM_SIZE-1);if(s > 0){shmaddr[s-1] = 0;Signal(fd);if(strcmp(shmaddr,"quit") == 0) break;}}CloseFifo(fd)// 去关联int n = shmdt(shmaddr);//取消关联assert(n != -1);Log("detach shm success", Error) << " client key : " << k << endl;sleep(10);// client 要不要chmctl删除呢?不需要!!return 0;
}

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

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

相关文章

[ Linux Busybox ] flash_eraseall 命令解析

文章目录 相关结构体flash_eraseall 函数实现flash_eraseall 实现流程图 文件路径&#xff1a;busybox-1.20.2/miscutils/flash_eraseall.c 相关结构体 MTD 相关信息结构体 struct mtd_info_user {__u8 type; // MTD 设备类型__u32 flags; // MTD设…

14.序列化和文件的输入/输出 保存对象

14.1 保存对象状态 你已经奏出完美的乐章&#xff0c;现在会想把它储存起来。你可以抓个文房四宝把它记下来&#xff0c;但也可以按下储存按钮(或按下File菜单上的Save)。然后你帮文件命名&#xff0c;并希望这个文件不会让屏幕变成蓝色的画面。 储存状态的选择有很多种&…

App备案-iOS云管理式证书 Distribution Managed 公钥及证书SHA-1指纹的获取方法

​ 根据近日工业和信息化部发布的《工业和信息化部关于开展移动互联网应用程序备案工作的通知》&#xff0c;相信不少要进行IOS平台App备案的朋友遇到了一个问题&#xff0c;就是apple不提供云管理式证书的下载&#xff0c;也就无法获取公钥及证书SHA-1指纹。 ​ 已经上架的应用…

aosp定制android系统

目录 AOSP 准备工作(配置) 确定机型和版本 初始化 git安装 curl安装 同步源码 环境变量 创建aosp目录 指定同步版本 解下来安装编译需要的依赖 编译aosp源码 刷入系统 AOSP 全称 Android Open Source Project 是指Android开源项目&#xff0c;它是由Google主导的…

【有源码】基于uniapp的农场管理小程序springboot基于微信小程序的农场检测系统(源码 调试 lw 开题报告ppt)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

算法:分治法-力扣题最大子数组和

文章目录 概念应用步骤实现过程-快速排序为例具体实现步骤&#xff1a;代码实现&#xff1a; 力扣-2586统计范围内的元音字符题解 概念 分治法是一种算法思想&#xff0c;其核心思想是将一个大问题分割成若干个小问题来解决。通过对小问题的分别计算&#xff0c;最终得到大问题…

SEO是什么?独立站如何进行SEO优化

创建一个独立网站并不是难事&#xff0c;但要做好独立网站并进行SEO优化以增加自然流量可能是一个不小的挑战。今天&#xff0c;我们将分享一些关于独立网站SEO优化的技巧&#xff0c;并详细探讨如何提升流量。 在本文中&#xff0c;我们将主要关注谷歌SEO&#xff0c;但请不要…

2000-2022年上市公司专利申请、创新绩效数据

2000-2022年上市公司专利申请、创新绩效数据 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;年份、股票代码、股票简称、行业名称、行业代码、省份、城市、区县、行政区划代码、城市代码、区县代码、首次上市年份、上市状态、专利申请总量、发明专利申请总量、实用新型…

Linux--vim

文章目录 Vim的介绍Vim的几种模式命令模式下的基本操作批量化注释Vim的简单配置使用插件 Vim的介绍 Vim是一个强大的文本编辑器&#xff0c;是从vi编辑器发展而来的&#xff0c;在vi编辑器的基础上进行了改进和拓展&#xff0c;具有强大的特性和功能。 Vim是一个自由开源软件&…

技术分享 | app自动化测试(Android)--显式等待机制

WebDriverWait类解析 WebDriverWait 用法代码 Python 版本 WebDriverWait( driver,timeout,poll_frequency0.5,ignored_exceptionsNone) 参数解析&#xff1a; driver&#xff1a;WebDriver 实例对象 timeout: 最长等待时间&#xff0c;单位秒 poll_frequency: 检测的间…

MySQL 8.0 Clone Plugin 详解

文章目录 前言1. 克隆插件安装2. 克隆插件的使用2.1 本地克隆2.2 远程克隆 3. 克隆任务监控4. 克隆插件实现4.1 Init 阶段4.2 File Copy4.3 Page Copy4.4 Redo Copy4.5 Done 5. 克隆插件的限制6. 克隆插件与 Xtrabackup 的异同7. 克隆插件相关参数后记 前言 克隆插件&#xff…

Go uuid库介绍

简介&#xff1a; 在现代软件开发中&#xff0c;全球唯一标识符&#xff08;UUID&#xff09;在许多场景中发挥着重要的作用。UUID是一种128位的唯一标识符&#xff0c;它能够保证在全球范围内不重复。在Go语言中&#xff0c;我们可以使用第三方库github.com/google/uuid来方便…

python 之 集合的相关知识

文章目录 1. 创建集合使用花括号 {}使用 set() 函数 2. 集合的特点3. 集合操作添加元素删除元素 4. 集合运算5. 不可变集合总结 在 Python 中&#xff0c;集合&#xff08;Set&#xff09;是一种无序且不重复的数据集合。它是由一组唯一元素组成的。下面是关于集合的一些基本知…

【云原生】使用nginx反向代理后台多服务器

背景 随着业务发展&#xff0c; 用户访问量激增&#xff0c;单台服务器已经无法满足现有的访问压力&#xff0c;研究后需要将后台服务从原来的单台升级为多台服务器&#xff0c;那么原来的访问方式无法满足&#xff0c;所以引入nginx来代理多台服务器&#xff0c;统一请求入口…

Leetcode-234 回文链表

我的解法&#xff1a;使用栈&#xff0c;定义了len略微复杂&#xff0c;拿链表的后半部分和前半部分比较即可&#xff0c;没必要全部比较 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* List…

369-HI-R-M-0-0-0-E 数字化转型如何改变DCS和SCADA

369-HI-R-M-0-0-0-E 数字化转型如何改变DCS和SCADA 高瞻远瞩的过程制造商正在投资数字化转型&#xff0c;而DCS和SCADA最终将成为这些努力的一部分。因此&#xff0c;它们与所有其他流程制造技术一起发展。DCS和SCADA系统的变化符合自动化金字塔正在进行的转变&#xff0c;它也…

Linux驱动开发——USB设备驱动

目录 一、 USB 协议简介 二、 Linux USB 驱动 三、 USB 设备驱动实例 一、 USB 协议简介 USB(Universal Serial Bus&#xff0c;通用串行总线)正如它的名字一样&#xff0c;是用来连接PC外设的一种通用串行总线&#xff0c;即插即用和易扩展是它最大的特点。所谓即插即用&am…

GPT-4V:AI在医疗领域的应用

OpenAI最新发布的GPT-4V模型为ChatGPT增添了语音和图像功能&#xff0c;为用户提供了更多在日常生活中使用ChatGPT的方式。这次更新将为用户带来更加便捷、直观的交互体验&#xff0c;用户可以直接通过拍照上传图片&#xff0c;并提出相关问题。OpenAI的最终目标是构建一个安全…

MYSQL多表联查on和where的区别

目录 一、背景 二、探究 2.1、统计每个班级中女生的数量 错误的写法 查询结果 正确的写法 查询结果 2.2、只统计"一班"的学生数量 错误的写法 查询结果 正确的写法 查询结果 三、总结 一、背景 在一次对数据进行统计的时候&#xff0c;需要对两张表进行…

【vite】vite.defineConfig is not a function/npm无法安装第三方包问题

当使用vite命令 npm init vite-app 项目名称时配置 import vue from vitejs/plugin-vueexport default defineConfig({plugins: [vue()] })会报错vite.defineConfig is not a function 还有就是npm下载的时候也会报错 原因vite插件vitejs/plugin-vue和vite版本问题 解决 调…