linux之进程通信

目录

一、进程通信介绍

1.目的

2.发展 

3.进程通信是什么,怎么通信?

二、管道

1.介绍

2.匿名管道 

1.单向通信管道原理

 2.代码实现

3.管道特征

4.管道的四种情况

5.管道的应用场景 

使用管道实现一个简易版本的进程池

3.命名管道

        1.思考

        2.创建一个命名管道

         3.匿名管道与命名管道的区别

        4.命名管道的打开规则 

4.日志 

日志等级:

日式时间相关函数 

日志代码实现

5.总结

三、system V共享内存

1.原理

2.代码书写

1.相关函数

1.shmget 

返回值:

key:

size:

shmfig:

2.shmat

3.shmdt

4.shmctl

2.代码

3.共享内存特性


一、进程通信介绍

1.目的

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

2.发展 

  • 管道       
  1. 匿名管道pipe
  2. 命名管道 

         简单经典的通信使用的一种方式

  • System V IPC 
  1. System V 消息队列
  2. System V 共享内存
  3. System V 信号量 

        单独设计了一套接口,与文件无关。 只能本地使用,本地通信,在网络阶段,有很多替代方案。

  • POSIX IPC 
  1. 消息队列
  2. 共享内存
  3. 信号量
  4. 互斥量
  5. 条件变量
  6. 读写锁 

       网络和多线程时使用 

3.进程通信是什么,怎么通信?

1.是什么?

        两个或多个进程实现数据层面的交互。

        因为进程独立性的存在,进程通信的成本较高 -> 进程通信是有成本的

2.怎么办?

  • 进程间通信的本质:必须让不同的进程看到同一份"资源"
  • "资源"?:特定形式的内存空间
  • 这个"资源"谁提供?一般是操作系统
  • 为什么不是我们两个进程中的一个呢?假设一个进程提供,这个资源属于谁?这个进程独有,破环进程独立性。来自第三方空间
  • 我们进程访问这个空间,进行通信,本质就是访问操作系统!进程代表的就是用户,"资源"从创建,使用,释放  --- 出自系统调用接口! --- 1.从底层设计,从接口设计,都要由操作系统独立设计;2.一般操作系统,会有一个独立的通信模块 -- 隶属于文件系统  -- IPC通信模块定制标准  -- 进程间通信是有标准的  -- 就是上述的system V(本机内部) 和 POSIX(网络通信)

二、管道

1.介绍

        基于文件级别的进程通信方式

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

2.匿名管道 

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

1.单向通信管道原理

  

 2.代码实现

创建管道函数 pipe

#include <iostream>
#include <string>#include <cstdlib> //stdlib.h
#include <cstdio>
#include <cstring>#include <unistd.h>
#include <sys/types.h>using namespace std;#define N 2
#define NUM 1024//child
void Writer(const int& wfd)
{string str = "hello, i am child!";pid_t self = getpid();int number = 0;char buffer[NUM];while(true){//构建发送字符串buffer[0] = 0; //字符串清空,只是为了提醒阅读代码的人,把这个字符数组当作字符串了。snprintf(buffer, sizeof(buffer), "%s-%d-%d", str.c_str(), self, number++);//发送/写入给父进程write(wfd, buffer, strlen(buffer));sleep(1);}
}//father
void Reader(const int& rfd)
{char buffer[NUM];while(true){ssize_t n = read(rfd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0; // 0  ==  '\0'cout << "father get some message[" << getpid() << "]:" << buffer << endl;}            }
}int main()
{int pipefd[N] = {0}; //输出型参数int n = pipe(pipefd); //申请管道if(n < 0)return -1;// cout << "pipefd[0]:" << pipefd[0] << ",pipefd[1]" << pipefd[1] << endl;// father -> r ; child -> w;pid_t id = fork(); //创建子进程if(id < 0)return 2;if(id == 0){//childclose(pipefd[0]);//IPC codeWriter(pipefd[1]);close(pipefd[1]);exit(0);}//fatherclose(pipefd[1]);// IPC codeReader(pipefd[0]);close(pipefd[0]);return 0;
}

3.管道特征

  1. 具有血缘关系的进程会进行进程间通信
  2. 管道只能单向通信
  3. 父子进程是会进程协同的,同步和互斥的 --- 保护管道文件的数据安全
  4. 管道是面向字节流的。
  5. 管道是基于文件的,而文件的生命周期是跟随进程的

4.管道的四种情况

  1.  读写端正常,管道如果为空,读端就要阻塞
  2. 读写端正常,管道如果被写满,写端就要阻塞
  3. 读端正常读,写端关闭,读端就会读到0,表面读到了文件(pipe)结尾,不会被阻塞
  4. 读端关闭,写端正常写,操作系统就要杀掉正在写入的进程。如何杀掉--通过信号杀掉

5.管道的应用场景 

使用管道实现一个简易版本的进程池

原由:创建进程需要调用fork函数,而fork函数这个系统调用是有成本的!

"Task.hpp"

#pragma once#include <iostream>
#include <vector>using namespace std;//函数指针
typedef void (*task_t)();void task1()
{std::cout << "lol : 刷新野怪" << std::endl;
}void task2()
{std::cout << "lol : 刷新蓝条" << std::endl;
}void task3()
{std::cout << "lol : 刷新血量" << std::endl;
}void task4()
{std::cout << "lol : 更新系统" << std::endl;
}void LoadTasks(std::vector<task_t> *tasks)
{tasks->push_back(task1);tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}

"ProcessPool.cc"

#include "Task.hpp"#include <string>
#include <vector>#include <cstdlib>
#include <ctime>#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>const int ProcessNum = 10;std::vector<task_t> tasks;//先描述 -- 管道
struct channel 
{int _cmdfd;                  //发送任务的文件描述符pid_t _slaverid;             //子进程的pidstd::string _processname;    //子进程的名字,方便我们打印日志channel(int cmdfd, pid_t slaverid, const std::string &processname): _cmdfd(cmdfd), _slaverid(slaverid), _processname(processname){}
};void slaver()
{while(true){int cmdcode = 0;int n = read(0, &cmdcode, sizeof(int));if(n == sizeof(int)){std::cout << " child get a command: " << getpid() << " cmdcode: " << cmdcode << std::endl;if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();}if(!n)break;}
}void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfd;// 1.初始化 --- bugfor (size_t i = 0; i < ProcessNum; ++i){int pipefd[2];   //临时空间int n = pipe(pipefd); //if(n != 0){perror("pipe create file");return;}pid_t id = fork();if(id == 0){std::cout << "child " << getpid() << " close history fd :";for (auto &e : oldfd){std::cout << e << " ";close(e);}std::cout << std::endl;// childclose(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();std::cout << "process " << getpid() << ":quit!" << std::endl;exit(0);}//fatherclose(pipefd[0]);//开始添加channel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfd.push_back(pipefd[1]);sleep(1);}
}void Debug(const std::vector<channel> &channels)
{// testfor(const auto& e:channels){std::cout << "pid: " << getpid() << " " << e._cmdfd << " " << e._slaverid << " " << e._processname << std::endl;}
}void Menu()
{std::cout << "############################################" << std::endl;std::cout << "########1.刷新野怪  2.刷新蓝条 #############" << std::endl;std::cout << "########3.刷新血量  4.更新系统  0.退出######" << std::endl;std::cout << "############################################" << std::endl;
}void ctrlSlaver(const std::vector<channel> &channels)
{srand(time(0));int which = 0;//int cnt = 0;while (true){Menu();std::cout << "Please enter@:";int n;std::cin >> n;if(n <= 0 || n >= 5)break;// 1.选择任务// int cmdcode =  rand() % tasks.size();int cmdcode = n - 1;// 2.选择子进程// int processpos = rand() % channels.size(); //随机方法std::cout << "father say:"<< "cmdcode: " << cmdcode << " already sendto " << channels[which]._slaverid << " processname: " << channels[which]._processname << endl;// 3.发送任务write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));++which;which %= channels.size(); //轮转法sleep(1);}
}void QuitProcess(const std::vector<channel>& channels)
{for(const auto& e: channels){close(e._cmdfd);waitpid(e._slaverid, nullptr, 0);}//version1// for (int i = channels.size() - 1; i >= 0; --i)// {//     close(channels[i]._cmdfd);//     waitpid(channels[i]._slaverid, nullptr, 0);// }//sleep(5);// 有bug// for (const auto &e : channels)//     close(e._cmdfd);// sleep(5);// for (const auto &e : channels)//     waitpid(e._slaverid, nullptr, 0);// sleep(5);
}int main()
{//再组织//将对子进程结构的增删查改转化为对数据结构vector的增删查改std::vector<channel> channels;// 1.初始化LoadTasks(&tasks);InitProcessPool(&channels);//testDebug(channels);// 2.开始控制子进程ctrlSlaver(channels);// 3.清理收尾QuitProcess(channels);return 0;
}

 结果

注:

 这是我们原先创建子进程的代码,但是这份代码会造成一个问题,就是子进程会继承父进程对上一个子进程管道读端。

void InitProcessPool(std::vector<channel> *channels)
{// 1.初始化 --- bugfor (size_t i = 0; i < ProcessNum; ++i){int pipefd[2];   //临时空间int n = pipe(pipefd); //if(n != 0){perror("pipe create file");return;}pid_t id = fork();if(id == 0){// childclose(pipefd[1]);dup2(pipefd[0], 0);close(pipefd[0]);slaver();std::cout << "process " << getpid() << ":quit!" << std::endl;exit(0);}//fatherclose(pipefd[0]);//开始添加channel字段std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));}
}

 而解决办法是,把上一个写端记录下来,在创建子进程时,顺便把子进程的继承自父进程的写端给close掉。 

3.命名管道

        1.思考

        我们上面使用的匿名管道只能在有共同祖先/血缘相近的进程间使用,而我们想在不同进程间进行管道通信时,应该怎么做呢?

        我们可以使用FIFO文件在做这项工作,它经常被称作命名管道。

        2.创建一个命名管道

               1.命令行创建 

mkfifo filename

                2.程序中创建 

int mkfifo(const char *filename,mode_t mode)

         3.匿名管道与命名管道的区别

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

        4.命名管道的打开规则 

如果当前打开操作是为读而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO 

4.日志 

      此内容与管道无关,只是需在当前练习中打印日志,所以做一个笔记

日志包含:日志时间,日志等级,日志内容,文件的名称和行号

日志等级:

  • Info:常规消息
  • Warning:报警信息
  • Error:必要严重的问题,可能需要立即处理
  • Fatel:致命的
  • Debug:调试

日式时间相关函数 

time:打印时间戳

time_t time(time_t *t);

 当前时间戳传nullptr

gettimeofday:

int gettimeofday(struct timeval *tv, struct timezone *tz /*时区*/);

struct timezone *tz:时区,缺省为nullptr即可

localtime:

struct tm *localtime(const time_t *timep);

注意:这里年是从1900年开始的,所以要加上1900.月是从0开始的,所以要加1.

日志格式可变参数部分

 int vsnprintf(char *str, size_t size, const char *format, va_list ap);

日志代码实现

#pragma once#include <iostream>
#include <string>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 1024//日志等级
#define Info 0
#define Debug 1
#define Warning 3
#define Error 4
#define Fatel 5//打印方式
#define Screen 1
#define Onefile 2
#define Classfile 3#define Logfile "log.txt"class Log
{
public:Log(){PrintMethod = Screen;path = "./log/";}void Enable(int method){PrintMethod = method;}std::string levelToString(int level){switch(level){case Info:return "Info";case Debug:return "Debug";    case Warning:return "Warning";case Error:return "Error";case Fatel:return "Fatel";default:return "None";}}//日志函数// void logmessage(int level, const char* format, ...)// {//     time_t t = time(nullptr);//     struct tm *ctime = localtime(&t);//     char leftbuffer[SIZE];//     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),//             ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,//             ctime->tm_hour, ctime->tm_min, ctime->tm_sec);//     va_list s;//     va_start(s, format);//     char rightbuffer[SIZE];//     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);//     va_end(s);//     //格式:默认部分+自定义部分//     char logtxt[SIZE];//     snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//     //printf("%s", logtxt);//     printlog(level, logtxt);// }void printlog(int level, const std::string &logtxt){switch(PrintMethod){case Screen:std::cout << logtxt;break;case Onefile:PrintOneFile(Logfile, logtxt);break;case Classfile:PrintClassFile(level, logtxt);break;default:break;}}void PrintOneFile(const std::string &filename, const std::string &logtxt){std::string logname = path + filename;int fd = open(logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void PrintClassFile(int level, const std::string &logtxt){std::string filename = Logfile;filename += ".";filename += levelToString(level);PrintOneFile(filename, logtxt);}void operator()(int level, const char* format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);//格式:默认部分+自定义部分char logtxt[SIZE];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);//printf("%s", logtxt);printlog(level, logtxt);}private:int PrintMethod;std::string path;
};

5.总结

        总的来说,匿名管道和命名管道都是面向字节流的,会进行同步和互斥,生命周期随进程,使用时需要打开文件,单向通行。让不同进程看到同一份资源 -- 文件。

  

三、system V共享内存

1.原理

共享内存是在物理地址空间上申请的,通过页表挂接到不同进程程序地址空间的一种通信方式。

那么这块物理内存是进程申请的还是操作系统来申请的呢?

答案是操作系统,因为进程具有独立性,进程申请的资源归进程所有。

ipcs -m :查看所有的共享内存

共享内存的生命周期是跟随的内核的,用户不主动释放,共享内存会一直存在,除非内核关闭(用户释放)。

ipcrm -m shmid :删除shmid对应的共享内存

2.代码书写

1.相关函数

1.shmget 

申请一块共享内存

int shmget(key_t key, size_t size, int shmflg);
返回值:

        共享内存标识符

key:

1.key是一个数字,这个数字是几不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识

2.第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key,就可和第一个进程看到同一个共享内存!

3.对于一个已经创建好的共享内存,key在哪?key在共享内存的描述对象中!

4.第一次创建的时候,必须有一个key了,怎么有?

         ftok - convert a pathname(路径) and a project identifier(项目id) to a System V IPC key

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

 ftok是一套算法,将路径字符串和整形id进行了数值计算。

5.key -- 类似 -- 路径 -- 唯一性

size:

        创建共享内存的大小,单位是字节

shmfig:

        如何创建,获取 。。 

IPC_CREAT单独使用,如果你申请的共享内存不存在,就创建,存在,就获取并返回
IPC_CREAT | IPC_EXCL如果你申请的共享内存不存在,就创建,存在,就出错返回。确保我们如果申请成功了一个共享内存,这个共享内存一定是一个新的
IPC_EXCL 不单独使用

注:

key与shmid

 key:操作系统内标定唯一性。

 shmid:只在你的进程内,用来表示资源的唯一性。

2.shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);

将申请的共享内存挂接到进程的虚拟地址空间

3.shmdt
 int shmdt(const void *shmaddr);

 取消挂接到进程的虚拟地址空间的共享内存

4.shmctl
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

cmd:

IPC_STAT: 获取

IPC_RMID:删除

2.代码

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>#include "log.hpp"using namespace std;class comm
{
public:comm(){//log.Enable(Classfile);}//获取keykey_t Getkey(){key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){log(Fatel, "ftok error string: %s, ftok error code: %d", strerror(errno), errno);exit(1);}log(Info, "ftok success, key is : 0x%x", k);return k;}int GetShareMemHelper(int flag){int shmid = shmget(Getkey(), size, flag);if(shmid < 0){log(Fatel, "create share memory error string: %s, error code: %d", strerror(errno), errno);exit(1);}log(Info, "create share memory success, shmid: %d", shmid);return shmid;}int CreateShm(){return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);}int GetShm(){return GetShareMemHelper(IPC_CREAT);}private:Log log;const string pathname = "/home/shen";const int proj_id = 0x6666;//共享内存大小一般是4096的整数倍, 如果我们申请4097的话,操作系统实际给出4096*2的大小const int size = 4096;
};#endif

processa.cc

#include "comm.hpp"Log log;int main()
{comm co;int shmid = co.CreateShm();log(Debug, "create shm done");char *straddr = (char*)shmat(shmid, nullptr, 0);log(Debug, "attach shm done");while(true){cout << "client say#:" << straddr << endl; //直接访问共享内存sleep(1);}shmdt(straddr);log(Debug, "detach shm done");shmctl(shmid, IPC_RMID, nullptr);log(Debug, "delete shm done");return 0;
}

 processb.cc

#include "comm.hpp"Log log;
int main()
{comm co;int shmid = co.GetShm();log(Debug, "Get shm done");char *straddr = (char*)shmat(shmid, nullptr, 0);log(Debug, "attach shm done");while(true){cout << "Please Enter@ ";fgets(straddr, 4096, stdin);}shmdt(straddr);log(Debug, "detach shm done");return 0;
}

3.共享内存特性

  1. 共享内存没有同步互斥之类的保护机制
  2. 共享内存是所有的进程间通信中,速度是最快的! ---  原因:拷贝次数少
  3. 共享内存内部的数据,由用户自己维护!

 四、system V补充

1.消息队列

  • 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
  • 每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
  • 特性方面: IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核

 相关函数

int msgget(key_t key, int msgflg);

成功返回一个消息队列标识符,失败返回-1。key通过ftok获取,msgflg可用IPC_STAT和IPC_EXCL做参

          //                    起始地址        大小
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

ipcs -q:查询消息队列

ipcrm -q msgid :删除msgid这个消息队列

2.信号量

int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);

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

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

相关文章

使用JXLS+Excel模板制作灵活的excel导出

前期一直卡在模板的批注上&#xff0c;改了很多遍的模板批注最终才成功导入&#xff0c;记录下方便以后寻找。 话不多说直接上代码&#xff1a; Report package com.example.jxls.common;import java.io.IOException; import java.io.InputStream; import java.io.OutputStr…

使用 Meta Llama 3 构建人工智能的未来

使用 Meta Llama 3 构建人工智能的未来 现在提供 8B 和 70B 预训练和指令调整版本,以支持广泛的应用 使用 Meta AI 体验 Llama 3 我们已将 Llama 3 集成到我们的智能助手 Meta AI 中,它扩展了人们完成工作、创造和与 Meta AI 联系的方式。通过使用 Meta AI 进行编码任务和解…

C语言.字符函数与字符串函数

字符函数与字符串函数 1.字符分类函数2.字符转换函数3.[strlen](https://cplusplus.com/reference/cstring/strlen/?kwstrlen) 的使用和模拟实现4.[strcpy](https://legacy.cplusplus.com/reference/cstring/strcpy/?kwstrcpy) 的使用和模拟实现5.[strcat](https://legacy.cp…

信息系统及其技术发展

目录 一、信息系统基本概念 1、信息系统项目开发 2、信息系统项目管理 3、信息系统 Ⅰ、生命周期 Ⅱ、新基建 ①信息基础设施 ②融合基础设施 ③创新基础设施 Ⅲ、工业互联网 Ⅳ、车联网 ①体系框架 ②连接方式 4、习题 二、信息技术发展 1、SDN 2、5G 3、存储…

书生·浦语大模型第二期实战营(5)笔记

大模型部署简介 难点 大模型部署的方法 LMDeploy 实践 安装 studio-conda -t lmdeploy -o pytorch-2.1.2conda activate lmdeploypip install lmdeploy[all]0.3.0模型 ls /root/share/new_models/Shanghai_AI_Laboratory/ln -s /root/share/new_models/Shanghai_AI_Laborato…

只需几步,即可享有笔记小程序

本示例是一个简单的外卖查看店铺点菜的外卖微信小程序&#xff0c;小程序后端服务使用了MemFire Cloud&#xff0c;其中使用到的MemFire Cloud功能包括&#xff1a; 其中使用到的MemFire Cloud功能包括&#xff1a; 云数据库&#xff1a;存储外卖微信小程序所有数据表的信息。…

【linux】软件工具安装 + vim 和 gcc 使用(上)

目录 1. linux 安装软件途径 2. rzsz 命令 3. vim 和 gcc 使用 a. vim的基本概念 b. 命令模式下的指令 c. 底行模式下的指令 1. linux 安装软件途径 源代码安装rpm安装 -- linux安装包yum安装&#xff08;最好&#xff0c;可以解决安装源&#xff0c;安装版本&#xff0…

0418WeCross搭建 + Caliper测试TPS

1. 基本信息 虚拟机名称&#xff1a;Pure-Ununtu18.04 WeCross位置&#xff1a;/root/wecross-demo 2. 搭建并启动WeCross 参考官方指导文档 https://wecross.readthedocs.io/zh-cn/v1.2.0/docs/tutorial/demo/demo.html 访问WeCross网页管理平台 http://localhost:8250/s/…

【Java框架】Spring框架(六)——Spring中的Bean的作用域

目录 Bean的作用域1.singleton(默认)代码示例 2.prototype代码示例 3.request代码示例 4.session代码示例 5.application代码示例 websocket Bean的作用域 Spring支持6个作用域&#xff1a;singleton、prototype、request、session、application、websocket 1.singleton(默认…

python基础知识二(标识符和关键字、输出、输入)

目录 标识符和关键字&#xff1a; 什么是标识符&#xff1f; 1. 标识符 2. 标识符的命名规则 什么是关键字&#xff1f; 1. 关键字 2. 关键字的分类 标识符和关键字的区别&#xff1a; ​​​输出&#xff1a; 1. 普通的输出 2. 格式化输出 格式化操作的目的&#…

Pycharm破解流程

1.下载pycharm 网上很多&#xff0c;随便找一个&#xff0c;懒得找的话&#xff0c;或者去我传上去的资源pycharm部分直接取 2.下载文件 文件部分&#xff0c;我放在pycharm文件里面一起 打开下载好的激活包 3.执行脚本 先执行unisntall-all-users.vbs,直接双击打开&#xff0c…

Springboot AOP接口防刷、防重复提交

Java利用注解、Redis做防重复提交和限流 使用场景 用户网络慢&#xff0c;电脑卡&#xff0c;一直点击保存&#xff0c;修改按钮无返回信息&#xff0c;会导致多个请求去保存、修改 开放接口、或加密接口频繁访问&#xff0c;会导致程序压力大&#xff0c;可能被他人写脚本一直…

Godot3D学习笔记1——界面布局简介

创建完成项目之后可以看到如下界面&#xff1a; Godot引擎也是场景式编程&#xff0c;这里的一个场景相当于一个关卡。 这里我们点击左侧“3D场景”按钮创建一个3D场景&#xff0c;现在在中间的画面中会出现一个球。在左侧节点视图中选中“Node3D”&#xff0c;右键创建子节点…

企业车辆管理系统平台是做什么的?

企业车辆管理系统平台是一种综合性的管理系统&#xff0c;它主要集车辆信息管理、车辆调度、车辆维修、油耗管理、驾驶员管理以及报表分析等多种功能于一体。通过这个平台&#xff0c;企业可以实现对车辆的全面管理&#xff0c;优化车辆使用效率&#xff0c;降低运营成本&#…

在Windows 10中禁用Windows错误报告的4种方法,总有一种适合你

序言 在本文中&#xff0c;我们的主题是如何在Windows 10中禁用Windows错误报告。你知道什么是Windows错误报告吗&#xff1f;事实上&#xff0c;Windows错误报告有助于从用户的计算机收集有关硬件和软件问题的信息&#xff0c;并将这些信息报告给Microsoft。 它将检查任何可…

基于postCSS手写postcss-px-to-vewiport插件实现移动端适配

&#x1f31f;前言 目前前端实现移动端适配方案千千万&#xff0c;眼花缭乱各有有缺&#xff0c;但目前来说postcss-px-to-vewiport是一种非常合适的实现方案&#xff0c;postcss-px-to-vewiport是一个基于postCss开发的插件&#xff0c;其原理就是将项目中的px单位转换为vw(视…

day07 51单片机-18B20温度检测

18B20温度检测 1.1 需求描述 本案例讲解如何从18B20传感器获取温度信息并显示在LCD上。 1.2 硬件设计 1.2.1 硬件原理图 1.2.3 18B20工作原理 可以看到18B20有两根引脚负责供电,一根引脚负责数据交换。18B20就是通过数据线和单片机进行数据交换的。 1)18B20工作时序 2)…

node.js-模块化

定义&#xff1a;CommonJS模块是为Node.js打包Javascript代码的原始方式。Node.js还支持浏览器和其他Javascript运行时使用的ECMAScript模块标准。 在Node.js中&#xff0c;每个文件都被视为一个单独的模块。 概念&#xff1a;项目是由很多个模块文件组成的 好处&#xff1a…

找不到msvcp140dll,无法继续执行代码的详细解决方法

在我们日常使用计算机进行各类工作任务的过程中&#xff0c;时常会遭遇一些突发的技术问题。比如&#xff0c;有时在运行某个重要程序或应用软件时&#xff0c;系统会突然弹出一个令人困扰的错误提示&#xff1a;“电脑提示找不到msvcp140.dll文件&#xff0c;因此无法继续执行…

AI预测福彩3D第9套算法实战化测试第1弹2024年4月22日第1次测试

经过前面多套算法的测试&#xff0c;总结了一些规律&#xff0c;对模型优化了一些参数&#xff0c;比如第8套算法的测试&#xff0c;7码的命中率由最开始的20%提高到了50%。虽然命中率有了很大的提高&#xff0c;但是由于咱们之前的算法只是为了测试和记录&#xff0c;提供的方…