如何在Linux中实现进程间通信

news/2024/4/27 9:56:52/文章来源:https://blog.csdn.net/qq_65307907/article/details/128749005

致前行路上的人:

        要努力,但不要着急,繁花锦簇,硕果累累都需要过程!

目录

1.进程间通信介绍

1.1进程间通信的目的

1.2进程间通信发展

1.3进程间通信分类

1.4进程间通信的本质

2.管道

2.1什么是管道

2.2管道与进程的关系

2.3什么是匿名管道

2.4进程与管道通信的特点

 2.5实例代码

2.6进程间通信读写策略

2.7管道的特点

2.8命名管道

2.9实例代码:

3.system V共享内存

3.1共享内存的概念

3.2共享内存的原理

3.3申请共享内存的函数

3.4查看共享内存

 3.5控制共享内存的函数

3.6共享内存和进程关联的函数

3.7共享内存和进程去关联的函数

3.8实例代码

3.9共享内存的特点

4.system V消息队列

4.system V信号量 

5.IPC资源组织方式

1.进程间通信介绍

1.1进程间通信的目的

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

1.2进程间通信发展

管道——>基于文件系统
System V进程间通信——>聚焦在本地通信
POSIX进程间通信——>跨主机通信

1.3进程间通信分类

管道
<匿名管道pipe>                                <命名管道>
System V IPC
<System V 消息队列 >                  <System V 共享内存>                        < System V 信号量>
POSIX IPC
<消息队列>                 <共享内存 >                <信号量>                 <互斥量 >                 <条件变量 >                   <读写锁>

1.4进程间通信的本质

要通信的进程必须看到同一份公共资源,所以要求操作系统必须直接或间接的方式给进程提供“内存空间”,而不同进程间通信的分类是操作系统中不同模块提供的公共资源进行分类的,因此不论是哪一种通信,前提必须是要提供一份公共资源!

2.管道

2.1什么是管道

管道是一种进程间通信的形式,是基于文件系统给进程提供的一份公共资源。

2.2管道与进程的关系

磁盘上的文件被打开,创建struct file结构体对象,父进程struct files_struct中保存打开文件的文件描述符,然后让父进程指向该文件,fork创建子进程,因为进程间具有独立性,所以子进程继承父进程的进程管理结构也指向该文件,每个被打开的文件对象中都有一块自己的内核缓冲区,而这个内核缓冲区就是一份公共资源,一个进程在这块区域写入数据,一个进程在这块区域读数据,这样就实现了进程间的通信了!

 注:把实现进程间通信的这种文件就被称为管道文件

2.3什么是匿名管道

不同进程间通信的管道文件并不是磁盘上的文件,而是内存级文件,因为如果说是磁盘上的文件,每次进程间通信都需要进行访问外设,效率就会很慢,所以操作系统为了提高效率,就将该文件创建在了内存,而在内存上创建的该文件是通过文件描述符来进行寻找的,而不是通过文件名进行标识的,所以把这种创建在内存级的文件就被称为是匿名管道!

2.4进程与管道通信的特点

 2.5实例代码

创建管道文件:

 参数
pipefd:文件描述符数组,其中pipefd[0]表示读端, pipefd[1]表示写端
返回值:成功返回0,失败返回错误代码

1.子进程写入,父进程读取

#include<iostream>
#include<cassert>
#include<cstring>
#include<cstdio>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
using namespace std;
//父进程读取。子进程写入
int main()
{//第一步:创建文件,打开读写端int fd[2];int n = pipe(fd);assert(n == 0);//第二步:创建子进程pid_t id = fork();assert(id >= 0);//子进程:if(id == 0){close(fd[0]);//子进程通信代码:const char*s = "我是子进程";int cnt = 0;while(true){cnt++;char buffer[1024];snprintf(buffer,sizeof buffer,"child->parent say:%s[%d][%d]",s,cnt,getpid());write(fd[1],buffer,strlen(buffer));sleep(1);}_exit(0);}//父进程:close(fd[1]);//父进程通信代码:while(true){char buffer[1024];//读取成功,返回读取字符的个数ssize_t s = read(fd[0],buffer,sizeof(buffer)-1);if(s > 0) buffer[s] = 0;cout << "# " << buffer << "|my pid:"<< getpid() <<endl;}int status = 0;n = waitpid(id,&status,0);assert(n == id);return 0;
}

2.6进程间通信读写策略

1.读慢,写快,缓冲区空间写满之后,写端不再写入

2.读块,写慢,读端读取数据之后,下一次读取为阻塞式等待,等待写端写入数据

3.向缓冲区写入一次数据之后,写端关闭,读端读取一次数据之后,下一次读取到0

4.读关闭,写端向缓冲区写入数据,操作系统会给写端发信号,终止写端

2.7管道的特点

1.管道的声明周期随进程,进程退出,管道释放!

2.只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创
建,然后该进程调用fork,此后父、子进程之间就可应用该管道!

3.管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道!

4.内核会对管道操作进行同步与互斥,是对共享资源进行保护的方案!

2.8命名管道

概念:

命名管道和匿名管道一样也是一种内存级的管道文件,向该管道文件中写入数据的时候不需要刷新到磁盘,不同的是命名管道是具有文件名的,在Linux操作系统中文件是具有唯一性的,可以通过路径加文件名的方式唯一标识,因此两个没有关系的进程可以通过文件名打开同一份文件,也是能够看到同一份资源,因此一个进程向该文件中写入数据,一个进程向该文件中读取数据,此时就完成了进程间通信了!

创建命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

mkfifo filename

命名管道也可以从程序里创建,相关函数有:

 创建成功返回0,失败返回-1

删除管道文件:

 删除成功返回0,失败返回-1

2.9实例代码:

server.cc从管道文件中读取,client.cc向管道文件中写入:

comm.hpp:

#pragma once#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<string>
#include<sys/stat.h>
#include<cstring>
#include<cassert>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
using namespace std;
#define NAME_PIPE "mypipe"//创建管道文件
bool createFifo(const string& path)
{umask(0);int n = mkfifo(path.c_str(),0666);if(n == 0){return true;}else{cout << "errno" << errno << "error:" << strerror(errno) << endl;return false;}
}
void removeFifo(const string& path)
{int n = unlink(path.c_str());assert(n == 0);
}

sever.cc

#include"comm.hpp"int main()
{bool ret = createFifo(NAME_PIPE);assert(ret);int rfd = open(NAME_PIPE,O_RDONLY);if(rfd == -1)   exit(-1);//read:char buffer[1024];while(true){ssize_t s = read(rfd,buffer,sizeof(buffer)-1);if(s > 0){cout << "client->sever#:" << buffer << endl;}else if(s == 0){cout << "client quit!" << endl;break;}else{perror("read fail:");}}removeFifo(NAME_PIPE);return 0;
}

client.cc:

#include"comm.hpp"int main()
{int wfd = open(NAME_PIPE,O_WRONLY);if(wfd == -1)   exit(-1);//write:char buffer[1024];while(true){cout << "Please say:"<< endl;fgets(buffer,sizeof(buffer),stdin);buffer[strlen(buffer)-1] = 0;ssize_t n = write(wfd,buffer,strlen(buffer));assert(n == strlen(buffer));}return 0;
}

运行结果:

3.system V共享内存

3.1共享内存的概念

通过让不同的进程,看到同一个内存块,把这个内存块称为共享内存

3.2共享内存的原理

如图所示:

 不同的进程通过进程地址空间,再通过页表映射到内存的同一块区域

 共享内存是一种通信方式,所有想通信的进程,都可以调用对应的函数接口,申请一块相同的空间实现进程间通信

3.3申请共享内存的函数

shmget函数

功能:用来创建共享内存

原型
int shmget(key_t key, size_t size, int shmflg);

参数
key:这个共享内存段名字

获取key:调用ftok函数


pathname:路径名  proj_id:一个整型

能够让不同的进程申请同一块内存空间,在底层内核数据结构中是通过key来实现的,key被shmget设置进入共享内存相关属性,用来标识该共享内存在内核中的唯一性

size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
 

IPC_CREAT:如果不存在,则进行创建,如果存在,则进行获取

IPC_EXCL:无法单独使用,使用的时候和IPC_CREAT一起使用,IPC_CREAT | IPC_EXCL,如果不存在就创建,如果存在则会报错

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

注:共享内存也是操作系统申请的空间,所以就需要被管理,而管理的本质是先描述,再组织,所以一定存在结构体描述共享内存的相关属性,申请共享内存,除了申请空间外还包含保存共享内存的相关属性的字段

3.4查看共享内存

共享内存的属性:

 共享内存的属性是随OS的,不是随进程的:

指令删除共享内存:ipcrm -m + shmid

 3.5控制共享内存的函数

shmctl函数

控制共享内存的选项: 

 功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的相关属性的数据结构
 


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

3.6共享内存和进程关联的函数

shmat函数

 功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是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,表示连接操作用来只读共享内存

3.7共享内存和进程去关联的函数

shmdt函数

 功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段

3.8实例代码

//comm.hpp
#ifndef __COMM_HPP_
#define __COMM_HPP_#include<iostream>
#include<cstring>
#include<cerrno>
#include<cstdlib>
#include<cstdio>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
using namespace std;
#define PATHNAME "."
#define PROJ_ID 0x66
#define SIZE 4096
key_t getKey()
{key_t key = ftok(PATHNAME,PROJ_ID);if(key == -1){cerr << errno << ":" << strerror(errno) << endl;exit(1);}return key;
}
int getShmHelper(key_t key,int flags)
{int shmid = shmget(key,SIZE,flags);if(shmid == -1){cerr << errno << ":" << strerror(errno) << endl;exit(2);}return shmid;
}
int createShm(key_t key)
{return getShmHelper(key,IPC_CREAT | IPC_EXCL | 0600); //0600 创建共享内存权限
}
int getShm(key_t key)
{return getShmHelper(key,IPC_CREAT);
}
void delShm(int shmid)
{int key = shmctl(shmid,IPC_RMID,nullptr);if(key == -1){cerr << errno << ":" << strerror(errno) << endl;exit(3);}
}
void* attachShm(int shmid)
{void* mem = shmat(shmid,nullptr,0);if((long long)mem == -1L){cerr << errno << ":" << strerror(errno) << endl;exit(4);}return mem;
}
void detachShm(void* start)
{if(shmdt(start) == -1){cerr << errno << ":" << strerror(errno) << endl;}
}
#endif
//client.cc
#include"comm.hpp"int main()
{key_t key = getKey();printf("0x%x\n",key);int shmid = getShm(key);printf("%d\n",shmid);char* start = (char*)attachShm(shmid);printf("attach sucess,address start:%p\n",start);const char*message = "hello server:我是另一个进程,正在给你发信息";pid_t id = getpid();int cnt = 1;char buffer[1024];while(true){// snprintf(buffer,sizeof(buffer),"%s[pid:%d],%d",message,id,cnt++);// memcpy(start,buffer,strlen(buffer)+1);snprintf(start,SIZE,"%s[pid:%d],消息编号:%d",message,id,cnt++);sleep(1);}//去关联detachShm(start);return 0;
}
//server.cc
#include"comm.hpp"int main()
{key_t key = getKey();printf("0x%x\n",key);int shmid = createShm(key);printf("%d\n",shmid);//关联成功:挂接成功char* start = (char*)attachShm(shmid);printf("attach sucess,address start:%p\n",start);//使用:while(true){printf("client say:%s\n",start);sleep(1);}//去关联detachShm(start);//删除共享内存delShm(shmid);return 0;
}

效果演示:

 3.9共享内存的特点

优点:所有进程通信,速度是最快的,能大大减少拷贝的次数

关于共享内存数据拷贝的一道面试题:

题目:考虑共享内存,键盘输入,键盘输出,共享内存有几次数据拷贝?

共享内存的缺点:不会进行同步和互斥的操作,不对数据进行保护

 共享内存的大小:

共享内存的大小,一般建议是4kb的整数倍,系统分配共享内存是以4kb为单位的,4kb是内存划分内存块的基本单位,内核分配内存是采取向上取整的原则,申请小于4kb大小的内存,操作系统也会给你4kb,但是你只能使用自己申请的大小空间

4.system V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

如图所示:

4.system V信号量 

信号量概念:

在了解什么是信号量之前我们来看看,进程要通信需要什么?根据上面管道和共享内存的学习,我们知道不同的进程想实现通信,两个进程必须看到同一份公共资源,一个进程在这个公共资源中写入数据,一个进程从公共资源中读取数据就能实现进程间通信,但是这种方式在实现进程间通信存在隐含的问题,就是不同的进程都能看到这份公共资源,这也就造成公共资源中的数据可以被任何进程修改,使得公共资源不安全的问题,所以为了解决这个问题就需要把公共资源保护起来,如何将这部分公共资源保护起来呢?操作系统实现了两种机制,就是同步互斥

互斥:是指一个进程访问公共资源的数据的时候,另一个进程不能够访问,这也就解决了一个进程正在向公共资源写数据的时候被另一个进程读取,造成数据不一致的问题。

注:将保护起来的公共资源被称为临界资源,使用公共资源的代码称之为临界区,不是使用公共资源的代码称为非临界区

了解上面这些之后,下面举一个电影院买票的例子来理解信号量,假设电影院有100个座位,所以卖票的时候最多只能卖100张票,为了避免电影院多卖票的问题,可以定义一个count变量保存电影院总共的票数,每次卖出一张票之后就将count--,当count==0的时候,就不会再卖票了,这种售票的机制联想到进程申请资源,可以将信号量类比为电影院卖票的个数count,当我们向内存申请公共资源的时候就将信号量--,释放资源的时候就将信号量++,所以所有进程在访问公共资源的时候,必须先申请公共资源,通过这样的方式就可以将公共资源保护起来,但是此时又引入了新的问题,就是信号量本身就是公共资源,那信号量该如何保护自己的安全呢?关于信号量如何保护自己的安全,介绍一个新的概念就是原子性。

什么是原子性?

所谓的原子性就是两中状态,要么做,要么不做。

而信号量就具有这种特性,所以不会有多个进程同时访问同一个信号量的问题,进而也就保护了信号量。

了解了上面这些之后就能得出一个结论,信号量本质是一个计数器,通常用来表示公共资源中,资源数量有多少的问题

注:上面说的预定资源信号量--,释放资源信号量++的两种操作被称为PV操作

当信号量为1的时候,将这个信号称为二元信号量,此时就具备互斥功能。

5.IPC资源组织方式

共享内存:

 消息队列: 

信号量:

这三种进程间通信的接口方式都非常类似,目的是操作系统在对这三种通信的方式公共资源管理的时候就可以通过一个结构体指针数组进行管理,数组的每一个下标存放每种通信方式公共资源属性的第一个字段的地址,然后进而维护和管理这三种通信方式申请的公共资源

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

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

相关文章

轻量级网络模型ShuffleNet V2

在学习ShuffleNet V2内容前需要简单了解卷积神经网络和MobileNet,以及Shuffnet V1的相关内容&#xff0c;大家可以出门左转&#xff0c;去看我之前的几篇博客MobileNet发展脉络&#xff08;V1-V2-V3&#xff09;&#xff0c;轻量级网络模型ShuffleNet V1&#x1f197;&#xff…

Android 高工分享一波性能优化的总结~

随着 Android 开发越来越规范&#xff0c;国内工程师的素质&#xff0c;以及用户对产品的要求也越来越高。这也间接导致我们对研发项目的质量要求到了近乎苛刻的地步&#xff0c;**内存优化、UI 卡顿优化、App 崩溃监控等性能调优也逐渐成了人手必备的技能。**工作之余&#xf…

【数据挖掘】1、综述:背景、数据的特征、数据挖掘的六大应用方向、有趣的案例

目录一、背景1.1 学习资料1.2 数据的特征1.3 数据挖掘的应用案例1.4 获取数据集1.5 数据挖掘的定义二、分类三、聚类四、关联分析五、回归六、可视化七、数据预处理八、有趣的案例8.1 隐私保护8.2 云计算的弹性资源8.3 并行计算九、总结一、背景 1.1 学习资料 推荐书籍如下&a…

【Spark分布式内存计算框架——Spark Streaming】3.入门案例(上)官方案例运行

2.1 官方案例运行 运行官方提供案例&#xff0c;使用【$SPARK_HOME/bin/run-example】命令运行&#xff0c;效果如下&#xff1a; 具体步骤如下&#xff1a; 第一步、准备数据源启动端口&#xff0c;准备数据 nc -lk 9999 spark spark hive hadoop spark hive 第二步、运行…

面试官: 你知道 JWT、JWE、JWS 、JWK嘛?

想起了 之前做过的 很多 登录授权 的项目 它相比原先的session、cookie来说&#xff0c;更快更安全&#xff0c;跨域也不再是问题&#xff0c;更关键的是更加优雅 &#xff0c;所以今天总结了一篇文章来介绍他 JWT 指JSON Web Token&#xff0c;如果在项目中通过 jjwt 来支持 J…

hook与mixin

看完vue3就开始看vue3的源码&#xff0c;表示很懵~ 刚把rollup打包搞完&#xff0c;这不响应式就接着来了&#xff01;&#xff0c;还是写篇直接使用vue3的博客清清脑吧&#xff01; 什么是hook、mixin&#xff1f; mixin: Vue2中多个组件内存在重复JS业务逻辑&#xff0c;使…

k8s学习之路 | Day15 k8s 中的 yaml 语法

文章目录yaml 基础什么是 yaml&#xff1f;yaml 特性适用场景基本语法规则数据类型yaml 对象yaml 数组yaml 纯量yaml 引用k8s 中的 yaml 语法\<string>\<Object>\<map[string]string>\<[]Object>\<boolean>示例 yaml 说明我在学习过程中&#xf…

Mr. Cappuccino的第44杯咖啡——Kubernetes之Service

Kubernetes之ServiceService的概念Service的类型Service演示案例环境准备ClusterIP&#xff08;集群内部访问&#xff09;IptablesIPVSEndpointNodePort&#xff08;对外暴露应用&#xff09;LoadBalancer&#xff08;对外暴露应用&#xff0c;适用于公有云&#xff09;Ingress…

echo命令

这是一条内置命令。 输出指定的字符串 一、语法 echo [选项] [参数] 二、选项 -e&#xff1a;激活转义字符。 使用-e选项时&#xff0c;若字符串中出现以下字符&#xff0c;则特别加以处理&#xff0c;而不会将它当成一般文字输出&#xff1a; \a 发出警告声&#xff1b; \b 删…

产业链金融的前世今生

产业链金融脱胎于供应链金融&#xff0c;又不同于供应链金融。二者的区别是&#xff0c; 供应链金融服务于单个环节、单个企业&#xff0c;而产业链金融是以产业链的核心 企业为依托&#xff0c;针对产业链的各个环节&#xff0c;设计个性化、标准化的金融服务产品&#xff0c;…

阿里巴巴内网 Java 面试 2000 题解析(2023 最新版)

前言 这份面试清单是今年 1 月份之后开始收集的&#xff0c;一方面是给公司招聘用&#xff0c;另一方面是想用它来挖掘在 Java 技术栈中&#xff0c;还有一些知识点是我还在探索的&#xff0c;我想找到这些技术盲点&#xff0c;然后修复它&#xff0c;以此来提高自己的技术水平…

DNS 域名解析

介绍域名 网域名称&#xff08;英语&#xff1a;Domain Name&#xff0c;简称&#xff1a;Domain&#xff09;&#xff0c;简称域名、网域。 域名是互联网上某一台计算机或计算机组的名称。 域名可以说是一个 IP 地址的代称&#xff0c;目的是为了便于记忆。例如&#xff0c…

3.2 网站图的爬取路径

深度优先与广度优先方法都是遍历树的一种方法&#xff0c;但是网站的各个网页 之间的关系未必是树的结构&#xff0c;它们可能组成一个复杂的图形结构&#xff0c;即有回路。如果在前面的网站中每个网页都加一条Home的语句&#xff0c;让每个网页都能回到主界面&#xff0c;那么…

JasperReports studio相关操作

1.2 JasperReports JasperReports是一个强大、灵活的报表生成工具&#xff0c;能够展示丰富的页面内容&#xff0c;并将之转换成PDF&#xff0c;HTML&#xff0c;或者XML格式。该库完全由Java写成&#xff0c;可以用于在各种Java应用程序&#xff0c;包括J2EE&#xff0c;Web应…

Playbook的用法

目录 Playbook Playbook 与 Ad-Hoc 对比 YAML 语言特性 YAML语法简介 支持的数据类型 写法格式 1 scalar 标量 建议缩进两个空格&#xff0c;可多 2 Dictionary 字典 3 List 列表 三种常见的数据格式 Playbook 核心组件 不要用 tab 可以#注释 hosts remote_us…

Oracle-01-简介篇

&#x1f3c6;一、Oracle的历史和发展 Oracle公司成立于1977年&#xff0c;由拉里埃里森&#xff08;Larry Ellison&#xff09;、鲍勃明特&#xff08;Bob Miner&#xff09;和埃德奥茨&#xff08;Ed Oates&#xff09;共同创立。起初&#xff0c;公司的主要业务是开发和销售…

Lenovo Legion Y530-15ICH电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板Lenovo Legion Y530-15ICH处理器Intel Core™ i7-8750H (Coffee-Lake)已驱动内存16GB RAM DDR4 2667MHz已驱动硬盘2TB HP EX950 PCI-E Gen3 x4 NVMe SSD已驱动显卡Intel UHD Graphics 630Nvidia GTX 10…

aws console 使用fargate部署aws服务快速跳转前端搜索栏

测试过程中需要在大量资源之间跳转&#xff0c;频繁的点击不如直接搜索来的快&#xff0c;于是写了一个搜索框方便跳转。 前端的静态页面可以通过s3静态网站托管实现&#xff0c;但是由于中国区需要备案的原因&#xff0c;可以使用ecs fargate部署 步骤如下&#xff1a; 编写…

DHCP服务器的使用以及可能出现的问题(图文详细版)

DHCP服务的使用 开始&#xff0d;管理工具&#xff0d;DHCP,打开DHCP服务器选项窗口 新建作用域 在此处输入名称和描述,单击下一步 随机确定一组IP地址的范围,并指定其子网掩码 , 单击下一步 若想要排除某一个/组特定的IP地址,我们可以在此界面输入该IP地址,若没有,则可…

如何使用 FreeSql 无缝接替 EF Core ?

如何使用 FreeSql 无缝接替 EF Core&#xff0c;并实现数据表的 CRUD 操作项目说明DB & 数据表结构DB & 数据表创建数据表 User 实体模型创建使用 EF Core 实现 User 表新增用户信息添加 EF Core 相关的 nuget 包编写 EF Core 操作 User 表的 CRUD 代码FreeSql 使用 Db…