Linux系统编程:通过System V共享内存实现进程间通信

news2024/9/26 5:15:28

目录

一. 共享内存实现进程间通信的原理

二. 共享内存相关函数

2.1 共享内存的获取 shmget / ftok

2.2 共享内存与进程地址空间相关联 shmat

2.3 取消共享内存与进程地址空间的关联 shmdt

2.4 删除共享内存 shmctl

2.5 通信双方创建共享内存代码

三. 共享内存实现进程间通信

3.1 实现方法及特性

3.2 为共享内存添加访问控制

四. 总结


一. 共享内存实现进程间通信的原理

要实现进程间通信,就必须让相互之间进行通信的进程看到同一份资源(同一块内存空间),如通过管道实现进程间通信,本质就是让两个进程分别以读和写的方式打开同一份管道文件,一个进程向管道中写数据,另一个进程再从管道中将数据读出,这样两个进程就可以看到同一份内存空间,从而实现了进程间通信。

System V共享内存实现进程间通信的方式与管道相同,区别在于管道是基于文件的,而共享内存则是直接申请内存空间,不需用进行文件相关操作通过System V共享内存实现通信的进程,都会使用物理内存中的同一块空间,这一块公共的物理内存空间经过通信双方进程的页表,映射到进程地址空间的共享区,通信双方进程在运行期间,拿到共享区虚拟地址,通过页表映射,就可以看到同一块物理内存,就可以实现进程间通信。

如果操作系统内有多组通过System V共享内存方式相互通信的进程处于运行状态,那么就会存在多组共享内存,操作系统需要对这些共享内存空间进行管理,管理方式为:先通过struct结构体进行描述,再利用特定的数据结构组织

可以这样理解:共享内存 = 共享的物理内存 + 对应的内核级数据结构。

图1.1 共享内存的实现原理

二. 共享内存相关函数

共享内存实现进程间通信的步骤可以总结为:创建共享内存 -> 共享内存与地址空间相关联 -> 通信 -> 共享内存与地址空间解绑 -> 销毁共享内存。

2.1 共享内存的获取 shmget / ftok

shmget函数:获取共享内存

头文件:#include<sys/ipc.h>、#include<sys/shm.h>

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

函数参数:

        key -- 特定共享内存的标识符

        size -- 共享内存的大小

        shmflg -- 共享内存获取的权限参数

返回值:创建成功返回共享内存的编号(称为shmid),失败返回-1

共享内存标识符key:OS中可能存在多个共享内存,需要保证通信双方看到同一块共享内存,因此,每个共享内存都需要一个特定的key值进行区分,这个key值是多少并不重要,只要保证它在OS中是唯一的即可通信双方进程(Serve && Client)需要约定相同的算法,保证他们可以使用shmget获取到同一块共享内存

ftok函数可以用于获取key值,只要调用ftok的实参相同,就会返回相同的key值。

ftok函数:获取共享内存标识符key

头文件: #include<sys/ipc.h>、#include<sys/types.h>

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

函数参数:

        pathname:项目(文件)路径

        proj_id:项目(文件)的id编号

返回值:成功返回特定的key值,否则返回-1。

共享内存大小size:以字节为单位,建议取页(PAGE:4096bytes)大小的整数倍,因为如果获取共享内存空间的大小不是页大小的整数倍,OS就会向上取整申请到页大小整数倍的内存空间,但是多申请的空间却不能被用户所使用。如,申请4097bytes的共享内存,OS会实际申请2*4096bytes的空间,而能被使用的只有4097bytes,剩下的都浪费掉了。

权限参数shmflg:有IPC_CREAT、IPC_EXCL、共享内存起始权限码、0这几种选项,他们之间通过竖划线 | 隔开,每个选项都有其意义。

  • IPC_CREAT:如果key标识的共享内存存在,就直接将其获取,如果不存在,就创建。
  • IPC_EXCL:单独使用没有任何意义,一般配合IPC_CREAT使用,IPC_CREAT | IPC_EXCL表示如果共享内存不存在就将其创建,如果存在直接报错,这样可以保证获取到的共享内存是一块全新的共享内存。
  • 起始权限码:用户对于这块共享内存的使用权限,如0666就表示拥有者、所属组、其他人就具有读写权限。
  • 0:只能获取已经存在的共享内存,不能创建新的,不存在就报错。

一般而言,通信双方分别以 IPC_CREAT | IPC_EXCL 和 0 的方式获取共享内存,确保一方创建全新的共享内存,另一方只能获取到该共享内存(传0阻断不存在创建新共享内存的可能)。

代码2.1以 IPC_CREAT | IPC_EXCL | 0666 的方式获取共享内存,运行代码,就可以成功获取共享内存,但是当第二次运行代码,却发现运行出错了(见图2.1),这是因为该共享内存再第一次程序运行后被创建,存在于操作系统中,IPC_CREAT | IPC_EXCL获取的共享内存一定是全新的,因此第二次运行程序会失败,删除该共享内存之后才可以再次成功运行。

结论:共享内存的生命周期是随OS内核的,而不是随进程的。

代码2.1:获取共享内存

// common.hpp -- 头文件
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096


// shmServe.cc -- 客户端代码源文件(用于接收信息)
#include "common.hpp"

int main()
{
    // 获取共享内存key值
    key_t k = ftok(PATH_NAME, PROJ_ID);   
    if(k == -1)
    {
        perror("ftok");
        exit(1);
    }

    // 创建共享内存
    int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        perror("shmget");
        exit(2);
    }
    printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);

    return 0;
}
图2.1 代码2.1的两次运行结果

这里介绍两条指令,分别用于查看共享内存信息和删除共享内存:

  • ipcs -m 指令:查看系统中所有共享内存的详细信息。
  • ipcrm -m [shmid]:通过指定共享内存的shmid来删除指定的共享内存。

当然,也可以通过代码删除共享内存,本文后面会讲解。

图2.2 通过指令查看共享内存的属性信息和删除共享内存

2.2 共享内存与进程地址空间相关联 shmat

shmat函数:将共享内存关联到进程地址空间

头文件:#include<sys/types.h>、#include<sys/shm.h>

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

函数参数:

        shmid:进行挂接的共享内存的shmid

        shmaddr:指定挂接的虚拟地址(传NULL表示让OS自动选择挂接地址)

        shmflg:挂接权限相关参数

返回值:若成功返回挂接到的虚拟地址,失败返回nullptr

挂接地址shmaddr参数:由于我们并不可知虚拟地址的具体使用情况,所以这个参数基本都是传NULL/nullptr来让OS自动选择虚拟地址进行关联。 

挂接权限shmflg:如果传SHM_RDONLY,这表示对应共享内存空间只有读权限,传其他都是读写权限,一般shmflg都传实参0。

当共享内存与虚拟地址关联期间,使用ipcs -m指令查看共享内存属性信息,nattch就会变为1,如果通信双方都与共享内存进行了关联,那么nattch就是2。

2.3 取消共享内存与进程地址空间的关联 shmdt

shmdt函数:让共享内存与当前进程脱离

头文件:#include<sys/types.h>、#include<sys/shm.h>

函数原型:int shmdt(const char* shmaddr)

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

2.4 删除共享内存 shmctl

通过共享内存控制shmctl函数(共享内存控制函数),可以删除共享内存。

删除共享内存的操作只要通信双方有一方指向即可,否则会造成重复删除。一般而言,读取信息的进程创建新的共享内存,也负责删除共享内存,遵循谁创建、谁删除的原则。

shmctl函数:控制共享内存

头文件:#include<sys/ipc.h>  #include<sys/shm.h>

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

函数参数:

        shmid -- 共享内存的shmid

        cmd -- 控制指令,选择操作

        buf -- 指向描述共享内存属性信息的结构体指针

返回值:成功返回非负数,失败返回-1

形参cmd可以选择具体的控制策略:

  • IPC_STAT -- 以buf为输出型参数,获取共享内存的属性信息。
  • IPC_SET -- 设置共享内存的属性为buf指向的内容。
  • IPC_RMID -- 删除共享内存,此时buf传空指针NULL。

2.5 通信双方创建共享内存代码

代码2.2:头文件common.hpp -- 由通信双方共同包含

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096

代码2.3:服务端代码shmServe.cc -- 用于数据读取

#include "common.hpp"

int main()
{
    // 获取共享内存key值
    key_t k = ftok(PATH_NAME, PROJ_ID);   
    if(k == -1)
    {
        perror("Serve ftok");
        exit(1);
    }
    printf("Serve# 成功获取key值,key:%d\n", k);

    // 创建共享内存
    int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        perror("Sreve shmget");
        exit(2);
    }
    printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);

    // 将共享内存与进程相关联
    char* shmaddr = (char*)shmat(shmid, NULL, 0);
    if(shmaddr == nullptr)
    {
        perror("Serve shmat");
        exit(3);
    }
    printf("Serve# 共享内存与进程成功关联,shmid:%d\n", shmid);

    // 通信代码
    // ... ...
    
    // 让共享内存脱离当前进程
    int n = shmdt(shmaddr);
    if(n == -1)
    {
        perror("Serve shmdt");
        exit(4);
    }
    printf("Serve# 共享内存成功脱离进程,shmid:%d\n", shmid);

    // 删除共享内存
    n = shmctl(shmid, IPC_RMID, NULL);
    if(n == -1)
    {
        perror("Serve shmctl");
        exit(5);
    }
    printf("Serve# 共享内存删除成功,shmid:%d\n", shmid);

    return 0;
}

代码2.4:客户端代码shmClient.cc -- 用于数据发送

#include "common.hpp"

int main()
{
    // 获取共享内存key值
    key_t k = ftok(PATH_NAME, PROJ_ID);   
    if(k == -1)
    {
        perror("Client ftok");
        exit(1);
    }
    printf("Client# 成功获取key值,key:%d\n", k);

    // 创建共享内存
    int shmid = shmget(k, SIZE, 0);
    if(shmid == -1)
    {
        perror("Client shmget");
        exit(2);
    }
    printf("Client# 共享内存获取成功,shmid:%d\n", shmid);

    // 将共享内存与进程相关联
    char* shmaddr = (char*)shmat(shmid, NULL, 0);
    if(shmaddr == nullptr)
    {
        perror("Client shmat");
        exit(3);
    }
    printf("Client# 共享内存与进程成功关联,shmid:%d\n", shmid);

    // 通信代码
    // ... ...
    
    // 让共享内存脱离当前进程
    int n = shmdt(shmaddr);
    if(n == -1)
    {
        perror("Client shmdt");
        exit(4);
    }
    printf("Client# 共享内存成功脱离进程,shmid:%d\n", shmid);

    return 0;
}

三. 共享内存实现进程间通信

3.1 实现方法及特性

在数据输入端(shmClient),我们可以将共享内存视为一块通过malloc得来的char*指向的一段动态内存空,可以使用printf系列函数向这块空间写数据,或者将共享内存空间视为数组,使用下标的形式给每个位置赋值,这样就实现了将数据写入共享内存。

在数据读取端(shmServe),可以将共享内存视为一个大字符串,通过特定的方式,从这个大字符串中获取数据即可。

代码3.1和代码3.2实现了共享内存进程间通信的简单逻辑,在shmClient端,通过下标访问的方式,每隔3s写一次数据,在shmServe端,每隔1s读取一次数据。先运行shmServe端代码,间隔几秒后运行shmClient端代码,根据图3.1展示的运行结果,shmServe端在shmClient端开始运行之前就开始读取共享内存中的内容,在shmClient运行起来后,由于读快写慢,shmClient写入的内容在shmServe端被多次读取,可见,共享内存,没有访问控制。

结论1:共享内存没有访问控制。

代码3.1:shmClient端发送数据

    // 通信代码
    char ch = 'a';
    int count = 0;
    for(; ch <= 'c'; ++ch)
    {
        shmaddr[count++] = ch;
        printf("write succsee# %s\n", shmaddr);
        sleep(3);
    }
    
    snprintf(shmaddr, SIZE, "quit");

代码3.2:shmServe端读取数据 

    // 通信代码
    while(true)
    {
        printf("[Client say]# %s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
        sleep(1);
    }
图3.1 共享内存通信读写双方代码执行结果

通过观察上面的代码我们发现,用户可以直接向共享内存中写数据和从共享内存中读取数据,不需要经过用户级缓冲区,共享内存的读或写操作最少只需要一次拷贝即可完成。而通过管道进行读写,则需要将数据预先写入或读入缓冲区,才可以写入管道文件或读出。图3.2为使用管道和共享内存的方法进行进程间通信时,读和写操作涉及的数据拷贝情况,管道通信至少要进行两次数据拷贝,而共享内存可以只进行一次数据拷贝,因此共享内存是一种高效的进程间通信手段。

结论2:共享内存进行进程间通信,通信的一方向共享内存中写入数据,通信的另一方马上就能读取到数据,不需要向操作系统中拷贝数据,共享内存是所有进程间通信方法中效率最高的。

图3.2 管道和共享内存实现进程间通信的资源拷贝情况

管道通信的特性总结:

  1. 不具有访问控制,存在并发问题。
  2. 不需要向OS内核中拷贝数据,通信效率高。

3.2 为共享内存添加访问控制

通过使用命名管道加以辅助,就可以为共享内存添加访问控制,具体的实现方法和原理为:

  • 在读端(shmServe)程序开始运行时创建命名管道文件,程序运行结束后管道文件销毁。
  • 在写端(shmClient)向共享内存中写入数据后,向管道文件中写入任意的、少量的数据,在读端(shmServe)获取共享内存内容之前,先读取管道中的资源,如果写端没有将期望的数据全部写入共享内存,那么就不会向管道中写数据,读端就必须阻塞等待管道中被写入数据,也就无法获取共享内存中的数据。只有当写端完成向共享内存中写入一次数据,然后向管道文件中写入数据让读端读到了管道资源后,读端代码才可以继续运行,获取到共享内存中的资源。

代码3.3 ~ 3.5,为通过管道为共享内存添加访问控制的实现代码。

代码3.3:common.hpp头文件 -- 被通信双方源文件包含

#pragma once

#include <iostream>
#include <cstring>
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATH_NAME "."
#define PROJ_ID 0x66
#define SIZE 4096

#define FIFO_NAME "fifo.ipc"
#define MODE 0666

// 定义类,其构造和析构函数可以创建和销毁管道文件
class Init
{
public:
    Init()
    {
        int n = mkfifo(FIFO_NAME, MODE);
        if(n == -1) perror("mkfifo");
        assert(n != -1);
        (void)n;
    }

    ~Init()
    {
        int n = unlink(FIFO_NAME);
        assert(n != -1);
        (void)n;
    }
};

#define READ O_RDONLY 
#define WRITE O_WRONLY

// 管道文件打开函数
int OpenFifo(const char* pathname, int flags)
{
    int fd = open(pathname, flags);
    assert(fd != -1);
    return fd;
}

// 等待函数 -- 用于读端访问控制
// 管道内没有资源时就阻塞
void Wait(int fd)
{
    uint32_t temp = 0;
    ssize_t sz = read(fd, &temp, sizeof(uint32_t));
    assert(sz == sizeof(uint32_t));
    (void)sz;
}

// 唤醒函数 -- 用于写端进程控制
// 向管道内写数据,终止读端进程的阻塞等待
void WakeUp(int fd)
{
    uint32_t temp = 1;
    ssize_t sz = write(fd, &temp, sizeof(uint32_t));
    assert(sz == sizeof(uint32_t));
    (void)sz;
}

// 管道关闭函数
void CloseFifo(int fd)
{
    close(fd);
}

代码3.4:读端源文件(shmServe.cc)代码

#include "common.hpp"

// 全局类对象
// 构造和析构函数分别负责管道文件的创建和销毁
Init init;

int main()
{
    // 获取共享内存key值
    key_t k = ftok(PATH_NAME, PROJ_ID);   
    if(k == -1)
    {
        perror("Serve ftok");
        exit(1);
    }
    printf("Serve# 成功获取key值,key:%d\n", k);

    // 创建共享内存
    int shmid = shmget(k, SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        perror("Sreve shmget");
        exit(2);
    }
    printf("Serve# 共享内存获取成功,shmid:%d\n", shmid);

    // 将共享内存与进程相关联
    char* shmaddr = (char*)shmat(shmid, NULL, 0);
    if(shmaddr == nullptr)
    {
        perror("Serve shmat");
        exit(3);
    }
    printf("Serve# 共享内存与进程成功关联,shmid:%d\n", shmid);

    // 通信代码
    int fd = OpenFifo(FIFO_NAME, READ);   // 只读方式打开管道文件
    while(true)
    {
        Wait(fd);   // 等待读取
        printf("[Client say]# %s\n", shmaddr);
        if(strcmp(shmaddr, "quit") == 0) break;
    }

    // while(true)
    // {
    //     printf("[Client say]# %s\n", shmaddr);
    //     if(strcmp(shmaddr, "quit") == 0) break;
    //     sleep(1);
    // }
    
    // 让共享内存脱离当前进程
    int n = shmdt(shmaddr);
    if(n == -1)
    {
        perror("Serve shmdt");
        exit(4);
    }
    printf("Serve# 共享内存成功脱离进程,shmid:%d\n", shmid);

    // 删除共享内存
    n = shmctl(shmid, IPC_RMID, NULL);
    if(n == -1)
    {
        perror("Serve shmctl");
        exit(5);
    }
    printf("Serve# 共享内存删除成功,shmid:%d\n", shmid);

    CloseFifo(fd);

    return 0;
}

代码3.5:写端源文件(shmClient.cc)代码

#include "common.hpp"

int main()
{
    // 获取共享内存key值
    key_t k = ftok(PATH_NAME, PROJ_ID);   
    if(k == -1)
    {
        perror("Client ftok");
        exit(1);
    }
    printf("Client# 成功获取key值,key:%d\n", k);

    // 创建共享内存
    int shmid = shmget(k, SIZE, 0);
    if(shmid == -1)
    {
        perror("Client shmget");
        exit(2);
    }
    printf("Client# 共享内存获取成功,shmid:%d\n", shmid);

    // 将共享内存与进程相关联
    char* shmaddr = (char*)shmat(shmid, NULL, 0);
    if(shmaddr == nullptr)
    {
        perror("Client shmat");
        exit(3);
    }
    printf("Client# 共享内存与进程成功关联,shmid:%d\n", shmid);

    // 通信代码
    int fd = OpenFifo(FIFO_NAME, WRITE);
    while(true)
    {
        ssize_t sz = read(0, shmaddr, SIZE);    // 共享内存从键盘中读入数据(换行符也被写入)
        assert(sz >= 0);
        shmaddr[sz - 1] = '\0';   //末尾添加'\0'表示终止
        WakeUp(fd);    // 唤醒读端进程
        if(strcmp(shmaddr, "quit") == 0) break;
    }

    // char ch = 'a';
    // int count = 0;
    // for(; ch <= 'c'; ++ch)
    // {
    //     shmaddr[count++] = ch;
    //     printf("write succsee# %s\n", shmaddr);
    //     sleep(3);
    // }
    
    // snprintf(shmaddr, SIZE, "quit");
    
    // 让共享内存脱离当前进程
    int n = shmdt(shmaddr);
    if(n == -1)
    {
        perror("Client shmdt");
        exit(4);
    }
    printf("Client# 共享内存成功脱离进程,shmid:%d\n", shmid);

    CloseFifo(fd);

    return 0;
}

四. 总结

  • System V共享内存实现进程间通信的底层原理是通信双方进程看到同一块内存,位于物理内存上的共享内存块,通过页表映射到通信双方的进程地址空间的共享区,通信双方拿到共享区的虚拟地址,通过页表映射,访问到同一块物理内存。
  • 使用System V共享内存实现进程间通信的操作流程为:通过ftok函数获取唯一的共享内存标识符key -> 通过shmget函数获取共享内存 -> 通过shmat函数让共享内存和进程绑定 -> 【进行进程通信】-> 通过shmdt函数让共享内存和进程脱离 -> 通过shmctl删除共享内存。
  • System V共享内存 进程间通信的特点为:(1)不需要向操作系统内核中拷贝数据,是所有进程间通信的方法中效率最高的。(2)没有访问控制。
  • 通过管道的辅助,可以为 System V共享内存 进程间通信添加访问控制。

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

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

相关文章

基于java/springboot的考研学习平台

摘 要 本毕业设计的内容是设计并且实现一个考研学习平台。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。考研学习平台的功能已基本实现&#xff0c;主要包括学生、教师、课程信息、学习资源、课程购买、课程发货、课程…

【Spring Boot】构建RESTful服务 — 实战:实现Web API版本控制

实战&#xff1a;实现Web API版本控制 前面介绍了Spring Boot如何构建RESTful风格的Web应用接口以及使用Swagger生成API的接口文档。如果业务需求变更&#xff0c;Web API功能发生变化时应该如何处理呢&#xff1f;可以通过Web API的版本控制来处理。 1.为什么进行版本控制 …

「天锐绿盾」——企业电脑文件加密防泄密软件

随着信息技术的快速发展&#xff0c;公司的日常运营和商业机密都依赖于电脑文件。然而&#xff0c;黑客攻击、员工疏忽或物理丢失等原因都可能导致公司电脑文件泄露&#xff0c;给公司带来巨大的经济损失和声誉损失。因此&#xff0c;公司需要采取有效的措施来保护电脑文件的安…

因数据泄露被罚80万!高校数据安全合规建设如何开展?

8月16日&#xff0c;“南昌网警巡查执法”官方公号披露了一起高校数据泄露事件。 根据通报&#xff0c;南昌某高校3万余条师生个人信息数据在境外互联网上被公开售卖。南昌公安网安部门即刻开展一案双查&#xff0c;抓获犯罪嫌疑人3名&#xff0c;并对涉案高校不履行数据安全保…

游戏IP如何变身数字人?数字人绑定技术了解下

随着数字人的概念大火&#xff0c;各行各业纷纷推出专属的数字人&#xff0c;游戏《王者荣耀》作为国内最大的手游IP&#xff0c;凭借其自有资源角色IP的优势&#xff0c;推出了数字人“上官婉儿”&#xff0c;在晚会上携手真人跨次元演绎歌曲&#xff0c;在动作和舞蹈过程中由…

打造专属照片分享平台:快速上手Piwigo网页搭建

文章目录 通过cpolar分享本地电脑上有趣的照片&#xff1a;部署piwigo网页前言1.Piwigo2. 使用phpstudy网页运行3. 创建网站4. 开始安装Piwogo 总结 &#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x…

性能分析之MySQL慢查询日志分析

一、背景 MySQL的慢查询日志是MySQL提供的一种日志记录,他用来记录在MySQL中响应的时间超过阈值的语句,具体指运行时间超过long_query_time(默认是10秒)值的SQL,会被记录到慢查询日志中。 慢查询日志一般用于性能分析时开启,收集慢SQL然后通过explain进行全面分析,一…

视频集中存储/云存储/安防监控/视频汇聚平台EasyCVR新增角色权限功能分配

视频集中存储/云存储/安防视频监控/视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。 EasyCVR视频集中…

解构软件开发中的破窗效应

目录 一、前言 二、解构破窗效应 三、如何解构&#xff1f; 一、前言 “一个房子如果窗户破了&#xff0c;没有人去修补&#xff0c;隔不久&#xff0c;其它的窗户也会莫名其妙地被人打破&#xff1b;一面墙&#xff0c;如果出现一些涂鸦没有被清洗掉&#xff0c;很快的&#x…

dll调用nodejs的回调函数

nodejs使用ffi调用dll。dll中有回调函数调用js中的方法。 c语言中cdll.h文件 extern "C" {typedef void(*JsCall)(int index); //这个就是要传入的类型结构extern __declspec(dllimport) int Add(int a, int b);extern __declspec(dllexport) void CallBackTest(Js…

【内网穿透】如何实现在外web浏览器远程访问jupyter notebook服务器

文章目录 前言1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 前言 Jupyter Notebook&#xff0c;它是一个交互式的数据科学和计算环境&#xff0c;支持多种编程语言&#xff0c;如…

Python系统学习1-9-类一之类语法

一、类之初印象 1、类就是空表格&#xff0c;将变量&#xff08;列名&#xff09;和函数&#xff08;行为&#xff09;结合起来 2、创建对象&#xff0c;表达具体行 3、创建类就是创建数据的模板 --操作数据时有提示 --还能再组合数据的行为 --结构更加清晰 4、类的内存分配…

为什么我建议机械专业的同学转行嵌入式

最近&#xff0c;我身边有不少机械专业找我&#xff0c;他们对未来的就业形势相当苦恼&#xff0c;觉得自己之后只能进厂打螺丝了。但是我跟他们说&#xff0c;在这个选择比努力重要的时代&#xff0c;只要入对行&#xff0c;谁都可以一飞冲天。 对于学机械的同学&#xff0c;…

mysql 8.0安装

操作系统&#xff1a;22.04.1-Ubuntu apt 安装命令 sudo apt install mysql-client-core-8.0 sudo apt install mysql-server-8.0终端输入 mysql 可以直接免密登录 如果此时提示需要密码&#xff0c;则可以进入配置文件&#xff0c;设置免密登录 sudo vim /etc/mysql/mysq…

Python爬虫性能优化:多进程协程提速实践指南

目录 1. 多进程爬虫的实现&#xff1a; 1.1 将爬虫任务划分成多个子任务&#xff1a; 1.2 创建进程池&#xff1a; 1.3 执行任务&#xff1a; 1.4 处理结果&#xff1a; 代码示例 2. 协程爬虫的实现&#xff1a; 2.1 定义异步爬虫函数&#xff1a; 2.2 创建事件循环&a…

小程序分包流程

目录 问题&#xff1a;小程序为什么要分包&#xff1f; 一、常见的分包形式 二、常规分包 概念&#xff1a; 1.操作位置 2.特点 3.分包使用 1.主包结构不变&#xff0c;但是要把分包过的页面移除 2.分几个包就声明几个 3.主结构展示 注意&#xff1a;分包之后当进行页…

GD32F207 位带操作 GPIO

下面的程序用在GD32F207上测试成功&#xff0c; 如果要在新的单片机上进行位带操作需要查看新的单片机的寄存器偏移量。 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)0x2000000((addr &0xFFFFF)<<5)(bitnum<<2)) #define MEM_ADDR(addr) *((vol…

安防监控/视频集中存储/云存储平台EasyCVR v3.3增加首页告警类型

安防监控/视频集中存储/云存储EasyCVR视频汇聚平台&#xff0c;可支持海量视频的轻量化接入与汇聚管理。平台能提供视频存储磁盘阵列、视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、平台级联、H.265自动转码等…

基于Lin协议的UDS调度表

参考ISO 17987-2. 有两种调度模式&#xff1a; diagnostics only mode 没什么好说的&#xff0c;不存在普通应用报文。 interleaved diagnostics mode&#xff1a; 需要UDS请求时&#xff0c;等待当前normal communication schedule table执行完成&#xff0c;开始执行dia…

chromedriver.exe 的所有版本下载地址

Chrome for Testing availability 上面的网址是V115 v116.... 以上的。 CNPM Binaries Mirror 上面这个是V115版本以下的。 这个文章没有任何实际价值&#xff0c;记录的原因是因为突然发现过去的py无法运行&#xff0c;原因是chrome浏览器偷偷升级到V115&#xff0c;于是找…