Linux-进程间通信(IPC)

进程间通信(IPC)介绍

进程间通信(IPC,InterProcess Communication)是指在不同的进程之间传播或交换信息。IPC 的方式包括管道(无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程间通信。

1. 管道

1.1 无名管道

1.1.1 特点
  • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  • 它只能用于父子进程之间的通信。
  • 管道是创建在内存中,进程结束空间释放,管道不复存在。对于它的读写可以使用普通的 readwrite 等函数。
1.1.2 函数原型
#include <unistd.h>
int pipe(int pipefd[2]);
1.1.3 返回值

成功返回 0,失败返回 -1。当一个管道建立时,它会创建两个文件描述符:fd[0] 为读而打开,fd[1] 为写而打开。

1.1.4 无名管道代码示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int fd[2];
    int pid;
    char buf[128];

    if (pipe(fd) == -1) {
        printf("create pipe fail\n");
    }

    pid = fork();

    if (pid < 0) {
        printf("create child fail\n");
    } else if (pid > 0) {
        sleep(3);
        printf("this is father\n");
        close(fd[0]);
        write(fd[1], "hello from father", strlen("hello from father"));
        wait(NULL);
    } else {
        printf("this is child\n");
        close(fd[1]);
        read(fd[0], buf, 128);
        printf("read = %s \n", buf);
        exit(0);
    }
    return 0;
}

注意:管道的特性:管道里没有数据时会阻塞。

1.2 命名管道

1.2.1 特点
  • 命名管道可以在无关的进程之间交换数据,与无名管道不同。
  • 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
1.2.2 函数原型
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
1.2.3 命名管道代码示例

read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char buf[128] = {0};

    if (mkfifo("./file", 0600) == -1 && errno != EEXIST) {
        printf("mkfifo fail\n");
        perror("why");
    }

    fd = open("./file", O_RDONLY);
    printf("read open success\n");

    while (1) {
        int n_read = read(fd, buf, 128);
        printf("read %d byte, context = %s \n", n_read, buf);
    }

    close(fd);
    return 0;
}

write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char *str = "message from fifo";

    fd = open("./file", O_WRONLY);
    printf("write open success\n");

    while (1) {
        write(fd, str, strlen(str));
        sleep(1);
    }
    close(fd);
    return 0;
}

2. 消息队列

消息队列是信息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。

2.1 特点

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及内容并不会删除。
  • 消息队列可以实现信息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2.2 函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
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);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

2.3 消息队列代码示例

send.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[128];
};

int main() {
    struct msgbuf sendBuf = {888, "this is msg from queue"};
    struct msgbuf readBuf;
    key_t key = ftok(".", 1);
    printf("key=%d\n", key);

    int msgId = msgget(key, IPC_CREAT | 0777);
    if (msgId == -1) {
        perror("why:");
    }

    while (1) {
        msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
        sleep(1);
        printf("write success\n");

        msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 988, 0);
        sleep(1);
        printf("read from queue: %s\n", readBuf.mtext);
    }

    msgctl(msgId, IPC_RMID, NULL);
    return 0;
}

read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[128];
};

int main() {
    struct msgbuf readBuf;
    struct msgbuf sendBuf = {988, "this is msg from father"};
    key_t key = ftok(".", 1);
    printf("key=%d\n", key);

    int msgId = msgget(key, IPC_CREAT | 0777);
    if (msgId == -1) {
        perror("why:");
    }

    while (1) {
        msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
        sleep(1);
        printf("read from queue: %s\n", readBuf.mtext);

        msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
        sleep(1);
        printf("write success\n");
    }

    msgctl(msgId, IPC_RMID, NULL);
    return 0;
}

3. 信号

3.1 信号的处理

信号的处理有三种方法:忽略、捕捉和默认动作。具体的信号默认动作可以使用 man 7 signal 来查看系统的具体定义。

3.2 信号处理函数的注册

信号处理函数的注册可以分为入门版和高级版:

  • 入门版:signal 函数
  • 高级版:sigaction 函数

3.3 信号处理发送函数

信号发送函数也分为入门版和高级版:

  • 入门版:kill
  • 高级版:sigqueue

3.4 signal 函数原型及示例

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

示例

#include <signal.h>
#include <stdio.h>

void handler(int signum) {
    printf("get signum = %d\n", signum);
    switch (signum) {
        case SIGINT:
            printf("SIGINT\n");
            break;
        case SIGKILL:
            printf("SIGKILL\n");
            break;
        case SIGUSR1:
            printf("SIGUSR1\n");
            break;
    }
}

int main() {
    signal(SIGINT, handler);
    signal(SIGKILL, handler);
    signal(SIGUSR1, handler);
    while (1);
    return 0;
}

3.5 kill 函数原型及示例

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char **argv) {
    int signum = atoi(argv[1]);
    int pid = atoi(argv[2]);
    printf("signum = %d , pid = %d \n", signum, pid);
    kill(pid, signum);
    printf("send signal ok\n");
    return 0;
}

4. 信号量

信号量(semaphore)是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

4.1 特点

  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  • 支持信号量组。

4.2 函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);

4.3 信号量代码示例

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>

//联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

void pGetKey(int id) {
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op = -1;
    set.sem_flg = SEM_UNDO;
    semop(id, &set, 1);
    printf("get key\n");
}

void pPutBackKey(int id) {
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op = 1;
    set.sem_flg = SEM_UNDO;
    semop(id, &set, 1);
    printf("put back the key\n");
}

int main() {
    int semid;
    key_t key = ftok(".", 2);

    //1. 获取或创建信号量
    semid = semget(key, 1, IPC_CREAT | 0666);

    union semun initsem;
    initsem.val = 0;

    //2. 初始化信号量
    semctl(semid, 0, SETVAL, initsem);

    int pid = fork();
    if (pid > 0) {
        //4. 拿锁
        pGetKey(semid);
        printf("this is father\n");

        //5. 还锁
        pPutBackKey(semid);

        //6. 销毁锁
        semctl(semid, 0, IPC_RMID);
    } else if (pid == 0) {
        printf("this is child\n");
        //3. 放锁
        pPutBackKey(semid);
    } else {
        printf("fork error\n");
    }

    return 0;
}

5. 共享内存

共享内存(shared memory)指两个或多个进程共享一个给定的存储区。

5.1 特点

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存储,而不需要任何数据的拷贝。
  • 只能单独一个进程写或读,如果 A 和 B 进程同时写,会造成数据的混乱(需要搭配信号量来使用)。

5.2 函数原型

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

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

5.3 共享内存代码示例

shm_write.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    int shmid;
    char *shmaddr;
    key_t key = ftok(".", 1);

    // 创建共享内存
    shmid = shmget(key, 1024 * 4, IPC_CREAT | 0600);
    if (shmid == -1) {
        printf("create shm fail\n");
        exit(-1);
    }

    // 连接映射共享内存
    shmaddr = shmat(shmid, 0, 0);
    printf("shmat OK\n");

    // 将数据拷贝到共享内存
    strcpy(shmaddr, "hello world\n");

    sleep(5); // 等待 5 秒,避免一下子断开连接。等待另外一个进程读完。

    // 断开共享内存连接
    shmdt(shmaddr);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, 0);

    printf("quit\n");
    return 0;
}

shm_get.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    int shmid;
    char *shmaddr;
    key_t key = ftok(".", 1);

    // 打开共享内存
    shmid = shmget(key, 1024 * 4, 0);
    if (shmid == -1) {
        printf("create shm fail\n");
        exit(-1);
    }

    // 连接并映射共享内存
    shmaddr = shmat(shmid, 0, 0);
    printf("get from shm_write message is: %s", shmaddr);

    // 断开共享内存连接
    shmdt(shmaddr);

    printf("quit\n");
    return 0;
}

6. 结合消息队列、共享内存、信号量的示例

6.1 代码示例

get.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 初始化信号量
int init_sem(int sem_id, int value) {
    union semun tmp;
    tmp.val = value;
    if (semctl(sem_id, 0, SETVAL, tmp) == -1) {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P 操作
int sem_p(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = -1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V 操作
int sem_v(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = 1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id) {
    union semun tmp;
    if (semctl(sem_id, 0, IPC_RMID, tmp) == -1) {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int create_sem(key_t key) {
    int sem_id;
    if ((sem_id = semget(key, 1, IPC_CREAT | 0666)) == -1) {
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  // 初值设为 1 资源未占用
    return sem_id;
}

int main() {
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct shmid_ds buf1;  // 用于删除共享内存
    struct msqid_ds buf2;  // 用于删除消息队列
    struct msg_form msg;   // 消息队列用于通知对方更新了共享内存

    // 获取 key 值
    if ((key = ftok(".", 'z')) < 0) {
        perror("ftok error");
        exit(1);
    }

    // 创建共享内存
    if ((shmid = shmget(key, 1024, IPC_CREAT | 0666)) == -1) {
        perror("Create Shared Memory Error");
        exit(1);
    }

    // 连接共享内存
    shm = (char *)shmat(shmid, 0, 0);
    if ((int)shm == -1) {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT | 0777)) == -1) {
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    semid = create_sem(key);

    // 读数据
    while (1) {
        msgrcv(msqid, &msg, 1, 888, 0);  // 读取类型为 888 的消息
        if (msg.mtext == 'q')  // quit - 跳出循环
            break;
        if (msg.mtext == 'r')  // read - 读共享内存
        {
            sem_p(semid);
            printf("%s\n", shm);
            sem_v(semid);
        }
    }

    // 断开连接
    shmdt(shm);

    // 删除共享内存、消息队列、信号量
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);

    return 0;
}

send.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P 操作
int sem_p(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = -1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V 操作
int sem_v(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = 1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

int main() {
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1;  // while 循环条件

    // 获取 key 值
    if ((key = ftok(".", 'z')) < 0) {
        perror("ftok error");
        exit(1);
    }

    // 获取共享内存
    if ((shmid = shmget(key, 1024, 0)) == -1) {
        perror("shmget error");
        exit(1);
    }

    // 连接共享内存
    shm = (char *)shmat(shmid, 0, 0);
    if ((int)shm == -1) {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, 0)) == -1) {
        perror("msgget error");
        exit(1);
    }

    // 获取信号量
    if ((semid = semget(key, 0, 0)) == -1) {
        perror("semget error");
        exit(1);
    }

    // 写数据
    printf("***************************************\n");
    printf("*                 IPC                 *\n");
    printf("*    Input r to send data to server.  *\n");
    printf("*    Input q to quit.                 *\n");
    printf("***************************************\n");

    while (flag) {
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch (c) {
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  // 访问资源
                scanf("%s", shm);
                sem_v(semid);  // 释放资源
                // 清空标准输入缓冲区
                while ((c = getchar()) != '\n' && c != EOF);
                msg.mtype = 888;
                msg.mtext = 'r';  // 发送消息通知服务器读数据
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                // 清空标准输入缓冲区
                while ((c = getchar()) != '\n' && c != EOF);
        }
    }

    // 断开连接
    shmdt(shm);

    return 0;
}

7. 对比总结

通过上述对比可以看出,各种 IPC 方式各有优劣,选择合适的方式进行进程间通信可以提高程序的效率和可靠性。

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

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

相关文章

44 - 50题高级字符串函数 / 正则表达式 / 子句 - 高频 SQL 50 题基础版

目录 1. 相关知识点2.例子2.44 - 修复表中的名字2.45 - 患某种疾病的患者2.46 - 删除重复的电子邮箱2.47 - 第二高的薪水2.48 - 按日期分组销售产品2.49 - 列出指定时间段内所有的下单产品2.50 - 查找拥有有效邮箱的用户 1. 相关知识点 相关函数 函数含义concat()字符串拼接upp…

MT6989(天玑9300)芯片性能参数_MTK联发科5G处理器

MT6989是联发科Dimensity旗舰系列的成员&#xff0c;旨在为旗舰5G智能手机供应商提供最先进的技术和性能。MT6989也是联发科目前最具创新和强大的5G智能手机芯片&#xff0c;具有领先的功耗效率&#xff0c;无与伦比的计算架构&#xff0c;有史以来最快和最稳定的5G调制解调器&…

MySQL之主从同步、分库分表

1、主从同步的原理 MySQL主从复制的核心是二进制日志 二进制日志&#xff08;binlog&#xff09;记录了所有DDL语句和DML语句&#xff0c;但不包括数据查询&#xff08;select、show&#xff09;语句。 1.1、复制分三步 master主库在事务提交时&#xff0c;会把数据变更记录…

九浅一深Jemalloc5.3.0 -- ②浅*size class

目前市面上有不少分析Jemalloc老版本的博文&#xff0c;但5.3.0却少之又少。而且5.3.0的架构与之前的版本也有较大不同&#xff0c;本着“与时俱进”、“由浅入深”的宗旨&#xff0c;我将逐步分析Jemalloc5.3.0的实现。 另外&#xff0c;单讲实现代码是极其枯燥的&#xff0c;…

mmap()函数和munmap()函数的例子

代码&#xff1a; #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <stdio.h> #include <unistd.h>#define FILELENGTH 80 int main(void) {int fd-1;char …

Objective-C使用块枚举的细节

对元素类型的要求 在 Objective-C 中&#xff0c;NSArray 只能存储对象类型&#xff0c;而不能直接存储基本类型&#xff08;例如 int&#xff09;。但是&#xff0c;可以将基本类型封装在 NSNumber 等对象中&#xff0c;然后将这些对象存储在 NSArray 中。这样&#xff0c;en…

爬虫中如何创建Beautiful Soup 类的对象

在使用 lxml 库解析网页数据时&#xff0c;每次都需要编写和测试 XPath 的路径表达式&#xff0c;显得非常 烦琐。为了解决这个问题&#xff0c; Python 还提供了 Beautiful Soup 库提取 HTML 文档或 XML 文档的 节点。 Beautiful Soup 使用起来很便捷&#xff0c;…

Web后端开发概述环境搭建项目创建servlet生命周期

Web开发概述 web开发指的就是网页向后再让发送请求,与后端程序进行交互 web后端(javaEE)程序需要运行在服务器中 这样前端才可以对其进行进行访问 什么是服务器? 解释1: 服务器就是一款软件,可以向其发送请求,服务器会做出一个响应.可以在服务器中部署文件&#xff0c;让…

使用世界变换的逆转置矩阵对法线进行变换

法向量变换细节记录 最近在做法向量变换的时候&#xff0c;踩了两个坑&#xff0c;记录一下相关的知识点 法向量做变换&#xff0c;最后一位是补0 我们知道&#xff0c;顶点在做变换的时候最后一位是 1.0&#xff0c;法线最后一位是补0.0 vec3 normCurrent (getMatrixWorld() …

【NodeJs】入门

目录 一、前导 二、 url模块 三、path模块 四、buffer模块 五、fs模块 六、stream流模块 七、os模块 八、crypto模块 九、util模块 十、http模块 nodejs官网 Node.js — 在任何地方运行 JavaScript nmp是Node.js包管理器&#xff0c;用来安装各种库、框架和工具&…

基于STM32的八位数码管显示和闹钟计时【Proteus仿真】

某鱼&#xff1a;两栖电子 一、系统功能 采用矩阵键盘&#xff0c;按下对应的数字再按下确认按键&#xff0c;数码管会显示自己输入的数字&#xff0c;如果按错可以使用删除按钮进行删除。点击计时按钮可以显示当前的时间。 二、使用器件 DS1302实时时钟芯片&#xff0c;8位数…

Mac虚拟机软件有什么用?

随着苹果M系列芯片电脑的推出&#xff0c;虚拟机的使用变得越来越流行。不同于苹果以往的Intel处理器电脑&#xff0c;其M系列芯片电脑无法安装双系统。如果要使用非macOS系统&#xff0c;可以通过创建虚拟机系统的方式实现。那么&#xff0c;虚拟机软件有什么作用和用途&#…

DP(动态规划)【3】 最长公共子序列 最长回文子串

目录 1.最长公共子序列 状态转移方程需要二维数组&#xff0c;1-dim已经不太够了 又是这个问题&#xff1a;如何读入字符串 2.最长回文子串 1.最长公共子序列 状态转移方程需要二维数组&#xff0c;1-dim已经不太够了 这里dp[i][j]是说S的前i位与T的前j位公共序列&#xff…

韩顺平0基础学java——第34天

p675-689 UDP网络编程 1.类 DatagramSocket和 DatagramPacket[数据包/数据报]实现了基于UDP协议网络程序。 2.UDP数据报通过数据报套接字DatagramSocket发送和接收&#xff0c;系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。 3.DatagramPacket对象…

FastAPI教程III

本文参考FastAPI教程https://fastapi.tiangolo.com/zh/tutorial 这部分暂无需求的没有记录&#xff0c;仅放置标题。 依赖项 安全性 中间件 你可以向FastAPI应用添加中间件。 ”中间件“是一个函数&#xff0c;它在每个请求被特定的路径操作处理之前&#xff0c;以及在每个…

植物大战僵尸融合版最新版2024蓝飘飘fly

亲爱的花园守护者们&#xff0c;是否已经厌倦了传统塔防游戏的老套模式&#xff1f;是否渴望在熟悉的《植物大战僵尸》中寻找全新的刺激体验&#xff1f;那么&#xff0c;让我们一起走进《植物大战僵尸融合版》的异想世界&#xff0c;开启一场别开生面的园艺之战吧&#xff01;…

区间动态规划——最长回文子序列长度(C++)

把夜熬成粥&#xff0c;然后喝了它。 ——2024年7月1日 书接上回&#xff1a;区间动态规划——最长回文子串&#xff08;C&#xff09;-CSDN博客&#xff0c;大家有想到解决办法吗&#xff1f; 题目描述 给定一个字符串s&#xff08;s仅由数字和英文大小写字母组成&#xff0…

以太网交换机原理

没有配置&#xff0c;比较枯燥&#xff0c;二可以认识线缆&#xff0c; 三比较重要&#xff0c;慢慢理解&#xff0c;事半功倍。 各位老少爷们&#xff0c;在下给大家说段以太网交换机原理&#xff0c;说得不好大家多多包涵&#xff0c;说得好呢&#xff0c;大家叫个好&#x…

Debugging using Visual Studio Code

One of the key features of Visual Studio Code is its great debugging support. VS Code’s built-in debugger helps accelerate your edit, compile, and debug loop. Debugger extensions VS Code 内置了对 Node.js 运行时的调试支持,可以调试 JavaScript、TypeScript…

Web3 前端攻击:原因、影响及经验教训

DeFi的崛起引领了一个创新和金融自由的新时代。然而&#xff0c;这种快速增长也吸引了恶意行为者的注意&#xff0c;他们试图利用漏洞进行攻击。尽管很多焦点都集中在智能合约安全上&#xff0c;但前端攻击也正在成为一个重要的威胁向量。 前端攻击的剖析 理解攻击者利用前端漏…