[Linux]进程间通信

news2024/11/16 19:41:24

[Linux]进程间通信

文章目录

      • [Linux]进程间通信
        • 进程间通信
          • 什么是进程间通信
          • 进程间通信的目的
          • 进程间通信的本质
          • 为什么存在进程间通信
          • 进程间通信的分类
        • 管道
          • 什么是管道
          • 匿名管道
            • 本质
            • pipe
            • pipe的使用
            • 匿名管道读写情况
            • 匿名管道的特征
          • 命名管道
            • 本质
            • 命令行创建命名管道
            • 创建和删除命名管道
            • 实现服务端与客户端通信
        • System V
          • system V共享内存
            • 共享内存的原理
            • IPC资源的查看
            • 共享内存的创建和释放
            • 共享内存的关联和去关联
            • 实现服务端与客户端通信

进程间通信

什么是进程间通信

进程之间具有独立性,如果需要进行通信,就必须打破进程间的独立性。进程通信需要提供一块公共的能够进行信息存储和取出的空间。文件系统提供的我们称为管道,操作系统提供的System V。

进程间通信的目的
  1. 数据传输:一个进程将数据发送给另一个进程。
  2. 资源共享:多个进程共享相同的资源。
  3. 事件通知:一个进程需要向另一个或一组进程发送消息。通知它们发生了某种事件,例如子进程终止时需要通知父进程。
  4. 进程控制:有的进程希望完全控制另一个进程的执行,例如Debug进程。
进程间通信的本质

进程间通信的本质就是让不同的进程看到同一份资源。

实际上就是构建一个公共区域,供不同进程进行写入或读取数据。

image-20230727104753581

为什么存在进程间通信

实际情况中,有时候我们需要多进程协作完成某种业务。

进程间通信的分类
  1. 管道
  • 匿名管道
  • 命名管道
  1. System V
  • System V消息队列
  • System V信号量
  • System V共享内存
  1. POSIX
  • 共享内存
  • 信号量
  • 消息队列
  • 互斥量
  • 条件变量
  • 读写锁

管道

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

其实我们早在之前命令中的学习就已经见过了管道|,我们也经常使用管道命令。

例如,查看服务器连接人数:

[---@VM-8-4-centos day04]$ who | wc -l

image-20230727110434843

在这里插入图片描述

匿名管道
本质

匿名管道主要用于父子间的通信。它本质上就是让父子进程看到同一个被打开的文件,然后让父子进程进行写入或读取数据,从而实现父子间的通信。

这里的文件是由操作系统提供的,所以在父进程或子进程写入数据时,并不会发生写时拷贝。

pipe
int pipe(int pipefd[2]);
  • 头文件:#include<unistd.h>
  • pidfd是一个输出型参数,pipfd[0]:管道读端的文件描述符;pipfd[1]:管道写端的文件描述符。
  • 返回值:调用成功,返回0;调用失败,返回-1。
pipe的使用

实际上,我们需要对一对父子进程关闭相反的两个端口来使用匿名管道进行通信。

  • 需要父进程读,则关闭父进程的写端和关闭子进程的读端。
  • 需要父进程写,则关闭父进程的读端和关闭子进程的写端。

例如,父进程关闭写端,子进程关闭读端。

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <cstring>

#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main()
{
    int fd[2] = {0};
    int n = pipe(fd);
    if (n != 0)
        perror("pipe fail");
    pid_t id = fork();
    int cnt = 0;
    const char *str = "I am a child process. MYPID->";
    if (id > 0)
    {
        close(fd[1]); // 父进程关闭写端
        while (1)
        {
            char buffer[1024];
            ssize_t r = read(fd[0], buffer, sizeof(buffer) - 1);
            if (r > 0)
            {
                buffer[r] = '\0';
                cout << "parent process get message-> " << buffer << endl;
            }
            else if (r == 0)
            {
                // 读完了
                cout << endl;
                cout << "数据读取完毕!!!" << endl;
                break;
            }
            else
            {
                perror("read fail");
                return -1;
            }
        }
        int wp = waitpid(-1, nullptr, 0);
        close(fd[0]);
        return 0;
    }
    else if (id == 0)
    {
        close(fd[0]); // 子进程关闭读端
        while (1)
        {
            char buffer[1024];
            snprintf(buffer, sizeof(buffer), "Message: %s%d, count: %d", str, getpid(), cnt++);
            write(fd[1], buffer, strlen(buffer));
            sleep(1); // 每隔1秒写入一次
            if (cnt == 6)
                break;
        }
        close(fd[1]);
        exit(0);
    }
    else
    {
        perror("fork fail");
        return -1;
    }
    return 0;
}

在这里插入图片描述

匿名管道读写情况
  1. 如果管道中没有数据,读端进行读取,就会阻塞当前读取的进程
  2. 如果写端写满了,写端还进行写入,就会阻塞当前写端的进程
  3. 如果写端关闭了,读端读完数据后就会返回0,正常退出
  4. 如果读端关闭了,操作系统会向写端发送13号信号SIGPIPE,从而让写端关闭
匿名管道的特征
  1. 匿名管道是半双工通信的。
  • 单工通信:数据传输在通信双方是单向的,一方为固定发送端,另一方为固定接收端。
  • 半双工通信:数据传输在通信双方是双向的,但不能同时发送数据。
  • 全双工通信:数据传输在通信双方是双向的,允许同时发送数据。
  1. 管道的生命周期随进程,进程退出,则管道释放。
  • 管道的本质是通过一个文件进行通信的,当打开这个文件的进程退出后,这个文件也会被释放掉。
  1. 管道提供的是流式服务。
  • 对于写端写入的数据,读端读取的数据是任意的,这就是流式服务。
  • 对于写端写入的数据,读端读取时根据数据的分割(按一定的报文段)读取,这就是与流式服务相对的数据报服务。
  1. 内核对管道操作会进行同步和互斥。
  • 同步:在特定的时间点或条件下,不同进程之间的操作按照一定的顺序和速度进行,以保证它们之间的状态和行为达到预期的一致性。
  • 互斥:一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

对于管道来说,同步就是指这两个进程不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作;互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作。

ps:管道的最大容量一般是65536字。

我们也可以通过以下命令查看:

[---@VM-8-4-centos day04]$ ulimit -a

image-20230727210011484

命名管道

匿名管道通常只能用于父子进程间的通信,为了让不具有亲缘关系的进程相互通信,由此有了命名管道。

本质

通过创建一个特殊的文件,让两个进程看到同一份资源,从而实现通信。

匿名管道和命名管道都是内存文件,但是命名管道在磁盘上有一个特殊的映像。(大小为0,因为命名管道和匿名管道都不会刷新到磁盘上)

命令行创建命名管道
[---@VM-8-4-centos day04]$ mkfifo named_pipe

image-20230727211628264

我们可以从第一个p看到出,这个文件类型是管道文件,管道文件大小默认是0。

此时我们已经可以进行通信了,我们使用两个不同的命令行进行通信测试:

image-20230727212134864

如果我们不进行读取,写端就会阻塞等待读端读取。

创建和删除命名管道
int mkfifo(const char *pathname, mode_t mode);
  • pathname:命名管道创建路径,若给出文件名,则创建在当前路径下;若给出路径,按路径创建

  • mode:管道文件默认权限

  • 返回值:创建成功,返回0;创建失败,返回-1。

image-20230727214411219

image-20230727214428861

int main()
{
    umask(0);
    int n = mkfifo("named_pipe", 0666);
    if(n < 0) perror("mkfifo fail");
    //创建成功
    cout << "mkfifo success..." << endl;
    return 0;
}

image-20230727215445358

int unlink(const char *path)
  • path:管道路径
  • 返回值,创建成功,返回0;创建失败,返回-1

image-20230728104124115

image-20230728104145636

int main()
{
    int n = unlink("./named_pipe");
    if(n == -1) 
    {
        perror("unlink fail");
        return -1;
    }
    cout << "unlink success ......." << endl;
    return 0;
}

image-20230728104530341

实现服务端与客户端通信

com.hpp

#pragma once
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>

using namespace std;

bool create_named_pipe()
{
    umask(0);
    int n = mkfifo("./named_pipe", 0600); // 创建管道文件
    if (n != 0)
    {
        perror("mkfifo fail");
        return false;
    }

    cout << "mkfifo success ......" << endl;
    return true;
}

void unlink_named_pipe()
{
    int n = unlink("./named_pipe");
    if (n != 0)
        perror("unlink fail");

    cout << "unlink success ......" << endl;
}

serve.cc

#include "com.hpp"

int main()
{
    bool flag = create_named_pipe();
    if (flag == false)
    {
        perror("create_named_pipe fail");
        exit(-1);
    }
    int fd = open("./named_pipe", O_RDONLY);
    if (fd < 0)
        perror("open fail");

    char buffer[1024];
    while (1)
    {
        ssize_t r = read(fd, buffer, sizeof(buffer) - 1);
        if (r > 0)
        {
            buffer[r] = '\0';
            cout << "serve get message -> " << buffer << endl;
        }
        else if (r == 0)
        {
            cout << "read end ......" << endl;
            break;
        }
        else
        {
            perror("read fail");
            return -1;
        }
    }
    close(fd);
    unlink_named_pipe();
    return 0;
}

client.cc

#include "com.hpp"

int main()
{
    int fd = open("./named_pipe", O_WRONLY);
    if (fd < 0)
        perror("open fail");

    char buffer[1024];
    while(1)
    {
        cout << "client Enter # ";
        fgets(buffer, sizeof(buffer), stdin);//fgets剩一个空间会被系统填充'\0',不用-1
        ssize_t w = write(fd, buffer, strlen(buffer));
        if(w != strlen(buffer))
        {
            perror("write fail");
            exit(-1);
        }
    }
    close(fd);
    return 0;
}

image-20230728224245270

另外一提,命令行中的管道|是匿名管道。

System V

之前我们提到过System V通信方式有:System V共享内存、System V消息队列、System V信号量,下面我们就着重说说System V共享内存。

system V共享内存
共享内存的原理

用户使用操作系统提供的接口在物理内存中申请一块资源,通过页表将这段物理空间映射至进程地址空间,进程将这段虚拟地址的起始地址返回给用户。

image-20230728110710348

操作系统中的进程都可以通过共享内存进行通信,一个操作系统可以有多个共享内存。

共享内存不止一个,操作系统必然对这些共享内存也要进行管理,系统也为他维护了一个数据结构:

struct shmid_ds {
	struct ipc_perm     shm_perm;   /* operation perms */
	int         shm_segsz;  /* size of segment (bytes) */
	__kernel_time_t     shm_atime;  /* last attach time */
	__kernel_time_t     shm_dtime;  /* last detach time */
	__kernel_time_t     shm_ctime;  /* last change time */
	__kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
	__kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
	unsigned short      shm_nattch; /* no. of current attaches */
	unsigned short      shm_unused; /* compatibility */
	void            *shm_unused2;   /* ditto - used by DIPC */
	void            *shm_unused3;   /* unused */
};

第一个成员shm_perm的类型ipc_perm结构是这样的。(key也存在shm_perm中)

struct ipc_perm{
	__kernel_key_t  key;
	__kernel_uid_t  uid;
	__kernel_gid_t  gid;
	__kernel_uid_t  cuid;
	__kernel_gid_t  cgid;
	__kernel_mode_t mode;
	unsigned short  seq;
};
IPC资源的查看

我们使用以下命名,可以查看系统中的IPC资源:

[---@VM-8-4-centos day05]$ ipcs

在这里插入图片描述

ipcs这个命令还有3个选项,分别来查看共享内存、消息队列和信号量。

  • -q:仅显示消息队列的信息
  • -m:仅显示共享内存的信息
  • -a:仅显示信号量的信息
共享内存的创建和释放
key_t ftok(const char *pathname, int proj_id);
  • 作用:将一个存在且可获取的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,方便共享内存创建唯一标识。
  • 返回值:创建成功,返回key值;创建失败,返回-1。

image-20230728112857273

image-20230728112914784

int shmget(key_t key, size_t size, int shmflg);
  • key:形成唯一标识,保证进程看到的是同一块共享内存。(使用ftok获取)
  • size:创建共享内存的大小。
  • shmflg:共享内存的创建方式。IPC_CREAT:共享内存不存在,则创建,如果存在则获取;IPC_EXCL:无法单独使用,IPC_CREAT|IPC_EXCL:如果不存在就创建,如果存在就出错返回。(记得设置权限0666等,例如IPC_CREAT | IPC_EXCL | 0666)
  • 返回值:创建成功,返回共享内存标识符;创建失败,返回-1。

image-20230728113015896
image-20230728113106487

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

using namespace std;

int main()
{
    key_t key = ftok("./makefile", 0x6666);
    if(key < 0) 
    {
        perror("ftok fail");
        return -1;
    }
    int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
    if(shm < 0)
    {
        perror("shmget fail");
        return -2;
    }
    cout << key << endl << shm << endl;
    return 0;
}

image-20230728112809018

我们使用ipcs命令来看一下,我们是否创建成功:

在这里插入图片描述


int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid:表示所控制共享内存的用户级标识符。
  • cmd:表示具体的控制动作。IPC_RMID最为常用,表示删除共享内存。
  • buf:用于获取或设置所控制共享内存的数据结构。一般设置为nullptr
  • 返回值:调用成功,返回0;调用失败。返回-1。

image-20230728113720899

image-20230728113747665

int main()
{
    key_t key = ftok("./makefile", 0x6666);
    if (key < 0)
    {
        perror("ftok fail");
        return -1;
    }
    int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL);
    if (shm < 0)
    {
        perror("shmget fail");
        return -2;
    }
    cout << key << endl << shm << endl;
    cout << "create success..." << endl;
    sleep(3);
    int ctl = shmctl(shm, IPC_RMID, nullptr);
    if(ctl < 0)
    {
        perror("shmctl fail");
        return -3;
    }
    cout << "delete success..." <<endl;
    return 0;
}

我们创建一块共享内存,让程序休眠3秒。休眠后,删除这块共享内存。(我们可以看见其中有3行打印了这块共享内存,第4行就没有了,这也证明了我们代码的逻辑是正确的)

右边命令行使用以下的监控脚本:

[wsj@VM-8-4-centos day05]$ while :; do ipcs -m;echo "###################################";sleep 1;done

image-20230728180549070


我们也可以使用命令行来删除共享内存:

[wsj@VM-8-4-centos day05]$ ipcrm -m 6(数字代表自己共享内存的shm)

image-20230728181119102

共享内存的关联和去关联
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:待关联的共享内存标识符
  • shmaddr:指定共享内存映射到进程地址空间中的某一地址,一般设置nullptr让内核自己选择。
  • shmflg:SHM_RDONLY,表示关联共享内存后,仅进行读取操作;SHM_RND,表示如果shmaddr不为空,则自动向下调整为SHMLBA的整数倍;0,默认为读写权限。
  • 返回值:调用成功,返回映射到进程地址空间的共享内存的地址;调用失败,返回(void*)-1。
int shmdt(const void *shmaddr);
  • shmaddr:待去关联的共享内存,使用shmat得到的地址。

  • 返回值,调用成功,返回0;调用失败,返回-1。

接下来,我们使用一段代码加深一下对这些接口调用的理解:

int main()
{
    key_t key = ftok("./makefile", 0x6666);
    if (key < 0)
    {
        perror("ftok fail");
        return -1;
    }
    int shm = shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
    if (shm < 0)
    {
        perror("shmget fail");
        return -2;
    }
    cout << "create success..." << endl;
    cout << "-----------------------" << endl;

    void* mem = shmat(shm, nullptr, 0);
    if(mem == (void*)-1)
    {
        perror("shmat fail");
        return -3;
    }
    cout << "attach success..." << endl;
    cout << "-----------------------" << endl;
    int dt = shmdt(mem);
    if(dt < 0)
    {
        perror("shmdt fail");
        return -4;
    }
    cout << "detach success ..." << endl;
    cout << "-----------------------" << endl;
    int ctl = shmctl(shm, IPC_RMID, nullptr);
    if(ctl < 0)
    {
        perror("shmctl fail");
        return -5;
    }
    cout << "delete success..." << endl;
    return 0;
}

右边的命令行,任然使用上面的监控脚本。

我们从共享内存,从无到有;从关联数,从0到1,再到0。证明我们代码的逻辑是正确的。(创建共享内存->关联该共享内存->去关联该共享内存->删除共享内存)

image-20230728184614542

ps:创建共享内存时需要设置权限,不然就无法正常关联。

image-20230728185006918

实现服务端与客户端通信

com.hpp

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

using namespace std;

#define PATH "./makefile"
#define PROJ_ID 0X888
#define MAX_SIZE 4096

key_t getKey() // 获取key值
{
    key_t key = ftok(PATH, PROJ_ID);
    if (key == -1)
    {
        perror("ftok fail");
        exit(-1);
    }
    return key;
}

int getShm(key_t key, int flag) // 创建共享内存,为下面两个函数服务
{
    int shm = shmget(key, MAX_SIZE, flag);
    if (shm < 0)
    {
        perror("shmget fail");
        exit(-2);
    }
    return shm;
}

int shmHelper(key_t key) // 获取共享内存(已创建的前提)
{
    return getShm(key, IPC_CREAT);
}

int createShm(key_t key) // 创建共享内存
{
    return getShm(key, IPC_CREAT | IPC_EXCL | 0666);
}

void *attachShm(int shm) // 关联
{
    void *mem = shmat(shm, nullptr, 0);
    if (mem == (void *)-1)
    {
        perror("shmat fail");
        exit(-3);
    }
    return mem;
}

void detachShm(void *mem) // 去关联
{
    if (shmdt(mem) < 0)
    {
        perror("shmdt fail");
        exit(-4);
    }
}

int deleteShm(int shm) // 删除共享内存
{
    if (shmctl(shm, IPC_RMID, nullptr) < 0)
    {
        perror("shmctl fail");
        exit(-5);
    }
}

serve.cc

#include "com.hpp"

int main()
{
    int key = getKey();
    int shm = createShm(key);
    cout << shm << endl;
    void *mem = attachShm(shm);
    cout << mem << endl;
    int cnt = 0;
    while (cnt++ < 10)
    {
        printf("client # %s\n", mem);
        struct shmid_ds ds;
        shmctl(shm,IPC_STAT,&ds);
        cout << "PID->" << getpid() << ", creator->" << ds.shm_cpid << ", key->" << ds.shm_perm.__key << endl;
        sleep(1);
    }
    detachShm(mem);
    deleteShm(shm);
    return 0;
}

client.cc

#include "com.hpp"

int main()
{
    int key = getKey();
    int shm = shmHelper(key);
    cout << shm << endl;
    void *mem = attachShm(shm);
    cout << mem << endl;
    int cnt = 0;
    char *message = "Hello, I`m client";
    while (1)
    {
        snprintf((char *)mem, MAX_SIZE, "PID->%d : %s, count : %d\n", getpid(), message, ++cnt);
        sleep(1);
    }
    detachShm(mem);
    return 0;
}

image-20230728223930542

共享内存是所有通信中最快的通信方式,因为它没有缓冲区,能大大减少通信数据的拷贝次数。

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

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

相关文章

SolidWorks绘制Maxwell仿真用带桥接的三维平板螺旋线圈

文章目录 前言一、建立涡状线二、拉伸方法1&#xff08;建立工作面&#xff0c;较复杂&#xff09;三、拉伸方法2&#xff08;穿透&#xff0c;较简单&#xff09;四、建立桥接 前言 在使用Maxwell进行电磁场仿真时&#xff0c;经常需要绘制各种异形线圈&#xff0c;由于Maxwel…

5.定时器-间歇函数

网页中经常会需要一种功能&#xff1a;每隔一段时间需要自动执行一段代码&#xff0c;不需要我们手动去触发 例如&#xff1a;网页中的倒计时 ●要实现这种需求&#xff0c;需要定时器函数 5.1开启定时器 语法 setInterval(函数,间隔时间)作用&#xff1a;每隔一段时间调用这…

Spring 6【单例设计模式、bean标签的scope属性、Spring 循环注入问题】(八)-全面详解(学习总结---从入门到深化)

目录 十五、单例设计模式 十六、bean标签的scope属性 十七、Spring 循环注入问题 十五、单例设计模式 设计模式&#xff1a;根据面向对象五大设计思想衍生出的23种常见代码写法&#xff0c;每种写法可以专门解决一类问题。 单例设计模式&#xff1a;保证某个类在整个应用程…

一文搞懂加密和接口签名小知识

最近在做的接口自动化测试工程中&#xff0c;一些接口需要签名&#xff0c;涉及到了加解密的一些知识&#xff0c;顺手梳理了下&#xff0c;分享给大家。 主要分为四个部分介绍&#xff1a; 一、基础概念 二、加密算法介绍 三、接口签名 四、实例分析 一、基础概念 加密是…

Leetcode 27 移除元素 代码逐行讲解

Leetcode 27 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考…

Go语言中的结构体详解

关于 Golang 结构体 Golang 中没有“类”的概念&#xff0c;Golang 中的结构体和其他语言中的类有点相似。和其他面向对 象语言中的类相比&#xff0c;Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性&#xff0c;但是当我们…

【Python】二维离散小波变换(2D-DWT)实现

文章目录 小波变换程序实现子带数学公式 小波变换 小波变换&#xff08;Wavelet Transform&#xff09;是一种数学信号处理技术&#xff0c;用于将信号或图像分解为不同频率的小波成分&#xff0c;从而可以在不同时间尺度上分析信号的特征。小波变换具有许多重要的特性&#x…

安装Python之后 安装库报错 There was an error checking the latest version of pip.

报错代码 & 图片如下 Looking in indexes: https://pypi.tuna.tsicmdnghua.edu.cn/simple WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(<pip._vendor.urllib3.connection.HT…

Matplotlib_概述_绘制图象

⛳绘制基础 在使用 Matplotlib 绘制图形时&#xff0c;其中有两个最为常用的场景。一个是画点&#xff0c;一个是画线。 pyplot 基本方法的使用如下表所示 方法名说明title()设置图表的名称xlabel()设置 x 轴名称ylabel()设置 y 轴名称xticks(x, ticks, rotation)设置 x 轴的…

【蓝图】p44简单解密机关

p44简单解密机关 p44简单解密机关文字提示开门文字提示开灯For Each Loop和For Each Loop With Break区别For Each LoopFor Each Loop With Break小操作&#xff1a;改变走线Execute Console Command(执行控制台命令) p44简单解密机关 文字提示开门 创建Actor蓝图类&#xff…

软件测试生命周期

本章简要介绍了软件开发项目中常用的生命周期模型&#xff0c;并解释了测试在每个模型中扮演的角色。它讨论了各种测试级别和测试类型之间的区别&#xff0c;并解释了这些在开发过程中的应用位置和方式。 大多数软件开发项目是按照事先选择的软件开发生命周期模型来计划和执行…

win11任务栏不合并 终于回归啦

25915.1000 win11任务栏不合并 终于回归啦&#xff01;&#xff01;&#xff01; 下载地址&#xff1a;https://uup.rg-adguard.net/

Jenkins搭建最简教程

纠结了一小会儿&#xff0c;到底要不要写这个&#xff0c;最终还是决定简单记录一下&#xff0c;因为Jenkins搭建实在是太简单了&#xff0c;虽然也有坑&#xff0c;但是坑主要在找稳定的版本上。 先学一个简称&#xff0c;LTS (Long Term Support) 属实是长见识了&#xff0c…

Excel透视表与python实现

目录 一、Excel透视表 1、源数据 2、数据总分析 3、数据top分析 二、python实现 1、第一张表演示 2、第二张表演示 一、Excel透视表 1、源数据 1&#xff09;四个类目&#xff0c;每类50条数据 2&#xff09;数据内容 2、数据总分析 1&#xff09;选择要分析的字段&…

live-server本地起node服务解决跨域问题

一、初始化node,构建package.json NPM 全局安装live-server npm install -g live-server在当前项目文件夹下cmd运行&#xff1a; npm init -y此时会在根目录下生成一个package.json文件。 二.生成代理脚本 在根文件夹新建一个build.js文件&#xff08;名字可以自定义) var …

银行项目性能压测?关键链路性能压力测试,测试老鸟总结...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 随着银行业数字化…

Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集

Python将COCO格式实例分割数据集转换为YOLO格式实例分割数据集 前言相关介绍COCO格式实例分割数据集转换为YOLO格式实例分割数据集coco格式对应的json文件&#xff0c;以test.json为例格式转换代码&#xff0c;内容如下 前言 由于本人水平有限&#xff0c;难免出现错漏&#xf…

MySQL基础(五)主从复制及读写分离

目录 前言 一、概述 &#xff08;一&#xff09;、MySQL Replication &#xff08;二&#xff09;、MySQL复制类型 &#xff08;三&#xff09;、MySQL支持的复制方式 二、部署MySQL主从异步复制 &#xff08;一&#xff09;、master&#xff08;主&#xff09; &#x…

一起来学shiny把(3)—-添加控件

什么是shiny&#xff1f;Shiny是一个R包&#xff0c;可让您轻松地直接从 R 构建交互式 Web 应用程序&#xff08;应用程序&#xff09;。本系列是个长教程&#xff0c;带你由浅入深学习shiny。 上一节我们在文章《R语言系列教程—–一起来学shiny吧&#xff08;2&#xff09;》…

【C++11】——右值引用、移动语义

目录 1. 基本概念 1.1 左值与左值引用 1.2 右值和右值引用 1.3 左值引用与右值引用 2. 右值引用实用场景和意义 2.1 左值引用的使用场景 2.2 左值引用的短板 2.3 右值引用和移动语义 2.3.1 移动构造 2.3.2 移动赋值 2.3.3 编译器做的优化 2.3.4 总结 2.4 右值引用…