【Linux系统编程二十】:(进程通信2)--命名管道/共享内存

【Linux系统编程二十】:命名管道/共享内存

  • 一.命名管道
    • 1.创建管道
    • 2.打开管道
    • 3.进行通信(server/client)
  • 二.共享内存
    • 1.实现原理
    • 2.申请内存
    • 3.挂接
    • 4.通信
    • 5.去关联
    • 6.释放共享内存
    • 7.特性:

一.命名管道

上一篇介绍的一个管道是没有名字的
因为你打开那个文件的时候,并没有告诉我们文件名。直接以读的方式方式打开复制子进程,各自拿一个读写单就可以通信了。
好,这有问题吗?没有问题
正是因为它没有名字,那么所以他必须得让我们对应的父子进程继承这才可以看到同一份资源。它采用的是让父子继承的方案看到的。
它只能是具有血缘关系的进程之间进行进程间通信。
那如果没有血缘关系的进程之间想要通信该怎么办呢?这就可以使用我们的命名管道!
如果两个不同的进程。打开同一个文件的时候。在内核中。操作系统会打开几个文件呢?
在这里插入图片描述

不同的进程打开同一个文件时,在操作系统层面上它还是这种结构。
所以呢两个进程可能有不同的读写文件对象,但是它文件缓冲区还是同一个。还记得进程间通信有个前提吗?这个前提就是应该让我们的不同进程看到。同一份我们对应的资源。
所以这个概念呢,其实和我们匿名管道的原理是一模一样的。

两个不同的进程打开同一个文件时。就要想到这样的问题:

问题1:你怎么知道你们两个打开的是同一个文件?
我们两个只要看到同一个文件名,那么此时这两个进程就可以打开同一个文件了还有个隐含条件叫做同路径下的我们对应的文件。路径加文件名,我们就能保证打开的是同一个文件。

问题2:我们为什么要打开同一个文件啊?
进程通信必须保证在同一个文件里通信啊,不然怎么通信呢,进程间通信的前提是先让不同的进程看到同一份资源。打开同一个文件不就是看到同一份资源了吗。

这种管道文件它是通过使用路径加文件名的方案,让不同的进程看到同一份文件资源,进而实现不同进程之间通信的。所以它有文件,有路径名,有文件名,所以这种通信我们叫做命名管道。

命名管道唯一和我们匿名管道区别的地方是在哪里呢?
就是我们这里所谓的匿名通信的呢,就是可以用来进行我们不相关进程之间进行进行通信的那它是怎么做到不相关进程之间通信的啊,前提条件是得先让不同进程看到同一份资源。它怎么看到的?
是通过文件名叫路径的方式,让我们看到同一份资源的。

1.创建管道

你要用管道文件进行通信,前提条件你是不是得先把对应要通信的资源,你先准备好,资源你都准备不好,那你玩什么呢?对不对?资源都没有准备好,你就不要玩了。

首先得创建一个管道文件创建,怎么创建呢?

mkfifo(pathname,mode)

第一个参数我们要创建这个管道文件
第二个参数呢就是我们打开这个管道文件对应的一个那么权限。

unlink(path)

unlink是用来删除管道文件的。

我们现在已经能创建一个管道了。下面要做的当然就是不同进程直接通信了。
通信之前你们两个进程决定一下一个从管道里读,一个从管道里写。命名管道也是管道,只支持单向通信的。未来我们俩要通信的话,是不是正常的进行我们管道的读写就可以了。

2.打开管道

现在我们剩下的就是打开管道文件,剩下读写。
那么这打开管道文件怎么打开呢?,linux下一切皆文件,它在设计的时候已经把接口设计成了通用接口。
同学们所以你要打开这个管道文件,怎么打开呢按照文件操作的一系列即可open.打开呀然后呢read write close,这样去读写就可以了,就这么简单。

后我们通信时,客户端和服务双方他们都直接那么叫做通过打开文件的方式那么来进行通信就可以了。

3.进行通信(server/client)

服务端server:



#include "comm.hpp"
// 服务端创建信道,客户端只需要发送信息即可
// 使用命名管道,就像使用文件一样要

using namespace std;

// 管理管道文件
int main()
{
    int n = mkfifo(FIFO_FILE, MODE);
    if (n == -1) // 创建失败
    {
       
        perror("mkfifo");
        exit(FIFO_CREATE_ERR); // 创建失败就退出
    }

    // 使用管道文件--如何使用呢?就跟文件操作一样使用,打开,写入关闭
    int fd = open(FIFO_FILE, O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }
    while (1)
    {
        // 读取到一个数组里
        char buffer[1024] = {0};

        int x = read(fd, buffer, sizeof(buffer));
        if (x > 0) // 说明读取成功
        {
            buffer[x] = 0;
            //读取完后,就将客户端发送
            cout << "client say@ " << buffer << endl;
        }
        else if (x == 0) // 说明读取结束,读到末尾
        {
            break;
        }
        else
            break;
    }
    close(fd);

    // 最后不用了,就删除管道文件
    int m = unlink(FIFO_FILE);
    if (m == -1)
    {
        perror("unlink");
        exit(FIFO_DELETE_ERR);
    }

    return 0;
}

客户端client:

#include "comm.hpp"
using namespace std;
//客户端这里只需要写入信息

int main()
{
    int fd=open(FIFO_FILE,O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }
    //打开成功后就可以进行通信
    cout<<"client opne file done"<<endl;
    string line;
    while(1)
    {
        cout<<"Please Enter@: "<<endl;
        getline(cin,line);
        write(fd,line.c_str(),line.size());
    }
    close(fd);

    return 0;
}


共同访问区:

#pragma once

#include <iostream>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <cstdlib>
#define FIFO_FILE "./myfifo" // 在当前目录下创建命名管道myfifo
#define MODE 0664

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_OPEN_ERR,
    FIFO_DELETE_ERR
};
//可以这样封装创建管道和销毁管道,定义一个Init对象即可。
class Init
{
    Init()
    {
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1) // 创建失败
        {

            perror("mkfifo");
            exit(FIFO_CREATE_ERR); // 创建失败就退出
        }
    }
    ~Init()
    {
        // 最后不用了,就删除管道文件
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

以上就是基于文件部分的通信,包括匿名管道和命名管道。

二.共享内存

1.实现原理

在介绍之前再强调一遍:通信的前提:让不同的进程先看到同一份资源。
sister v的共享内存呢,它是一个那么单独设计的内核模块,用来进行进程间通信的。

让进程看到同一份资源,什么资源不是资源,谁提供资源都行。
上面讲的那些都叫做基于文件的。
现在讲的这个共享内存,它是专门由操作系统去给我们提供的一种通信方案。
假设我们有a b两个进程呢,那么不管是哪两个进程呢,那么我们以a进程为例,它自己有自己的代码区有自己的数据区,那么就可以有自己的页表,然后经过页表呢将对地址空间当中的内容进行映射到物理地址当中。(当然前提肯定是你这个进程的代码数据各种全全部都有了)。
然后映射过来,此时我们的进程就能看到,就能访问了。

本质原理:
①这里第一步就先在这个物理内存先创建出来一块空间。
②第二步再把这个共享空空间再经过页表啊映射到我们对应的一个进程的共享区当中。
在这里插入图片描述

那么映射到我们的共享区当中呢,然后给应用层返回一个对应的连续的内存空间的起始地址啊,所以这里呢我们就有一个起始的虚拟地址。

那么首先这一点在操作系统层面上,他想做是完全可以做到的那么这时我们就完成了一个叫做共享内存的创建。

这个共享内存是建立在我们进程地址空间的共享区的,那么进程这个时候就能直接访问了。既然操作系统能把你对应的申请的这块内存给你这个进程a去进行建立映射。要通信其他进程,其他进程你也可以来进行映射啊,经过页表映射到你自己的地址空间里面。
然后呢建立好之后,给这应用层也返回虚拟地址啊,所以从此往后呢,我们两个进程就可以通过各自的页表访问同一块物理内存了。

那么此时这就叫做先让不同的进程看到同样的一份资源。这个原理就叫做共享内存。

这要做的第一步无非就是要先申请内存啊,二步就是要把申请好的这块内存分别挂接到我们两个进程它的地址空间。然后返回我们对应的首地址啊。返回了我们对应的地址之后呢,就有了一段我们对应的起始对应的内存了。
所以我们未来访问时可以经过列表访问这个内存,一个进程向我们的共享内存里进行写,如果你愿意,你也可以写啊,共享内存是双向通信的,但一般我们都是一个写一个读。

【问题1】系统调用
进城需要通信。操作系统来帮我们来做这个建立通信的信道。
也就是说,我们的进程呢,说我要通信,那么操作系统说行,那我帮你执行。
进程自己不能在系统里直接访问内核数据结构,它也不能直接搞它的页表,然后建立映射。需要操作系统来搞。如果进程自己做了这个东西,那么这个内存就属于你自己的。我们想要的是一个共享的空间。
所以我们的需求是要让操作系统去执行,它解决的是我们为什么要要建立共享内存,它解决的是我们如何建立的问题。
所以我们必须得由操作系统来给我们的进程操作,这样就一定要存在一个东西叫做系统调用。
所以共享内存这玩意儿呢,它将来的建立,挂接,最终是由操作系统必须得给我提供一批系统调用。然后我进程呢调你的系统调用,我什么时候调是由我进程说了算。但一旦我调系统调用了,就跟我的进程没关系,是由操作系统帮我去做的,创建出来的共享内存,那么其中就不属于我这个进程私有的。

【问题2】共享内存在内核方面的理解
所以这个共享内存是要由操作系统管理的,当很多进程都要进行通信,操作系统就要创建多个共享内存,操作系统就需要对这些共享内存进行管理:
在操作系统当中,你对应的要管理这个共享内存。要管理这个管理层,必须得把管理层先得描述起来。比如说啊我们就得有一个struct结构体描述你申请的共享内存多大呀,谁申请的呀?那么当前有多少个进程和我是关联的呀,那么我们当前进程呃共享内存啊我们使用了多少了等它的诸多属性。我们是不是一定要有内核结构体来描述共享内存。再把所有的这些被共享内存的结构体,用链表了、数组了,或者你各种数据结构管理起来。
所以在操作系统层面上管理共享内存的行为就变成了对某种数据结构的增删查改。

【问题3】删除共享内存
共享内存申好了,那么如何删除共享内存呢???
那么你肯定是要先去关联啊,不难理解,就是你曾经不是把共享内存挂接到你地址空间了吗?那要释放共享内存,那么首先得做的是让当前进程和共享内存去掉关联,也就是把页表曾经映射的那些条目直接去掉。那么去关联之后,然后才能进行释放共享内存。
可是最最尴尬的是,我们的共享内存,它怎么知道有几个进程跟他是关联的呢?
好,共享内存,它怎么知道有几个进行关联呢?所以它在内部是存在着引用计数,计算有多少个进程和它关联起来的。那么删除共享内存时,去关联后,并不能直接删除共享内存,还需要看引用计数是否为0,只有当最后一个进程去关联的时候,减到零的时候才可以真正的删除共享内存。

2.申请内存

首先得能够在系统里创建一个共享内存。在linux系统里创建一个共享内存,我们所要采用的接口叫shmget(),它的作用呢是申请一个系统的共享内存啊
在这里插入图片描述
shmget系统调用三个参数分别代表的意义:

1.第一个参数:key
不管我将来是想创建共享内存,还是想获取共享内存。我们两个j进程必须拿到同一个key。
因为只有拿到了同一个king,我们此时才能证明我们两个访问的是同一个共享内存。因为系统里可同时会存在非常多的共享内存哦,那有很多的话,你怎么保证你的就是通信双方的进程看到的是同一个共享内存呢?
所以key是让我们的不同进程进行唯一性标识。我们呢才能保证我们在共享内存里判断一个共享内存存在。
因为我要创建过共享内存,我也得有个key。这样在系统里去创建共享内存的时候,你存在还是不存在,我就可以拿着我的k和系统里进行对比就可以了。那么正是因为我们有了key,所以啊第一个进程可以通过我们对应的k创建共享内存。
那么第二个进程他们只要拿着同一个key值获得已经创建的共享内存。
所以两个进程只要拿着同一个key就可以看到同一个叫做我们的共享内存了。
第一个进程通过king创建。第二个他只要拿着同样的key,他就可以和第一个进程看到同共享内存了。
因为我们这个king是唯一的,而且呢我们两个呢那么就可以通过key来标定为一个共享内存了。在这里插入图片描述
通过ftok(pathname,processid)接口就可以创建一个key值。
【问题1】为什么要自己创建key,而不是让操作系统创建呢?
如果操作系统给你形成了一个key,你怎么把这个key交给另一个和你通信的进程呢?
2.第二个参数
共享内存的大小
3.第三个参数
设置共享内存创建时,设置一些配置和权限
IPC_CREAT:创建共享内存,如果不存在,就创建。如果存在就返回存在的共享内存。
IPC_CREAT|IPC_EXCL.创建共享,如果不存在就创建,如果存在就报错。
4.返回值
shmid返回的是用来标识唯一的共享内存标识符。

key是给操作系统用的,让操作系统去拿着它去申请共享内存,这里所谓的shmid最后是不是就是它的返回值啊?它的返回值叫做共享内存标识符,它只在你的进程内。
一个是给我们对应的操作系统内标定唯一性的。
一个是让我们在自己的编码当中用这个共享内存,用它来进行控制共享内存的。所以只有创建共享内存是用这个key从此往后再也不用这个key了。创建完后,再对这个共享内存操作都是通过shmdi来找到共享内存操作的。

3.挂接

共享内存申请完了,第二步骤就是我们要把这个外部内存和我的进程挂接起来。那么怎么挂接呢?首先肯定不能你自己挂,肯定是由操作性的给你提供接口的,所以我们叫做shmat。
在这里插入图片描述
返回值这里的代表的含义就是你想让你当前对应的共享内存挂接到地址空间的什么位置。共享内存最终挂接的时候是在我们对应的共享区,但具体你想让它挂载到我们共享区的什么位置?void*就是我们最终得到的那个虚拟的起始地址。第三个参数我们也是默认啊,默认的话就按照我们的这个呃叫做我们共享内存它默认的权限来就行了。所以这里设为零就可以。
第二个参数设为nullptr,它给我们得到的是一块连续堆空间的起始地址,说白了就是一块虚拟地址。

4.通信

将共享内存挂接到不同进程的地址空间后,这些进程之间就可以进行通信了。
公共资源comm.hpp。它们之间的通信非常简单直接,就是各种朝自己的内存里输出和输入即可。
在这里插入图片描述

#include <iostream>

using namespace std;
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
//key值的获取根据系统调用ftok(pathname,pro_id)

const string pathname="/home/tao";
const int pro_id=0x6666;
int size= 4096;

//这是用户自己决定的
//Log log;
key_t GetKey()
{
    key_t k=ftok(pathname.c_str(),pro_id);
    if(k<0) 
    {
       // log(Fatal,"ftok error:%s",strerror(errno));//致命错误
        exit(1);
    }
    //普通日志:成功
    //log(Info,"ftok success key is %d",k);
    return k;
}

int GetMEm(int flag=IPC_CREAT|IPC_EXCL|0666)//默认是创建,权限是0666
{
    int key=GetKey();
    int shmid=shmget(key,size,flag);//确保每次创建的都是新的共享内存
    if(shmid<0)
    {
     //   log(Fatal,"Creat shmget error:%s",strerror(errno));
        exit(2);
    }
    //log(Info,"Creat shmget success shmid is%d",shmid);
    return shmid;
}

//进程a是创建共享内存的,而进程b是要获取共享内存的,我们可以通过传不同的参数
//调用同一个GetMem来完成,因为获取共享内存,我们就不加上IPC_EXCL就可以实现
//如果共享内存以及存在,就直接返回该共享内存。

int CreatSM()
{
    return GetMEm();//默认是创建
}
int GetSM()
{
    return GetMEm(IPC_CREAT);//只传入IPC_CREAT就是获取原先存在的共享内存
}




进程a.cc

#include "comm.hpp"

//两个进程想通过共享内存通信
//1.首先进程a根据key值让操作系统申请共享内存
//通过唯一的key值来让操作系统申请一块唯一的共享内存,所以第一步首先需要约定以一个key值
//本质就是操作系统会拿这个key值去构造一个结构体对象,该结构体对象是用来管理共享内存的
//2.进程a的地址空间需要挂接到共享内存
//3.不用了就去挂接
//4.共享内存是没有同步互斥的,我们可以利用管道的方式,来进行同步,在进行通信之前,创建
//命名管道,并将命名管道打开。开始接受信息,一旦接受到一个信息才可以进行输出,如果没有收到就在等待

int main()
{
   //1.创建共享内存
   int shmid=CreatSM();
   
   //2.挂接到共享内存--挂接到地址空间的哪里呢?挂接到shmadder地方
   char*shmaddr=(char*)shmat(shmid,nullptr,0);
   //3.通信
  while(true)
  {
     cout<<"client say@ "<<shmaddr<<endl;
    
  }

   //4.不用了去关联
   shmdt(shmaddr);

   //5.最后如果没有进程要用共享内存了,共享内存是不会自动销毁的,需要用户手动销毁
   shmctl(shmid,IPC_RMID,nullptr);
}

进程b.cc

#include "comm.hpp"

//进程b如果想和进行a通过共享内存通信,需要用自己的key来和共享内存进行匹配
//因为共享内存被进程a创建完了,进程b只需匹配
//进程b匹配完也需要将自己的地址空间挂接到共享内存上。

//实现同步---在通信之前创建命名管道,并打开命名管道,往命名管道里输入固定信息,才能往
//共享内存里输入。
int main()
{
   //1.获取到共享内存
   int shmid=GetSM();

   //2.挂接到地址空间
   char* shmaddr=(char*)shmat(shmid,nullptr,0);
   
   //3.开始通信
  while(true)
  {
    cout<<"Please enter:";
     cin>>shmaddr;
  }

   //4.不用了,去关联
   shmdt(shmaddr);
}

5.去关联

如果想去掉你自己对某一个共享内存的关联,那么你只需要使用shmdt
在这里插入图片描述
那么这个参数什么意思呢?这个参数就是曾经这个shmat所得到的虚拟地址的起始地址。

6.释放共享内存

自己不主动的把共享内存给我关掉,操作系统我也不给你关。你不主动关闭,那么共享内存会一直存在。所以共享内存一旦创建,还要放在那儿。
你用户你想进行几个进程通信,你就随时挂机,你想用你就创建啊,你想用你就挂机,不用你把它直接去关联,除非你主动关闭好,否则固态内存一直存在。生命周期随内核啊
在这里插入图片描述
shamctl可以用来删除共享内存,也可以用来获取共享内存的属性。它这个接口第一个参数啊,就是我们共享内存的id,也就是共享内存标识符。第二个参数用来删除共享内存的。第三个参数就是用来获取共享内存的各种属性的,各种属性都存在struct shmid_ds这个结构体对象里。

7.特性:

共享内存这块,我们发现它没有同步机制,需要我们自己设置一些同步加锁来保护它,它是通信方式中最快的一种,因为通信的过程中,拷贝次数很少。
在这里插入图片描述

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

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

相关文章

在Python中调用imageJ开发

文章目录 一、在ImageJ中进行Python开发二、在Python中调用imageJ开发2.1、简介2.2、环境配置2.3、测试一2.4、测试二 Python imageJ 解决方案&#xff0c;采坑记录 一、在ImageJ中进行Python开发 原生ImageJ仅支持JS脚本&#xff08;JAVAScript&#xff09;&#xff0c;而Im…

蓝桥杯物联网竞赛_STM32L071_2_继电器控制

Stm32l071原理图&#xff1a; PA11与PA12连接着UNL2803 ULN2803是一种集成电路芯片&#xff0c;通常被用作高电压和高电流负载的驱动器。 ULN2803是一个达林顿阵列&#xff0c;当输入引脚&#xff08;IN1至IN8&#xff09;被连接到正电源时&#xff0c;相应的输出引脚&#xff…

大数据-计算框架选型与对比

计算框架选型与对比 一、大数据平台二、计算框架分类1.批处理架构2.实时流处理架构3.流批一体处理架构 三、计算框架关键指标1.处理模式2.可伸缩性3.消息传递3.1 至少一次&#xff08;at least once&#xff09;3.2 至多一次&#xff08;ai most once&#xff09;3.3 恰好一次&…

Redis报错:JedisConnectionException: Could not get a resource from the pool

1、问题描述&#xff1a; redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool 2、简要分析&#xff1a; redis.clients.util.Pool.getResource会从JedisPool实例池中返回一个可用的redis连接。分析源码可知JedisPool 继承了 r…

【git】使用ssh

前言 git之前一直使用https&#xff0c;因为很方便随时随地都可以用。最近把代码托管到GitHub&#xff0c;使用https就使用不了。后面听同事说GitHub使用ssh是没问题的&#xff0c;就想着尝试一下。 git ssh配置 设置用户名和邮箱 git config --global use.name username g…

FFmpeg常用命令讲解及实战二

文章目录 前言一、ffmpeg 常用命令1、ffmpeg 的封装转换2、ffmpeg 的编转码3、ffmpeg 的基本编转码原理 二、ffprobe 常用参数1、show_format2、show_frames3、show_streams4、print_format5、select_streams 三、ffplay 的常用命令1、ffplay 常用参数2、ffplay 高级参数3、ffp…

教你看现货黄金实时报价

现货黄金投资市场上的交易软件众多&#xff0c;很多人不知道选择什么软件好&#xff0c;但选择主流软件MT4&#xff0c;基本就可以满足投资者不同的需求。本文为大家讲讲&#xff0c;为什么有那么多的投资者&#xff0c;都选择通过MT4获取实时的行情报价。 现货黄金市场波动激烈…

什么是网络爬虫技术?它的重要用途有哪些?

网络爬虫&#xff08;Web Crawler&#xff09;是一种自动化的网页浏览程序&#xff0c;能够根据一定的规则和算法&#xff0c;从互联网上抓取和收集数据。网络爬虫技术是随着互联网的发展而逐渐成熟的一种技术&#xff0c;它在搜索引擎、数据挖掘、信息处理等领域发挥着越来越重…

【MySQL】子查询

文章目录 子查询IN运算符子查询 VS 连接ALL关键字ANY关键字相关子查询 !EXISTS运算符select子句中的子查询from子句中的子查询 子查询 获取价格大于id为3的货物的商品 用到了内查询&#xff0c;获取id为3的商品的单价&#xff0c;把结构传给外查询 在where子句中编写子查询&am…

导数、方向导数、梯度方向、梯度

导数&#xff1a;自变量改变一定量时&#xff08;大于或小于0&#xff09;&#xff0c;因变量改变多少 方向导数&#xff1a;限定在某一个方向上&#xff0c;自变量改变一定量时&#xff08;大于0&#xff09;&#xff0c;因变量改变多少 梯度方向&#xff1a;方向导数最大的…

Java实现王者荣耀小游戏

主要功能 键盘W,A,S,D键&#xff1a;控制玩家上下左右移动。按钮一&#xff1a;控制英雄发射一个矩形攻击红方小兵。按钮控制英雄发射魅惑技能&#xff0c;伤害小兵并让小兵停止移动。技能三&#xff1a;攻击多个敌人并让小兵停止移动。普攻&#xff1a;对小兵造成基础伤害。小…

2023年最新PyCharm环境搭建教程(含Python下载安装)

文章目录 写在前面PythonPython简介Python生态圈Python下载安装 PyCharmPyCharm简介PyCharm下载安装PyCharm环境搭建 写在后面 写在前面 最近博主收到了好多小伙伴的吐槽称不会下载安装python&#xff0c;博主听到后非常的扎心&#xff0c;经过博主几天的熬夜加班&#xff0c;…

网络安全如何自学?

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

Nginx环境搭建:安装与卸载

目录 一、卸载 二、安装 注&#xff1a;如果直接使用yum安装nginx&#xff0c;则默认安装路径为&#xff1a;/usr/share/nginx/ 下面这种方式我们是指定了安装目录 一、卸载 因为我之前的虚拟机上面已经有了nginx服务&#xff0c;所以这里可以先介绍一下nginx的卸载方法&a…

靠这份求职指南找工作,稳了!

大家好&#xff0c;我是鱼皮。为了帮助朋友们更好的准备秋招&#xff0c;我们精心汇总整理了 编程导航星球 内鱼友反馈的 200 多个高频求职问题和 150 多篇面经、以及最新秋招企业投递信息表&#xff0c;解答大家的求职困惑。 一、最新秋招投递信息表 目前已汇总整理了 600 多家…

佳易王羽毛球馆计时计费软件灯控系统安装教程

佳易王羽毛球馆计时计费软件灯控系统安装教程 佳易王羽毛球馆计时计费软件&#xff0c;点击开始计时的时候&#xff0c;自动打开灯&#xff0c;结账后自动关闭灯。 因为场馆每一场地的灯功率都很大&#xff0c;需要加装交流接触器。这个由专业电工施工。 1、计时计费功能 &…

【iOS】数据持久化(一)之Plist文件、Preference(NSUserDefaults类)

目录 什么是Plist文件&#xff1f;plist可以存储哪些数据类型plist文件数据的读取与存储 Perference&#xff08;NSUserDefaults&#xff09;使用方法registerDefaults: 方法的使用 什么是Plist文件&#xff1f; Plist文件&#xff08;属性列表&#xff09;是将某些特定的类&a…

MG-HSF

作者未提供代码

“下一代云”白皮书发布:PaaS成为核心增长动力,腾讯云市场份额第二

“市场需求进一步向PaaS和SaaS层进发&#xff0c;使之成为公有云服务市场增长的主要动力。”11月22日&#xff0c;全球领先的IT研究和咨询公司国际数据公司&#xff08;IDC&#xff09;联合腾讯云发布“下一代云”白皮书——《聚焦平台能力&#xff0c;支撑智能化业务发展》指出…

python -opencv 图像锐化

python -opencv 图像锐化 图像锐化其实&#xff0c;是一种增强图片对比度的技术&#xff0c;我们可以通过计算图像的导数&#xff0c;把导数绝对值数值大于零的数值加回原图像&#xff0c;通过这种方法&#xff0c;可以增强图像的对比度。 实现代码如下&#xff1a; import c…