【Linux】System V消息队列 System V信号量

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 前言
  • 一、消息队列 (了解)
      • 1.1 原理
      • 1.2 消息队列的数据结构
      • 1.3 系统调用接口
        • 1.3.1 msgget - 创建消息队列
        • 1.3.2 msgctl - 释放消息队列
        • 1.3.3 msgsnd - 发送数据块
        • 1.3.4 msgrcv - 接收数据块
      • 1.4 小结
  • 二、信号量
      • 2.1 前置概念:互斥、临界资源等概念(重点)
      • 2.2 理解信号量(重点)
      • 2.3 总结一波
      • 2.4 系统调用接口(了解)
        • 2.4.1 semget - 创建信号量
        • 2.4.2 semctl - 释放
        • 2.4.3 semop - 操作
      • 2.5 信号量凭什么是进程间通信的一种?
      • 2.6 信号量的数据结构
  • 三、深入理解 System V 通信方式 (重点)

前言

System V通信标准中,还有一种通信方式:消息队列,以及一种实现互斥的工具:信号量;随着时代的发展,这些陈旧的标准都已经较少使用了,但作为IPC中的经典知识,我们可以对其做一个简单了解。尤其是 信号量,可以通过它,为以后多线程学习中POSIX信号量的学习做铺垫

一、消息队列 (了解)

1.1 原理

进程间通信的本质是:要让双方进程看到同一块资源。那么对于System V消息队列,操作系统首先就要在内核中创建一个队列(数据结构),再通过某种手段将两个或多个进程看到同一个队列后,即可通信

  • 进程A发送数据是以数据块的形式发送到消息队列中。
  • 进程B同样是以数据块的形发送到消息队列中。
  • 注意:System V消息队列允许多个进程双向进行通信,而管道通常只能单向通信

在这里插入图片描述

  • 但有一个问题:那消息队列中存放着不同进程发送的数据块,那如何判断该数据块是由哪个进程接收呢?

发送消息时,接收进程通常是根据消息类型来判断消息的来源。

当然了,消息队列跟共享内存一样,是由操作系统创建的,其生命周期不随进程,因此在使用结束后需要手动释放,不然会导致内存泄漏!

1.2 消息队列的数据结构

而我们知道,因为系统中不止一对进程在进行通信,可能会存在多个,那么操作系统就要在内核中开辟多个消息队列,那么操作系统就必须对这些消息队列进行管理,这又得搬出管理的六字真言:先描述,再组织。在Unix/Linux中,描述消息队列的信息通常通过struct msqid_ds结构体来表示:

  • struct msqid_ds
struct msqid_ds
{// struct ipc_perm 结构包含了消息队列的所有权和权限信息。struct ipc_perm msg_perm;  // 最后一次向队列中发送消息 (msgsnd) 的时间。 time_t msg_stime;      // 最后一次从队列中接收消息 (msgrcv) 的时间。     time_t msg_rtime;     // 消息队列属性最后一次变更的时间。       time_t msg_ctime;       // 队列中当前的字节数     unsigned long __msg_cbytes;    // 队列中当前的消息数目。                           msgqnum_t msg_qnum;         // 队列中允许存放的最大字节数。                          msglen_t msg_qbytes;  // 最后一次发送消息 (msgsnd) 的进程pid。                  pid_t msg_lspid;    // 最后一次接收消息 (msgrcv) 的进程pid。pid_t msg_lrpid;           
};
  • struct ipc_perm
struct ipc_perm
{// __key用于标识 IPC 对象的键值,由用户指定。key_t __key;         // 拥有者的有效用户ID (UID),即对象的当前所有者。uid_t uid;          // 拥有者的有效组ID (GID),即对象的当前所属组。gid_t gid;        // 创建者的有效用户ID (UID),即创建对象的用户。    uid_t cuid;     // 创建者的有效组ID (GID),即创建对象的用户所属的组。      gid_t cgid;        // 对象的权限模式,定义了对象的访问权限,通常以八进制表示。    unsigned short mode;  // 序列号,用于处理 IPC 对象创建时的竞争条件。unsigned short __seq; 
};

最后再通过诸如链表、顺序表等数据结构将这些结构体对象管理起来。因此,往后我们对共享内存的管理,只需转化为对某种数据结构的增删查改。

1.3 系统调用接口

1.3.1 msgget - 创建消息队列

msgget用于创建一个新的System V消息队列或获取一个已经存在的消息队列。

函数原型如下:

#include <sys/types.h>#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);

参数说明:

  • key:消息队列的键值。这个键值用于唯一标识一个消息队列(内核层使用),多个进程可以通过相同的键值来访问同一个消息队列。通常,可以使用ftok函数来生成一个键值。

  • msgflg:这是一个标志参数,用于指定操作模式和权限。可以用操作符'|'进行组合使用。它可以是以下几个标志的组合:

    • IPC_CREAT:这个选项单独使用的话,如果申请的消息队列不存在,则创建一个新的消息队列;如果存在,获取已存在的消息队列。
    • IPC_EXCL: 一般配合IPC_CREAT一起使用(不单独使用)。他主要是检测共享内存是否存在,如果存在,则出错返回;如果不存在就创建。确保申请的消息队列一定是新的。
    • 权限标志:以与文件权限类似的方式指定消息队列的访问权限(例如0666表示所有用户可读写)。
    • 但在获取已存在的消息队列时,可以设置为0
  • 返回值:

    • 成功时返回消息队列标识符msqid。(操作系统内部分配的,提供给用户层使用,类似于文件描述符fd
    • 失败时返回 -1,并设置errno以指示错误原因。

看到这里,有没有发现以上接口和创建共享内存段shmget函数非常的像啊,至于key和消息队列标识符的区别这里就不再详细介绍了,更多细节请参考:点击跳转

接下来我们简单使用msgget函数创建消息队列,并使用 ipcs -q指令查看系统维护的消息队列的信息

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>using namespace std;const char *pathname = "/home/wj";
int proj_id = 'A';int main()
{// 使用ftok函数生成键值key_t key = ftok(pathname, proj_id);printf("key is 0x%x\n", key);// 创建消息队列int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);printf("msqid is %d\n", msqid);return 0;
}

【程序结果】

在这里插入图片描述

由于我们还没使用消息队列进行通信,所以已使用字节used-bytes和消息数messages都是0

1.3.2 msgctl - 释放消息队列

如上我们可以看见,当进程结束后,我们还是能看到消息队列在系统的相关信息。所以我们应该手动将其释放,避免内存泄漏!

释放的方法和共享内存一样有两种方法:

  • 方法一:使用以下指令
ipcrm -q <msqid>

在这里插入图片描述

  • 方法二:使用系统调用接口

msgctl函数是用于控制消息队列的函数之一,它允许程序员执行多种操作,如获取消息队列的属性、设置消息队列的属性、删除消息队列等。

具体的函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数说明:

  • msqid:消息队列的标识符。即msgget函数的返回值。

  • cmd:要执行的操作命令,可以是以下几种之一:

    • IPC_STAT:获取消息队列的状态信息,并将其存储在struct msqid_ds *buf中。
    • IPC_SET:设置消息队列的状态,从struct msqid_ds *buf中读取新的状态信息。
    • IPC_RMID:从系统中删除消息队列。
  • buf:一个指向struct msqid_ds结构的指针,用于存储或传递消息队列的状态信息。如果是删除消息队列,此参数可以设置为nullptr

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>using namespace std;const char *pathname = "/home/wj";
int proj_id = 'A';int main()
{// 使用ftok函数生成键值key_t key = ftok(pathname, proj_id);printf("key is 0x%x\n", key);// 创建消息队列int msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);printf("msqid is %d\n", msqid);// 进程结束前释放消息队列msgctl(msqid, IPC_RMID, nullptr);return 0;
}

【程序结果】

在这里插入图片描述

1.3.3 msgsnd - 发送数据块

共享内存会比消息队列多两步:挂接到各自进程的进程地址空间、取消挂接。而对于消息队列,当我们创建好资源后,就可以直接发送数据了。

msgsnd函数用于向消息队列中发送消息,其函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数说明:

  • msqid:消息队列的标识符,由msgget函数返回。
  • msgp:指向要发送的消息内容的指针,通常是用户定义的结构体指针。就是我们在原理部分说的数据块结构体。其结构如下:
struct msgbuf
{long mtype;    /* message type, must be > 0 */char mtext[1]; /* message data */
};

mtype就是传说中数据块类型,据发送方而设定;而mtex是一个比较特殊的东西:柔性数组,其中存储待发送的 信息,因为是 柔性数组,所以可以根据 信息 的大小灵活调整数组的大小。对于柔性数组,大家可以参考这篇文章:点击跳转

  • msgsz:消息的大小,以字节为单位。这个大小必须是消息队列的最大消息大小(msg_qbytes)的一个有效值,否则会导致msgsnd失败。
  • msgflg:表示发送数据块的方式,一般默认为0
  • 返回值:成功返回0,失败返回-1
1.3.4 msgrcv - 接收数据块

msgrcv函数用于从消息队列中接收消息。

其函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数说明:

  • msqid:是消息队列的标识符,由msgget函数返回。
  • msgp:是一个指向接收消息的缓冲区的指针,通常是一个用户定义的结构体指针。
  • msgsz:是接收缓冲区的大小,即可以接收的最大消息大小(字节数)。如果实际接收到的消息大小大于msgsz,则消息可能会被截断,这取决于msgflg是否设置了MSG_NOERROR
  • msgtyp:是消息类型,即从消息队列中选择接收的消息类型。如果msgtyp大于0,则只接收msgtyp 类型的消息;如果msgtyp等于0,则接收队列中的第一个消息;如果msgtyp小于0,则接收队列中小于或等于msgtyp绝对值的最高优先级的消息。
  • msgflg:表示接收数据块的方式,一般默认为0
  • 返回值:成功返回接收到的消息的大小(字节数);失败返回-1,并设置errno来指示错误的具体原因。

同样的,接收的数据结构如下所示,也包含了类型和柔性数组

struct msgbuf
{long mtype;    /* message type, must be > 0 */char mtext[1]; /* message data */
};

1.4 小结

消息队列 的大部分接口都与 共享内存 近似,所以掌握 共享内存 后,即可快速上手 消息队列。但是如你所见,System V版的消息队列 使用起来比较麻烦,并且过于陈旧,现在已经较少使用了,所以我们不必对其进行深究,知道个大概就行了 ~

二、信号量

2.1 前置概念:互斥、临界资源等概念(重点)

进程A发送消息,进程B接收消息,在整个通信的过程中可能会出现错乱问题。比方AB发送100Byte的任务信息,但是A可能才写到50ByteB进程就开始读走了,导致B进程任务信息不完整。我们称之 数据不一致问题。因此,就衍生出以下几个概念:

  1. 首先可以通过加锁的方式(多线程部分讲解) 来保证 互斥互斥本质就是:任何时刻,只允许一个执行流访问共享资源(保护共享资源免受并发访问的影响)
  2. 而这种只允许一个执行流访问(执行访问代码)的资源称做临界资源。这个临界资源一般是内存空间。(比方说管道就是一种临界资源)
  3. 我们访问临界资源的代码称做 临界区(类比代码区)

注意:在管道通信中不存在这些问题,因为管道有原子性和同步互斥,而共享内存是没有任何的保护机制的。

那么现在就可以解释一个现象:为什么多个进程(或者线程)向显示器打印各自的信息有时候会错乱。原因很简单,在Linux中,显示器是文件,当多个进程向同一个文件打印,前提是这些进程需要看到同一份资源,所以显示器文件在多进程中就是一个共享资源,而在打印的过程中并没有添加保护机制,因此会看到数据不一致,错乱问题。如果不想有这些情况,你就需要将显示器文件变成临界资源,如加锁等。

2.2 理解信号量(重点)

信号量(有的教材叫信号灯)的本质是就是计数器。这个计数器用来记录可用资源的数量

下面将一个故事来加深理解:假设一个放映厅有100个位置,对应也会售卖100张票(不考虑其他情况)。当我们买票去看电影,但是还没去观看(甚至不看),那个位置在电影的时间段永远是我们自己的。因此,买票的本质是对资源的预定机制!而每卖一张票,剩余的票数(计数器)就要减一,对应的放映厅里面的资源就要少一个。当票数的计数器减到0之后,表示资源已经被申请完毕了。

临界资源(如同放映厅)可以被划分很多小块的资源(如同放映厅里的位置),那么我们可以允许多个执行流(如同观众)来访问这份临界资源,但是最怕多个执行流会访问同一个小块的资源,一旦出现,就会发生混乱现象。因此,最好的方法就是引入一个计数器cnt,当cnt > 0 && cnt - 1,说明执行流申请资源成功(本质是对资源的预定机制),就有访问资源的权限。当cnt <= 0表示资源被申请完了,当再有执行流申请,一定会失败,除了有执行流释放(退票)。

所以每一个执行流若是要访问共享资源中的一小部分的时候,不是直接访问,而是先申请计数器资源。如同看电影需要先买票 ~

故事还没完,如果电影院的放映厅只有一个座位,我们只需要值为1的计数器,但如果有10个人想要这一个位置,那么必定要先申请计数器资源,但不变的是只有一个人能看电影,不就是只有一个执行流在访问一份临界资源,这不就是互斥访问吗?

在并发编程中,一个只能取两个状态(通常是01)的计数器被称为二元信号量。二元信号量通常被用来实现互斥访问(本质就是一个锁),即保证在任何时刻只有一个进程(或线程)能够访问临界资源。在电影院座位的故事中,计数器的两个状态可以分别表示座位的空闲(1)和已占用(0)状态。

这又有一个新的问题:要访问临界资源,先要申请计数器资源。而信号量本质是计数器,那么信号量不就是共享资源吗?

而计数器(信号量)作为保护方,要保护临界资源只允许一个执行流访问。但俗话说得好,要保护别人的安全,首先得先保证自己的安全。而对一个整数--其实并不安全,这里简单说说为什么不是安全的,等到线程部分再详谈。

--操作在C语言上是一条语句;但是这条语句在汇编语言上就是多条汇编语句,一般是三条。首先计数器的值要从内存中放到CPU的寄存器中,然后再CPU进行--操作,最后再将计算结果协会计数器变量的内存位置。而执行流在运行的时候,可以随时被切换,如果没有适当的同步措施(如互斥锁),多个执行流同时访问计数器可能会导致竞态条件。竞态条件会破坏计数器的预期行为,使其不能正确地反映实际资源的状态。

即然--都不安全,那谈何保护别人?

因此,申请信号量,本质是对计数器--,在信号量中专门封装了一个操作(方法),我们将这种操作称为P操作。如果减一后的计数器值小于零(即信号量的计数器值变为负数),那么执行流就会被阻塞,直到信号量的计数器变为正数,表示有可用资源;释放资源的同时也要释放信号量,本质是对计数器进行++操作,也叫做V操作。

需要注意的是,P操作和V操作通常需要具有 原子性其意思是一件事情要么不做,要做就做完,是两态的。没有“正在做”这样的概念。也就是说,原子性确保了多个执行流在执行--操作时,不会被其他执行流中断或干扰,而且操作要么完全执行成功,要么完全不执行,没有正在执行的说法。

2.3 总结一波

  1. 信号量本质是计数器,PV操作具有原子性。

  2. 执行流申请资源,必须先申请信号量(计数器)资源,得到信号量之后,才能访问临界资源!

  3. 信号量值10两态的。二元信号量,就是互斥功能。

  4. 申请计数器(信号量)的本质是对临界资源的预定机制!

2.4 系统调用接口(了解)

信号量的系统调用挺“恶心”的,大家了解就行~

2.4.1 semget - 创建信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);
组成部分含义
返回值 int创建成功返回信号量集的 semid,失败返回 -1
参数1 key_t key创建信号量集时的唯一 key 值,通过函数 ftok 计算获取
参数2 int nsems待创建的信号量个数,这也正是 集 的来源
参数3 int semflg位图,可以设置消息队列的创建方式及创建权限

除了参数2,其他基本与另外俩兄弟一模一样,实际传递时,一般传 1,表示只创建一个 信号量

2.4.2 semctl - 释放

老生常谈的两种释放方式:指令释放、函数释放

  • 指令释放:直接通过指令ipcrm -s <semid>释放信号量集。你还可以使用ipcs -s来查看系统中信号量的相关信息。
  • 通过函数释放。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
组成部分含义
返回值 int成功返回 0,失败返回 -1
参数1 int semid待控制的信号量集 id
参数2 int semnum表示对信号量集中的第 semnum 个信号量作操作
参数4 ...可变参数列表,不止可以获取信号量的数据结构,还可以获取其他信息
2.4.3 semop - 操作

信号量的操纵比较ex,也比较麻烦,所以仅作了解即可

使用 semop 函数对 信号量 进行诸如 +1、-1 的基本操作。

 #include <sys/types.h>#include <sys/ipc.h>#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
组成部分含义
返回值 int成功返回 0,失败返回 -1
参数1 int semid待操作的信号量集 id
参数2 struct sembuf *sops一个比较特殊的参数,需要自己设计结构体
参数3 unsigned nsops可以简单理解为信号量编号

重点在于参数2,这是一个结构体,具体成员如下:

unsigned short sem_num;  /* semaphore number */
short          sem_op;   /* semaphore operation */
short          sem_flg;  /* operation flags */

其中包含信号量编号、操作等信息,需要我们自己设计出一个结构体,然后传给semop函数使用。

可以简单理解为:sem_op 就是要进行的操作,如果将 sem_op 设为 -1,表示信号量 -1(申请),同理 +1 表示信号量 +1(归还)

sem_flg 是设置动作,一般设为默认即可

当然这些函数我们不必深入去研究,知道个大概就行了

2.5 信号量凭什么是进程间通信的一种?

讲了这么多信号量的知识,我们并没有发现信号量能传数据进行通信,而是作为一个计数器。

这里就要解释一下了,通信并不仅仅在于数据的传递,也在于双方互相协同

补充什么是协同:双方或多方在通信或合作过程中,通过相互配合、相互支持、相互理解和相互作用,共同达成某种目标。

虽然协同不是以传输数据为目的,但是它是以事件通知为目的,它的本质也是在传递信息,只是没那么容易感知到而已。

因此,协同本质也是通信,信号量首先要被所有的通信进程看到。

2.6 信号量的数据结构

Linux中,可以通过man semctl进行查看

  • struct semid_ds
struct semid_ds
{struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime;         /* Last semop time */time_t sem_ctime;         /* Last change time */unsigned long sem_nsems;  /* No. of semaphores in set */
};
  • struct ipc_perm
struct ipc_perm
{key_t __key;          /* Key supplied to semget(2) */uid_t uid;            /* Effective UID of owner */gid_t gid;            /* Effective GID of owner */uid_t cuid;           /* Effective UID of creator */gid_t cgid;           /* Effective GID of creator */unsigned short mode;  /* Permissions */unsigned short __seq; /* Sequence number */
};

显然,无论是 共享内存、消息队列、信号量,它们的ipc_perm结构体中的内容都是一模一样的,结构上的统一可以带来管理上的便利,具体原因可以接着往下看。

三、深入理解 System V 通信方式 (重点)

接下来我们再来详细说说IPC资源在内核中是怎么管理的。

在这里插入图片描述

如上我们发现:操作系统描述IPC对象(共享内存、消息队列、信号量)的数据结构的第一个字段的第一个成员都是struct ipc_perm类型成员变量

这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组(或链表等数据结构)来管理所有的IPC对象,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。

在这里插入图片描述

这是因为IPC对象的增、删、查、改操作与struct ipc_perm结构体相关,struct ipc_perm包含了IPC对象的权限信息。这些权限信息对于操作系统来说是非常重要的,它决定了哪些进程可以访问、操作这些IPC对象。因此,往后我们对IPC对象的增、删、查和改操作,就转化为对数组的增、删、查和改操作。而数组下标,就是IPC对象的标识符。(类似于文件描述符fd

就比方说通过共享内存段标识符在数组中找到其struct ipc_perm对象,而当我们需要访问其struct shmid_ds成员变量时,只需将struct ipc_perm*强制转化为struct shmid_ds*即可访问。

而操作系统为什么能知道要转化哪个IPC对象?可以这么理解:

  • 在用户角度,操作(增、删、查、改)IPC对象时会使用struct ipc_perm结构体来描述对象的权限和所有者信息。这是给开发者和应用程序使用的接口,用来传递创建和访问IPC对象的参数。
  • 但从内核角度出发,真正管理IPC对象的是kern_ipc_perm结构体(或类似的结构体)。内核会在创建IPC对象时使用特定的系统调用(如 msggetshmgetsemget)来分配和初始化相应的 kern_ipc_perm结构体。这些结构体通常包含一个类型标志位字段,用于标识这个IPC对象的类型。

那这不就是多态的思想吗?struct ipc_perm充当基类,其他的IPC对象数据结构充当子类,它们继承了 struct ipc_perm的属性,并且增加了特定于每种 IPC 对象类型的信息和操作。指针指向谁就调用谁。

至此,进程间通信的知识点就到此结束啦~

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

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

相关文章

Spark调度底层执行原理详解(第35天)

系列文章目录 一、Spark应用程序启动与资源申请 二、DAG&#xff08;有向无环图&#xff09;的构建与划分 三、Task的生成与调度 四、Task的执行与结果返回 五、监控与容错 六、优化策略 文章目录 系列文章目录前言一、Spark应用程序启动与资源申请1. SparkContext的创建2. 资…

Python | Leetcode Python题解之第233题数字1的个数

题目&#xff1a; 题解&#xff1a; class Solution:def countDigitOne(self, n: int) -> int:# mulk 表示 10^k# 在下面的代码中&#xff0c;可以发现 k 并没有被直接使用到&#xff08;都是使用 10^k&#xff09;# 但为了让代码看起来更加直观&#xff0c;这里保留了 kk,…

Excel第31享:基于left函数的截取式数据裂变

1、需求描述 如下图所示&#xff0c;在“Excel第30享”中统计2022年YTD各个人员的“上班工时&#xff08;a2&#xff09;”&#xff0c;需要基于工时明细表里的“日期”字段建立辅助列&#xff0c;生成“年份”字段&#xff0c;本文说明“年份”字段是怎么裂变而来的。 下图为…

图像处理:使用 OpenCV-Python 卡通化你的图像(2)

一、说明 在图像处理领域&#xff0c;将图像卡通化是一种新趋势。人们使用不同的应用程序将他们的图像转换为卡通图像。如今&#xff0c;玩弄图像是许多人的爱好。人们通常会点击图片并添加滤镜或使用不同的东西自定义图像并将其发布到社交媒体上。但我们是程序员&#xff0c;…

godis源码分析——Redis协议解析器

前言 redis这个目录下的所有代码就是为了一个事情&#xff0c;就是适配redis。 流程 redis下的基本流程 源码 在redis/client/client.go 主要是客户端处理 package clientconst (created iotarunningclosed )type B struct {data chan stringticker *time.Ticker }// …

Docker安装RabbitMQ(带web管理端)

1.拉取带web管理的镜像 可以拉取rabbitmq对应版本的web管理端&#xff0c;比如&#xff1a;rabbitmq:3.9.11-management&#xff0c;也可以直接拉取带web管理端的最新版本 rabbitmq:management. docker pull rabbitmq:3.9.11-management 注意&#xff1a;如果docker pull ra…

jenkins系列-06.harbor

https://github.com/goharbor/harbor/releases?page2 https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz harbor官网&#xff1a;https://goharbor.io/ 点击 Download now 链接&#xff0c;会自动跳转到上述github页面&am…

C++ | Leetcode C++题解之第233题数字1的个数

题目&#xff1a; 题解&#xff1a; class Solution { public:int countDigitOne(int n) {// mulk 表示 10^k// 在下面的代码中&#xff0c;可以发现 k 并没有被直接使用到&#xff08;都是使用 10^k&#xff09;// 但为了让代码看起来更加直观&#xff0c;这里保留了 klong l…

Redis系列命令更新--Redis哈希命令

一、设置密码验证&#xff1a; 使用文本编辑器&#xff0c;这里使用Notepad&#xff0c;打开Redis服务配置文件。 注意&#xff1a;不要找错了&#xff0c;通常为redis.windows-service.conf&#xff0c;而不是redis.windows.conf。后者是以非系统服务方式启动程序使用的配置…

《BASeg: Boundary aware semantic segmentation for autonomous driving》论文解读

期刊&#xff1a;Neural Networks | Journal | ScienceDirect.com by Elsevier 年份&#xff1a;2023 代码&#xff1a;https://github.com/Lature-Yang/BASeg 摘要 语义分割是自动驾驶领域街道理解任务的重要组成部分。现有的各种方法要么专注于通过聚合全局或多尺度上下文…

旷野之间20 - Google 研究的推测 RAG

为什么选择 RAG 新兴能力 直到最近&#xff0c;人们发现 LLM 具有新兴能力&#xff0c;即在与用户或任务交互过程中出现的意外功能。 这些功能的示例包括&#xff1a; 解决问题&#xff1a; LLM 可以利用其语言理解和推理能力&#xff0c;为未经过明确培训的任务提供富有洞…

python的字符串

字符串 简单操作 创建 利用 ‘ ’ 或 “ ” 将字符或数字包裹起来的都为字符串 a"你好" 格式化字符串 元组的字符格式化 字符串格式化函数 srt.format() f格式化 方法 split()//指定分割符经行分割 strip()//指定移除字符头尾的字符 join()//指定序列中的字符连接成新…

5、 测试

这里写目录标题 1、自动化测试简介&#xff08;1&#xff09;自动化测试是什么&#xff08;2&#xff09;为什么要写测试测试节约你的时间发现错误&#xff0c;预防错误测试使得代码更有吸引力 2、基础测试策略3、开始写第一个测试&#xff08;1&#xff09;首先得有个bug&…

Not Invented Here 不是在这里发明的 / Proudly found elsewhere 自豪地在其他地方找到

注&#xff1a; 机翻&#xff0c;未校对。 两篇关于创新管理的小文章 Not Invented Here 不是在这里发明的 In the history of organizational success, the enterprises that dominate tend to flawlessly execute on ideas that were created elsewhere. Examine just abo…

智慧水利解决方案:从理论到实践的全面跨越,展示其在水资源管理、水灾害预警、水生态保护等方面的创新应用

目录 一、引言&#xff1a;智慧水利的时代背景与意义 二、智慧水利的理论框架与技术体系 1、理论框架 2、技术体系 三、智慧水利在水资源管理中的应用 1、水资源优化配置 2、水量水质协同管理 四、智慧水利在水灾害预警中的应用 1、洪水预警与应急响应 2、干旱监测与评…

Mediapipe-姿态估计实例

Mediapipe简介 Mediapipe 是由 Google Research 开发的一款开源框架&#xff0c;旨在帮助开发者轻松地构建、测试和部署复杂的多模态、多任务的机器学习模型。它特别擅长于实时处理和分析音频、视频等多媒体数据。以下是 Mediapipe 的一些关键特点和组件&#xff1a; 关键特点…

基于微信小程序的音乐播放平台

基于微信小程序的音乐播放平台 音乐播放小程序项目简介技术栈功能模块项目流程系统E-R图项目页面 音乐播放小程序 项目简介 微信音乐小程序旨在提供一个简洁高效的音乐播放平台&#xff0c;用户可以方便地搜索、播放和收藏自己喜欢的音乐。整个项目采用前后端分离的架构&…

WIN10开机突然,过一会就自动重启蓝屏DRIVER_IRQL_NOT_LESS_OR_EQUAL

环境&#xff1a; Win10 专业版 DELL7080 问题描述&#xff1a; WIN10开机突然&#xff0c;过一会就自动重启蓝屏DRIVER_IRQL_NOT_LESS_OR_EQUAL 事件日志 解决方案&#xff1a; 1.找到MEMORY.DMP文件内容&#xff0c;分析一下 Microsoft (R) Windows Debugger Version 10…

主机安全-开源HIDS字节跳动Elkeid安装使用

目录 概述什么是HIDSHIDS与NIDS的区别EDR、XDR是啥&#xff1f; Elkeid架构Elkeid Agent && Agent centerElkeid DriverElkeid RASPElkeid HUBService DiscoveryManager安装数据采集规则&告警 参考 概述 什么是HIDS HIDS&#xff08; host-based intrusion detec…

使用Gitee仓库镜像管理功能实现Gitee与Github 双向同步

进入你所需要同步的仓库&#xff0c;点击「管理」->「镜像仓库管理」&#xff0c;点击「添加镜像」选项&#xff1b; 如果你的Gitee账号还没有绑定过 GitHub 帐号&#xff0c;先根据弹窗的提示绑定 GitHub 帐号&#xff1b; 添加镜像时候&#xff0c;在「镜像方向」中选择…