Linux知识点 -- 进程间通信(一)

Linux知识点 – 进程间通信(一)

文章目录

  • Linux知识点 -- 进程间通信(一)
  • 一、了解进程间通信
    • 1.进程间通信的必要性
    • 2.进程间通信的技术背景
    • 3.进程间通信的本质理解
    • 4.进程间通信的标准
  • 二、匿名管道
    • 1.匿名管道通信的原理
    • 2.匿名管道的使用
    • 3.管道的特点
    • 4.进程池项目
  • 三、命名管道
    • 1.命名管道的原理
    • 2.命名管道的使用


一、了解进程间通信

1.进程间通信的必要性

单进程,无法使用并发能力,更加无法实现多进程协同,如传输数据、同步执行流、消息通知等;

2.进程间通信的技术背景

(1)进程是具有独立性的;虚拟地址空间 + 页表保证进程运行的独立性(进程内核数据结构 + 进程的代码和数据);
(2)通信成本会比较高;

3.进程间通信的本质理解

(1)进程间通信的前提,首先是要让不同的进程看到同一块内存空间(特定的结构组织的);
(2)所谓的看到同一块空间,这块空间应该隶属于哪一个进程呢?应该不能隶属于任何进程,而应该强调共享;

4.进程间通信的标准

  • Linux原生能提供的:管道
    匿名管道;
    命名管道;
  • SystemV IPC:多进程,用于单机通信
    共享内存;
    消息队列;
    信号量;
  • POSIX IPC:多线程,用于网络通信

二、匿名管道

1.匿名管道通信的原理

管道通信是进程之间通过管道进行通信,管道就是两个进程能够共享的一块内存空间;
在这里插入图片描述

  • 管道的本质就是一个文件:
    (1)父进程分别以读写的方式打开一个文件;
    在这里插入图片描述
    完成后,父进程中分别由两个文件描述符对应的文件指针指向同一个文件,一个用来读,一个用来写,这个文件就是管道;
    (2)fork创建子进程;
    在这里插入图片描述
    在父进程fork出子进程后,子进程会拷贝父进程PCB的信息,因此在子进程的文件序列中,也会有同样的文件描述符对应的文件指针指向父进程创建的管道;
    (3)双方进程各关闭自己不需要的文件描述符;
    在这里插入图片描述
    在确定好父子进程的读写后,比如父进程写,子进程读,那就关闭父进程读和子进程写对应的fd,到此,一个管道就形成了;

  • 注:
    (1)创建子进程时,只拷贝和进程相关的数据,PCB,文件相关的不会拷贝,拷贝完成后,父子进程指向的文件是一样的;
    (2)进程间通信都是基于内存的,效率高;
    (3)我们在命令行使用的 | 就是管道;
    在这里插入图片描述
    三个sleep都是进程, 是兄弟进程,一个进程处理完数据,通过管道交给下一个进程;

2.匿名管道的使用

pipe函数:创建管道,相当于完成了父进程以读写方式打开一个文件;
在这里插入图片描述

  • 参数:
    pipefd[2]输出型参数,期望通过这个参数,得到被打开文件的fd(读和写各一个fd),pipefd[0]是读端,pipefd[1]是写端
    返回值:在这里插入图片描述
    成功返回0,失败返回-1;

  • makefile:

pipe-use:pipe-use.cppg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f pipe-use
  • pipe-use.cpp:
#include<iostream>
#include<string>
#include<cstdio>         //在c++中更好兼容c语言的头文件
#include<cstring>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>using namespace std;int main()
{//1.创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;        //debug模式下assert是有效的,而release模式下assert就无效了//(void)n就是将n使用以下,避免release模式下报错//条件编译,打印出pipefd中的内容
//如果想要执行这段代码,在g++编译选项中加上-DEGUB即可
#ifdef DEBUGcout << "pipefd[0]" << pipefd[0] << endl;cout << "pipefd[1]" << pipefd[1] << endl;
#endif//2.创建子进程pid_t id = fork();assert(id != -1);if(id == 0){//子进程 - 读//3.构建单向通信的管道,父进程写入,子进程读取//3.1 关闭子进程不需要的fdclose(pipefd[1]);char buffer[1024];while(true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);//从管道中读取数据if(s > 0){buffer[s] = 0;cout << "child get a message[" << getpid() << "]father# " << buffer << endl;}}close(pipefd[0]);//关闭子进程读取fd。可以不关闭,因为子进程退出时会关闭其所有fdexit(0);}//父进程//3.构建单向通信的管道,父进程写入,子进程读取//3.1关闭父进程不需要的fdclose(pipefd[0]);string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024];while(true){//3.2构建一个变化的字符串//sprintf是向字符串中格式化显示内容snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d",message.c_str(), getpid(), count++);//3.3写入write(pipefd[1], send_buffer, sizeof(send_buffer));//3.4sleepsleep(1);}    pid_t ret = waitpid(id, nullptr, 0);assert(ret < 0);(void)ret;close(pipefd[1]);return 0;
}
  • 注意:
    (1)条件编译
    在这里插入图片描述
    如果想要执行这段代码,在g++编译选项中加上-DEGUB即可:
    在这里插入图片描述
    (2)snprintf
    在这里插入图片描述
    安全的向字符串中格式化显示内容;
    在这里插入图片描述
    (3)cstdio & cstring
    在这里插入图片描述
    这两个头文件是为了更好兼容c语言;
    (4)定义全局buffer能否用来进程间通信?
    不能,因为有写时拷贝的存在,父子进程间一定要保持数据的独立性;

3.管道的特点

  • (1)管道是用来用来具有血缘关系的进程间进行进程间通信的;
  • (2)管道具有让进程间协同的作用,提供了访问控制;
    父进程每1s写入一次数据,子进程也是没1s读取一次,但是我们只在父进程发送时设置了1s写入,子进程并没有设置,这是管道的访问控制;
    如果让父进程一直写入,子进程一段时间读取一次:
    在这里插入图片描述

在这里插入图片描述
运行结果:
在这里插入图片描述
我们可以看到,在父进程将管道写满后,就阻塞在这里了,等待子进程的读取;
在这里插入图片描述
当子进程读取了一定的数据后,父进程才能继续写入;
总结:
a. 写快,读慢,写满管道后就不能再写了;
b. 写慢,读快,管道没有数据的时候,读必须等待;
c. 写关,读就会返回0,标识读到了文件的结尾;
d. 读关,写继续写,OS会终止进程;

  • (3)管道提供的是面向流式的通信服务 – 面向字节流 – 协议
    管道读文件时,不是一次读一条,而是一次读一批;
  • (4)管道是基于文件的,文件的生命周期是随进程的额,管道的生命周期就是随进程的
    让父进程在5s后停止写入,观察子进程:
    在这里插入图片描述
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    写入的一方,fd没有关闭,如果有数据,就读,没有数据就等;
    写入的一方,fd关闭,读取的乙方,read就会返回0,表示读到了文件的结尾;
  • (5)管道是单向通信的,就是半双工通信的一种特殊情况
    半双工:不能同时读写,只能一方读,一方写;

4.进程池项目

父进程创建四个子进程,使用四个管道进行进程间通信,为子进程派发任务,单机版的负载均衡;

  • makefile:
process-pool:process-pool.cppg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f process-pool
  • process-pool.cpp
#include<iostream>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<cassert>
#include<vector>
#include<cstdlib>
#include<ctime>
#include"Task.hpp"#define PROCESS_NUM 5  //子进程数量using namespace std;int waitCommand(int waitFd, bool &quit) //如果对方不发,我们就阻塞
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(uint32_t));return command;
}void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{//加载方法load();//保存管道信息vector<pair<pid_t, int>> slots; //pid : pipefd//先创建多个进程for(int i = 0; i < PROCESS_NUM; i++){//创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n != -1);(void)n;pid_t id = fork();assert(id != -1);if(id == 0){//child//子进程读取close(pipefd[1]);while(true){//等命令bool quit = false;//进程退出判断int command = waitCommand(pipefd[0], quit);//如果对方不发,我们就阻塞if(quit)//如果父进程停止写入,子进程也停止{break;}//执行对应的命令if(command >= 0 && command < handlerSize()){callbacks[command];}else{cout << "非法command" << command << endl;}}exit(1);}// father// 父进程写入close(pipefd[0]);slots.push_back(make_pair(id, pipefd[1])); // 保存管道信息}// 父进程派发任务(均衡的派发给每一个子进程)srand((unsigned long)time(nullptr) ^ getpid() ^ 23323123123L); // 让数据源更随机while (true){int slect;int command;//任务编号cout << "##########################################" << endl;cout << "##   1.show functions  2.send command   ##" << endl;cout << "##########################################" << endl;cout << "Please Slect> ";cin >> slect;if (slect == 1){showHandler();}else if (slect == 2){cout << "Enter Your Command> ";// 选择任务cin >> command;// 选择进程int choice = rand() % slots.size();// 把任务发送给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);}else{}//关闭fd,所有的子进程都会退出for(const auto &slot : slots){close(slot.second);}//回收所有的子进程信息for(const auto &slot : slots){waitpid(slot.first, nullptr, 0);}}return 0;
}
  • Task.hpp
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<functional>typedef std::function<void()> func; //定义函数类型,实现函数回调std::vector<func> callbacks;//存储回调函数
std::unordered_map<int, std::string> desc;void readMySQL()
{std::cout << "process[" << getpid() << "]执行访问数据库的任务" << std::endl;
}void executeUrl()
{std::cout << "process[" << getpid() << "]执行解析Url" << std::endl;
}void cal()
{std::cout << "process[" << getpid() << "]执行加密任务" << std::endl;
}void save()
{std::cout << "process[" << getpid() << "]执行数据持久化任务" << std::endl;
}//加载方法和对应的描述
void load()
{desc.insert(callbacks.size(), "readMySQL: 读取数据库");callbacks.push_back(readMySQL);desc.insert(callbacks.size(), "executeUrl: 进行url解析");callbacks.push_back(executeUrl);desc.insert(callbacks.size(), "cal: 进行加密计算");callbacks.push_back(cal);desc.insert(callbacks.size(), "save: 进行文件保存");callbacks.push_back(save);}void showHandler()
{for(const auto &iter : desc){std::cout << iter.first << "\t" << iter.second << std::endl;}
}int handlerSize()
{return callbacks.size();
}

运行结果:
在这里插入图片描述

三、命名管道

1.命名管道的原理

匿名管道是基于创建子进程的进程信息拷贝来通信的,只能用于有血缘关系的进程间通信;
而命名管道可以实现毫不相关的进程之间的通信;
在这里插入图片描述
不同的进程打开同一个文件,操作系统会检测文件路径,不会再将文件内容加载到内存中,而是将进程指向同一个文件结构体;
这就是命名管道,这是一种内存级文件,但是在磁盘中构建了一个文件名,在进程访问的时候访问同一路径下的文件名;
命名管道只是在磁盘中建立了一个符号,只是为了通信双方看到同一份资源,其数据都是内存级的,不会向磁盘中写入数据;

  • 实验:
    在这里插入图片描述
    mkfifo在指定路径下创建一个命名管道文件;
    在这里插入图片描述
    创建好了一个管道文件,p就是管道文件;
    然后让两个进程通过命名管道进行通信:
    在这里插入图片描述
    一个进程将打印的字符重定向到管道文件中,这时,这个进程就阻塞了,这是在等待其他进程从管道读取数据;
    在这里插入图片描述
    另一个进程从管道文件中读取数据并打印,写入的进程就可以推出阻塞了;
    在这里插入图片描述
    不断地写入和读取;

2.命名管道的使用

mkfifo:使用系统接口创建命名管道:
在这里插入图片描述
参数:
pathname:指定路径;
mode:指定管道文件的权限;
返回值:成功返回0;失败返回-1;

  • makefile
.PHONY:all
all:mutiServer clientmutiServer:mutiServer.cppg++ -o $@ $^ -std=c++11 
client:client.cppg++ -o $@ $^ -std=c++11 .PHONY:clean
claen:rm -f mutiServer client
  • comm.hpp
#ifndef _COMM_H_
#define _COMM_H_#include<iostream>
#include<string>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include "Log.hpp"using namespace std;#define MODE 0666 //管道文件的权限
#define SIZE 128string ipcPath = "./fifo.ipc";//管道文件的路径#endif
  • Log.hpp
#ifndef _LOG_H_
#define _LOG_H_#include<iostream>
#include<ctime>#define DeBug   0
#define Notice  1
#define Waring  2
#define Error   3const std::string msg[] = {"DeBug","Notice","Waring","Error"
};std::ostream &Log(std::string message, int level)
{std::cout << " | " << (unsigned)time(nullptr) << " | " << msg[level] << " | " << message;return std::cout;
}
#endif

这是打印日志的头文件;

  • mutiServer.cpp
#include "comm.hpp"int main()
{// 1.创建管道文件if (mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功", DeBug) << "step 1" << endl;// 2.正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY); // 服务端读取信息if (fd < 0){perror("open");exit(2);}Log("打开管道文件成功", DeBug) << "step 2" << endl;// 3.编写正常的通信代码char buffer[SIZE];while (true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer));if (s > 0){cout << '[' << getpid() << "] client say: " << buffer << endl;}else if (s == 0){cerr << '[' << getpid() << "] read end of file, client quit, server quit" << endl;break;}else{perror("read");break;}}// 4.关闭文件close(fd); // 关闭管道文件Log("关闭管道文件成功", DeBug) << "step 3" << endl;unlink(ipcPath.c_str()); // 删除管道文件Log("删除管道文件成功", DeBug) << "step 4" << endl;return 0;
}

在这里插入图片描述
这条代码执行后会创建命名管道文件;
在这里插入图片描述
ulink是删除文件的系统调用接口;
在这里插入图片描述
在关闭管道文件后直接删除;

  • client.cpp
#include "comm.hpp"int main()
{//客户端不需要创建管道了//1.打开管道文件int fd = open(ipcPath.c_str(), O_WRONLY);if(fd < 0){perror("open");exit(1);}//2.通信操作string buffer;while(true){cout << "Please Enter Command >";getline(cin, buffer);write(fd, buffer.c_str(), buffer.size());}//3.关闭管道文件close(fd);return 0;
}

客户端就不要创建管道文件,直接获取,然后打开,向管道中写信息;

  • 运行结果:
    在这里插入图片描述
    在服务端运行时,创建好管道文件,然后服务端进程阻塞,等待客户端写入命令;
    在这里插入图片描述
    当客户端进程创建后,双方都打开管道文件;
    在这里插入图片描述
    客户端发送指令,服务端接受指令;
    在这里插入图片描述
    当客户端退出进程后,服务端读到了0,也退出进程,关闭并删除管道文件;

如果在服务端创建多个子进程来处理客户端请求:

  • mutiServer.cpp
#include "comm.hpp"void getMessage(int fd)
{char buffer[SIZE];while (true){memset(buffer, '\0', sizeof(buffer));ssize_t s = read(fd, buffer, sizeof(buffer));if (s > 0){cout << '[' << getpid() << "] client say: " << buffer << endl;}else if (s == 0){cerr << '[' << getpid() << "] read end of file, client quit, server quit" << endl;break;}else{perror("read");break;}}}int main()
{// 1.创建管道文件if (mkfifo(ipcPath.c_str(), MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功", DeBug) << "step 1" << endl;// 2.正常的文件操作int fd = open(ipcPath.c_str(), O_RDONLY); // 服务端读取信息if (fd < 0){perror("open");exit(2);}Log("打开管道文件成功", DeBug) << "step 2" << endl;// 3.编写正常的通信代码int pnums = 4;for(int i = 0; i < pnums; i++){pid_t id = fork();if(id == 0){getMessage(fd);exit(1);}}for(int i = 0; i < pnums; i++){pid_t ret = waitpid(-1, nullptr, 0);}// 4.关闭文件close(fd); // 关闭管道文件Log("关闭管道文件成功", DeBug) << "step 3" << endl;unlink(ipcPath.c_str()); // 删除管道文件Log("删除管道文件成功", DeBug) << "step 4" << endl;return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 运行结果:
    在这里插入图片描述
    可以看到,每次执行客户端任务的子进程都是随机的,多进程竞争式获取数据;

注:.hpp文件与.h文件的区别
hpp,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用 project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库。

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

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

相关文章

Service not registered 异常导致手机重启分析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、Service not registered 异常导致手机重启二、Service not registered 解决方案 一、Service not registered 异常导致手机重启 1.重启 的部分Log如…

Web Worker API

Web Worker API Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务&#xff0c;使主线程&#xff08;通常是 UI 线程&#xff09;的运行不会被阻塞/放慢。 Web Worker概念与用法 Wor…

Maven引入本地jar包

maven做为一种强大的依赖管理工具&#xff0c;可以帮助我们更方便的管理项目中的依赖&#xff1b;而在使用过程中我们难免会有需要引入本地jar包的需求&#xff0c;这里踩过坑之后我分享俩种引入方式&#xff1b; 1.上传jar到本地maven仓库&#xff0c;再引入 使用此方法后可…

【DMA】认识 DMA 及其工作流程

DMA&#xff08;Direct Memory Access&#xff09;&#xff0c;字面意思“直接访问内存”&#xff0c;无需 CPU 干预直接读写内存。传统CPU读写数据时&#xff0c;需要先将要使用的数据保存到 RAM&#xff0c;等要用时再从RAM 加载。 目录 一、传统CPU存取数据 二、认识DMA …

SpringBoot + ajax 实现分页和增删查改

0目录 1.SpringBoot 2.SpringBoot分页&#xff1b;增删改查 1.SpringBoot分页 创建数据库和表 创建SpringBoot工程&#xff0c;引入springboot下的分页依赖 配置application.yml 实体类 Mapper接口 Mapper.xml Service接口 Service实现类 控制层 测试 加…

Kylin v10基于cephadm工具离线部署ceph分布式存储

1. 环境&#xff1a; ceph&#xff1a;octopus OS&#xff1a;Kylin-Server-V10_U1-Release-Build02-20210824-GFB-x86_64、CentOS Linux release 7.9.2009 2. ceph和cephadm 2.1 ceph简介 Ceph可用于向云平台提供对象存储、块设备服务和文件系统。所有Ceph存储集群部署都从…

Webpack开启本地服务器;HMR热模块替换;devServer配置;开发与生成环境的区分与配置

目录 1_开启本地服务器1.1_开启本地服务器原因1.2_webpack-dev-server 2_HMR热模块替换2.1_认识2.2_开启HMR2.3_框架的HMR 3_devServer配置3.1_host配置3.2_port、open、compress 4_开发与生成环境4.1_如何区分开发环境4.2_入口文件解析4.3_区分开发和生成环境配置 1_开启本地服…

无涯教程-Perl - 环境配置

在开始编写Perl程序之前&#xff0c;让我们了解如何设置我们的Perl环境。 您的系统更有可能安装了perl。只需尝试在$提示符下给出以下命令- $perl -v 如果您的计算机上安装了perl&#xff0c;那么您将收到以下消息: This is perl 5, version 16, subversion 2 (v5.16.2) b…

深度学习之用PyTorch实现线性回归

代码 # 调用库 import torch# 数据准备 x_data torch.Tensor([[1.0], [2.0], [3.0]]) # 训练集输入值 y_data torch.Tensor([[2.0], [4.0], [6.0]]) # 训练集输出值# 定义线性回归模型 class LinearModel(torch.nn.Module):def __init__(self):super(LinearModel, self)._…

中国农村程序员学习此【正则表达式进阶】发明cahtGPT,购买大平层,开上帕拉梅拉,迎娶白富美出任CEO走上人生巅峰

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 限制可能的用户名匹配空白字符匹配非空白字符指定匹配的上限和下限只指定匹配的下限指定匹配的确切数量检查全部或无正向先行断言和负向先行断言检查混合字符组使用捕获组重用模式使用捕获组搜索和替换删除…

关于电子接插件插拔耐久试验

500次的插拔次数怎么来的? - 知乎 EIA-364-09耐插拔测试方法 - 豆丁网 (docin.com) 连接器的电气性能测试要遵循什么样的国家标准&#xff1f;_插拔_绝缘_规定 (sohu.com) 连接器的插拔寿命标准 - 百度文库 (baidu.com) IEC 60512-1:2018 电气和电子设备用连接器. 试验和测量…

ResNet50卷积神经网络输出数据形参分析-笔记

ResNet50卷积神经网络输出数据形参分析-笔记 ResNet50包含多个模块&#xff0c;其中第2到第5个模块分别包含3、4、6、3个残差块 5049个卷积&#xff08;3463)*31和一个全连接层 分析结果为&#xff1a; 输入数据形状:[10, 3, 224, 224] 最后输出结果&#xff1a;linear_0 [10,…

【Spring】Spring中的设计模式

文章目录 责任链模式工厂模式适配器模式代理模式模版方法观察者模式构造器模式 责任链模式 Spring中的Aop的通知调用会使用责任链模式责任链模式介绍 角色&#xff1a;抽象处理者&#xff08;Handler&#xff09;具体处理者&#xff08;ConcreteHandler1&#xff09;客户类角…

Kafka

消息中间件的作用 异步解耦削峰填谷 MQ对比 RabbitMQ 优点&#xff1a;管理界面最好用&#xff1b;支持语言较多&#xff1b; 缺点&#xff1a;不支持扩展&#xff08;集群&#xff09; 主要流程&#xff1a;生产者发送消息给交换机&#xff0c;通过路由机制投递到对应的队…

Python入门【​编辑、组合、设计模式_工厂模式实现 、设计模式_单例模式实现、工厂和单例模式结合、异常是什么?异常的解决思路 】(十七)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

构造方法 与代理

构造方法 public class Son {String name;/*构造方法1.与类名相同2.没有返回值也没有void*//*作用&#xff1a;实例化初始值使用new关键字必须要有构造器*/public Son(){//无参构造方法this.name"sss";}//有参构造方法public Son(String name){//定义有参构造 就必…

【云存储】使用OSS快速搭建个人网盘教程(阿里云)

使用OSS快速搭建个人网盘 一、基础概要1. 主要的存储类型1.1 块存储1.2 文件存储1.3 对象存储 2. 对象存储OSS2.1 存储空间2.2 地域2.3 对象2.4 读写权限2.5 访问域名&#xff08;Endpoint&#xff09;2.6 访问密钥2.7 常用功能&#xff08;1&#xff09;创建存储空间&#xff…

WebGL Shader着色器GLSL语言

在2D绘图中的坐标系统&#xff0c;默认情况下是与窗口坐标系统相同&#xff0c;它以canvas的左上角为坐标原点&#xff0c;沿X轴向右为正值&#xff0c;沿Y轴向下为正值。其中canvas坐标的单位都是’px’。 WebGL使用的是正交右手坐标系&#xff0c;且每个方向都有可使用的值的…

MFC第二十六天 CRgn类简介与开发、封装CMemoryDC类并应用开发

文章目录 CRgn类简介与开发CRgn类简介CRgn类区域管理开发CRgn类区域管理与不规则形状的选取 封装CMemoryDC类并应用开发CMemoryDC.h封装CMemoryDC开发游戏透明动画CFlashDlg.hCFlashDlg.cpp 封装CMemoryDC开发游戏动画 附录四大窗口CDC派生类 CRgn类简介与开发 CRgn类简介 CR…

c高级:day3

作业: 1. 整理思维导图 2.判断家目录下,普通文件的个数和目录文件的个数 #!/bin/bash ######################################################################## # File Name: zy1.sh # Created Time: 2023年08月04日 星期五 19时13分08秒 ##############################…