System V IPC+消息队列

news2024/11/15 12:01:26

多进程与多线程

使用有名管道实现双向通信时,由于读管道是阻塞读的,为了不让“读操作”阻塞“写操作”,使用了父子进程来多线操作,
1)父进程这条线:读管道1
2)子进程这条线:写管道2

实际上:凡是涉及到多线操作的,基本都使用多线程来实现

1)主线程:读管道1
2)次线程:写管道2
  • 线程和进程都是并发运行的,但是线程和进程各自的使用的场合有所不同

    多线使用多线程更省计算机cpu和内存的开销

    创建出并发运行的线程目的-----------多线操作

程序必须要去运行一个新程序时,此时必须涉及到多进程

  • 这里并发运行的主要目的并不是为了多线操作,而是为了单独的去执行新程序

执行新程序时,我们只能使用多进程来操作,因为线程是不可能去执行一个新程序的

System V IPC

无名管道和有名管道都是UNIX系统早期提供的比较原始的一种进程间通信方式

后来Unix系统升级到第5版本时,又提供了三种新的IPC通信方式

  • 消息队列
  • 信号量
  • 共享内存

System V就是系统第5版本的意思

后来的Linux也继承了unix的这三个通信方式

管道的机制

  • 管道的本质就是一段缓存
  • Linux OS内核是以文件的形式来管理管道

我们都是使用文件描述符文件的形式来操作:无名管道和有名管道

操作管道时:
除了pipe和mkfifo这两个函数外,其它的像read、write、open都是文件io函数

System V IPC

System V IPC与管道有所不同:

它完全使用了不同的实现机制,与文件没任何的关系

也就是说内核不再以文件的形式来管理System V IPC

  • 对于System V IPC,OS内核提供了全新的API
  • System V IPC时,不存在亲缘进程一说,任何进程之间都可以使用System V IPC来通信

System V IPC标识符

  • 这个“标识符”就是文件描述符的替代者,但是它是专门给System V IPC使用的
  • 不能使用文件IO函数来操作“标识符”,只能使用System V IPC的特有API才能操作
如何得到ipc标识符

调用某API创建好某个“通信结构”以后,API就会返回一个唯一的“标识符”

比如创建好了一个“消息队列”后,创建的API就会返回一个唯一标识消息队列的“标识符”

ipc标识符作用

如果创建的是消息队列的话:

  • 进程通过消息队列唯一的标识符,就能找到创建好的“消息队列”

  • 使用这个消息队列,进程就能读写数据,从而实现进程间通信

可以读写数据就是实现了通信

也就是标识符就是可是识别传建好的system V IPC

消息队列

本质

消息队列的本质:由内核创建的用于存放消息的链表

由于是存放消息的,所以把这个链表称为了消息队列

如何存放消息

消息队列这个链表有很多的节点,链表上的每一个节点就是一个消息

  • 注意是一个双向链表
  • 每个消息由两部分组成
    • 1)消息编号:识别消息用
    • 2)消息正文:真正的信息内容

发送接收消息过程

1.发送消息

(a)进程先封装一个消息包

(b)调用相应的API发送消息

这个消息包其实就是如下类型的一个结构体变量:
----封包时将消息编号和消息正文写到结构体的成员中
struct msgbuf{
				long mtype;         /* 放消息编号,必须> 0 */
				char mtext[msgsz];   /* 消息内容(消息正文) */
			};	
b过程:
1.调用API时通过“消息队列的标识符”找到对应的消息队列
2.将消息包发送给消息队列,消息包会被作为一个链表节点插入链表
2.接收消息

调用API接收消息时,必须传递两个重要的信息

  1. 消息队列标识符
  2. 你要接收消息的编号

有了这两个信息:

API就可以找到对应的消息队列,然后从消息队列中取出你所要编号的消息

收到了别人所发送的信息,实现了通信

“消息队列”有点像信息公告牌:
--------发送信息的人把某编号的消息挂到公告牌上
--------接收消息的人自己到公告牌上去取对应编号的消息
如此,发送者和接受者之间就实现了通信

----使用消息队列实现网状交叉通信很容易

----消息队列作为媒介,一个往双向链表发数据,一个根据编号取出数据

消息队列使用步骤

  1. 使用msgget函数

    • 消息队列不存在:创建新的消息队列

    • 消息队列存在:获取已存在的某个消息队列,并返回唯一标识消息队列的标识符(msqID)

      后续收发消息就是使用msqID这个标识符来实现的

  2. 收发消息

    • 发送消息:使用msgsnd函数,利用消息队列标识符发送某编号的消息
    • 接收消息:使用msgrcv函数,利用消息队列标识符接收某编号的消息
  3. 使用msgctl函数,利用消息队列标识符删除消息队列

对于使用消息队列来通信的多个进程来说:
        只需要一个进程来创建消息队列就可以了
对于其它要参与通信的进程来说:
        直接使用这个创建好的消息队列即可

  • 为了保证消息队列的创建,让每一个进程都包含创建消息队列的代码,谁先运行就由谁创建

  • 后运行的进程如果发现它想用的那个消息队列已经创建好了,就直接使用

  • 当众多进程共享操作同一个消息队列时,即可实现进程间的通信。

消息队列API

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//功能:利用key值创建、或者获取一个消息队列
/*
1.如果key没有对应任何消息队列,那就创建一个新的消息队列
2.如果key已经对应了某个消息队列,说明消息队列已经存在了,那就获取这个消息队列来使用
*/
//msgflg:指定创建时的原始权限,比如0664
int msgget(key_t key, int msgflg);

key值--------用于为消息队列生成(计算出)唯一的消息队列ID

  • 第一种:指定为IPC_PRIVATE宏

    指定这个宏后,每次调用msgget时都会创建一个新的消息队列

  • 第二种:可以自己指定一个整形数,但是容易重复指定

  • 第三种:使用ftok函数来生成key

    #include <sys/types.h>
    #include <sys/ipc.h>
    /*
    ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,
    ---------只要路径名和整形数不变,所对应的key值就唯一不变的
    */
    //ftok只会使用整形数(proj_id)的低8位,因此我们往往会指定为一个ASCII码值
    key_t ftok(const char *pathname, int proj_id);
    

    创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项

    msgid = msgget(key, 0664|IPC_CREAT);
    
    • 创建一个新的消息队列,此时就会用到msgflg参数

多个进程如何共享同一个消息队列

  1. 创建进程
  • 创建者使用"./file", 'a’生成一个key值
  • 然后调用msgget创建了一个消息队列
key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);

当创建者得到msgid后,即可操作消息队列

  1. 实现共享

只要能拿到别人创建好的消息队列的ID,即可共享操作同一个消息队列,实现进程间通信

获取别人创建好的消息队列的ID,有两个方法:

a)创建者把ID保存到某文件,共享进程读出ID即可

这种情况下,共享进程根本不需要调用msgget函数来返回ID

b)调用msgget获取已在消息队列的ID

  • 使用ftok函数,利用与创建者相同的“路径名”和8位整形数,生成相同的key值
  • 调用msgget函数,利用key找到别人创建好的消息队列,返回ID
key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);						

这种方法是最常用的方法,因为ftok所用到的“路径名”和“8位的整形数”比较好记忆

代码演示

创建一个消息队列

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MSG_FIFE "./msgfile"
int  create_or_get_msgque(){
    //创建消息队列,返回msgid
     int msgId=-1;
     key_t key=-1;
     int fd=0;
     //为了用到文件路径名,用open创建
     fd=open(MSG_FIFE,O_RDWR|O_CREAT,0664);
     //用生成的路径+asc码生成唯一整数
     key=ftok(MSG_FIFE,'a');
     msgId=msgget(key,0664|IPC_CREAT);
     return msgId;
}
int main(void){
    int msgID=-1;
    int ret=-1;
    msgID=create_or_get_msgque();
    /*父子进程收发消息*/
    ret=fork();
    if(ret>0){//父进程发送消息

    }
    else if(ret==0){//子进程接收消息

    }
    return 0;
}

ipcs命令:

  • a 或者 什么都不跟:消息队列、共享内存、信号量的信息都会显示出来
  • m:只显示共享内存的信息
  • q:只显示消息队列的信息
  • s:只显示信号量的信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awKLEzWs-1669027002168)(/home/guojiawei/.config/Typora/typora-user-images/image-20221120214437592.png)]

system v ipc的缺点:

进程结束时,system v ipc不会自动删除,进程结束后,使用ipcs依然能够查看到。

如何删除消息队列

  • 方法1:重启OS,很麻烦
  • 方法2:进程结束时,调用相应的API来删除
  • 方法3:使用ipcrm命令删除

ipcrm命令

 删除共享内存
 ----+M:按照key值删除   ipcrm -M key
 ----+m:按照标识符删除   ipcrm -m msgid
 删除消息队列
-----+Q:按照key值删除
-----+q:按照标识符删除
删除信号量
-------+S:按照key值删除
-------+s:按照标识符删除

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:消息队列的标识符
  • msgp:存放消息的缓存的地址,类型struct msgbuf类型

这个缓存就是一个消息包(存放消息的结构体变量)

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[1];    /* message data */
              };
  • msgsz:消息正文大小

  • msgflg

  • 0:阻塞发送消息(没有发送成功的话,该函数会一直阻塞等)
  • IPC_NOWAIT:非阻塞方式发送消息,不管发送成功与否,函数都将返回

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);
//返回值
		//成功:返回消息正文的字节数
		//失败:返回-1,errno被设置

五个参数:

  • msqid:消息队列的标识符
  • msgp:缓存地址,缓存用于存放所接收的消息
  • msgsz:消息正文的大小
  • msgtyp:你要接收消息的编号
  • msgflg:0:阻塞接收消息;IPC_NOWAIT:非阻塞接收消息

程序—消息队列的通信

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 #include <unistd.h>
 #include <strings.h>
#define MSG_FIFE "./msgque"
#define MSG_LENGTH 1024
//int msgget(key_t key, int msgflg);
//key_t ftok(const char *pathname, int proj_id);
void print_error(char* str){
    perror(str);
    exit(-1);
}
int create_or_get_MSG(void){
      int fd=0;
      int msgid=0;
      key_t key=0;
      //打开文件,没有就创建
      fd=open(MSG_FIFE,O_RDWR|O_CREAT,0664);
      if(fd==-1) print_error("open fails\n");
      //生成唯一key,用函数ftok()
      key=ftok(MSG_FIFE,'a');
      if(key==-1) print_error("generate key fails\n");
      //消息队列的获取或创建
      msgid=msgget(key,0664|IPC_CREAT);//别忘IPC_CREAT
      if(msgid==-1) print_error("msgget fails\n");
      return msgid;
}
struct msgbuf {//使用时候自己定义
        long mtype;       
        char mtext[MSG_LENGTH];   
};
int main(int argc,char** argv){
    if(argc!=2) print_error("input two args\n");
    int msgID=-1;
    msgID=create_or_get_MSG();
    long recv_type=0;
    recv_type=atol(argv[1]);
    pid_t ret=fork();
    if(ret>0){//父进程
    //int msgsnd(int msqid, const void *msgp,\
                     size_t msgsz, int msgflg);
        struct msgbuf msg_buf={0};
        while(1){
           bzero(&msg_buf,sizeof(msg_buf));
           /*封装消息包*/
           printf("输入要通信的内容:\n");
           //scanf("%s",msg_buf.mtext);
           read(0,msg_buf.mtext,sizeof(msg_buf.mtext));
           printf("输入要通信msgbuf的编号\n");
           //read(0,&msg_buf.mtype,sizeof(msg_buf.mtype));
           scanf("%ld",&msg_buf.mtype);
           msgsnd(msgID,&msg_buf,MSG_LENGTH,0);
        }
    }
    //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,\
                      int msgflg);
    else if(ret==0){//子进程
           struct msgbuf msg_buf={0}; 
          /*接收消息*/
          while(1){
            int mm=0;
            bzero(&msg_buf,sizeof(msg_buf));
            mm=msgrcv(msgID,&msg_buf,MSG_LENGTH,recv_type,0);
            if(mm>0)
                printf("%s\n",msg_buf.mtext);
          }
    }
    return 0;
}

进程结束,自动删除消息队列

msgctl()

 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>
//功能是根据cmd指定的要求,去控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
进行哪些控制
  • 获取消息队列的属性信息
  • 修改消息队列的属性信息
  • 删除消息队列

调用msgctl函数的最常见目的就是删除消息队列

参数
  • msqid:消息队列标识符

  • cmd:控制选项

    其实cmd有很多选项,我这里只简单介绍三个

    • IPC_STAT:获取属性信息

      把msqid消息队列的属性信息读到第三个参数指定的缓存

    • IPC_SET:使用第三个参数中的新设置去修改消息队列的属性IPC_SET

    • 定义一个struct msqid_ds buf
    • 将新的属性信息设置到buf中
    • msgctl函数就会使用buf中的新属性去修改消息队列原有的属性
    • IPC_RMID:删除消息队列
  • ​ buf:存放属性信息(了解)

struct msqid_ds {
		struct ipc_perm  msg_perm; /* 消息队列的读写权限和所有者 */
		time_t  msg_stime;    /* 最后一次向队列发送消息的时间*/
		time_t  msg_rtime;    /* 最后一次从消息队列接收消息的时间 */
		time_t  msg_ctime;    /* 消息队列属性最后一次被修改的时间 */
		unsigned  long __msg_cbytes; /* 队列中当前所有消息总的字节数 */
		msgqnum_t  msg_qnum;     /* 队列中当前消息的条数*/
		msglen_t msg_qbytes;  /* 队列中允许的最大的总的字节数 */
		pid_t  msg_lspid;     /* 最后一次向队列发送消息的进程PID */
		pid_t  msg_lrpid;     /* 最后一次从队列接受消息的进程PID */
		};
//struct ipc_perm  msg_perm; 
struct ipc_perm {
		key_t   __key;   /* Key supplied to msgget(2):消息队列的key值*/
        uid_t   uid;    /* UID of owner :当前这一刻正在使用消息队列的用户 */
		gid_t   gid;    /* GID of owner :正在使用的用户所在用户组 */
		uid_t   cuid;   /* UID of creator :创建消息队列的用户 */
		gid_t   cgid;   /* GID of creator :创建消息队列的用户所在用户组*/        unsigned short mode;    /* Permissions:读写权限(比如0664) */
 unsigned short __seq; /* Sequence number :序列号,保障消息队列ID不被立即 重复使用 */};
  • 控制1:获取消息队列属性

    struct msqid_ds buf;
    msgctl(msgid, IPC_STAT, &buf);

  • 控制2:删除消息队列

    msgctl(msgid, IPC_RMID, NULL);

利用信号捕获,删除消息队列

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#define MSG_FIFE "./msgque"
#define MSG_LENGTH 1024
int msgID=-1;//全局变量
//int msgget(key_t key, int msgflg);
//key_t ftok(const char *pathname, int proj_id);
void print_error(char* str){
    perror(str);
    exit(-1);
}
int create_or_get_MSG(void){
      int fd=0;
      int msgid=0;
      key_t key=0;
      //打开文件,没有就创建
      fd=open(MSG_FIFE,O_RDWR|O_CREAT,0664);
      if(fd==-1) print_error("open fails\n");
      //生成唯一key,用函数ftok()
      key=ftok(MSG_FIFE,'a');
      if(key==-1) print_error("generate key fails\n");
      //消息队列的获取或创建
      msgid=msgget(key,0664|IPC_CREAT);//别忘IPC_CREAT
      if(msgid==-1) print_error("msgget fails\n");
      return msgid;
}
struct msgbuf {//使用时候自己定义
        long mtype;       
        char mtext[MSG_LENGTH];   
};
void signal_fun(int signo){
    msgctl(msgID,IPC_RMID,NULL);
    exit(-1);
}
int main(int argc,char** argv){
    if(argc!=2) print_error("input two args\n");
    msgID=create_or_get_MSG();
    long recv_type=0;
    recv_type=atol(argv[1]);
    pid_t ret=fork();
    if(ret>0){//父进程
    //int msgsnd(int msqid, const void *msgp,\
                     size_t msgsz, int msgflg);
        signal(SIGINT,signal_fun);            
        struct msgbuf msg_buf={0};
        while(1){
           bzero(&msg_buf,sizeof(msg_buf));
           /*封装消息包*/
           printf("输入要通信的内容:\n");
           //scanf("%s",msg_buf.mtext);
           read(0,msg_buf.mtext,sizeof(msg_buf.mtext));
           printf("输入要通信msgbuf的编号\n");
           //read(0,&msg_buf.mtype,sizeof(msg_buf.mtype));
           scanf("%ld",&msg_buf.mtype);
           msgsnd(msgID,&msg_buf,MSG_LENGTH,0);
        }
    }
    //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,\
                      int msgflg);
    else if(ret==0){//子进程
           struct msgbuf msg_buf={0}; 
          /*接收消息*/
          while(1){
            int mm=0;
            bzero(&msg_buf,sizeof(msg_buf));
            mm=msgrcv(msgID,&msg_buf,MSG_LENGTH,recv_type,0);
            if(mm>0)
                printf("%s\n",msg_buf.mtext);
          }
    }
    return 0;
}
  • 当你的程序必须涉及到多进程网状交叉通信时,消息队列是上上之选
  • 消息队列的缺点,不能实现大规模数据的通信
  • 大规模数据的通信,必须使用后面讲的“共享内存”来实现

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

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

相关文章

【二叉树的顺序结构:堆 堆排序 TopK]

努力提升自己&#xff0c;永远比仰望别人更有意义 目录 1 二叉树的顺序结构 2 堆的概念及结构 3 堆的实现 3.1 堆向下调整算法 3.2 堆向上调整算法 3.3堆的插入 3.4 堆的删除 3.5 堆的代码实现 4 堆的应用 4.1 堆排序 4.2 TOP-K问题 总结&#xff1a; 1 二叉树的顺序结…

分享几招教会你怎么给图片加边框

大家平时分享图片的时候&#xff0c;会不会喜欢给照片加点装饰呢&#xff1f;比如加些边框、文字或者水印之类的。我就喜欢给图片加上一些边框&#xff0c;感觉加了边框的照片像裱在相框中的感觉似的&#xff0c;非常有趣。那么你知道如何给图片加边框吗&#xff1f;不知道的话…

【Nginx】01-什么是Nginx?Nginx技术的功能及其特性介绍

目录1. 介绍1.1 常见服务器的对比1&#xff09;IIS2&#xff09;Tomcat3&#xff09;Apache4&#xff09;Lighttpd1.2 Nginx的优点(1) 速度更快、并发更高(2) 配置简单、扩展性强(3) 高可靠性(4) 热部署(5) 成本低、BSD许可证2. Nginx常用功能2.1 基本HTTP服务2.2 高级HTTP服务…

华为数通2022年11月 HCIP-Datacom-H12-821 第二章

142.以下关于状态检测防火墙的描述&#xff0c;正确是哪一项&#xff1f; A.状态检测防火墙需要对每个进入防火墙的数据包进行规则匹配 B.因为UDP协议为面向无连接的协议&#xff0c;因此状态检测型防火墙无法对UDP报文进行状态表的匹配 C.状态检测型防火墙只需要对该连接的第一…

性能测试-CPU性能分析,IO密集导致系统负载高

目录 IO密集导致系统负载高 使用top命令-观察服务器资源状态 使用vmstat命令-观察服务器资源状态 使用pidstat命令-观察服务器资源状态 使用iostat命令-观察服务器资源状态 IO密集导致系统负载高 stress-ng -i 10 --hdd 1 --timeout 100-i :有多少个工作者进行&#…

函数的极限:如何通过 δ 和 ϵ 来定义一个连续的函数

连续的定义 维基百科给出的定义&#xff1a; 连续函数&#xff08;英语&#xff1a;Continuous function&#xff09;是指函数在数学上的属性为连续。直观上来说&#xff0c;连续的函数就是当输入值的变化足够小的时候&#xff0c;输出的变化也会随之足够小的函数。 所以不要直…

51单总线控制SV-5W语音播报模块

单总线控制SV-5W语音播报模块SV-5W语音播报模块SV-5W语音播报模块简介工作模式说明模块配置接线驱动部分代码效果展示SV-5W语音播报模块 SV-5W语音播报模块简介 DY-SV5W是一款智能语音模块&#xff0c;集成IO分段触发&#xff0c;UART串口控制&#xff0c;ONE_line单总线串口控…

macOS monterey 12.6.1安装homebrew + nginx + php + mysql

效果图 主要步骤 安装homebrew使用brew安装nginxphpmysql详细步骤 参考“Homebrew国内如何自动安装&#xff08;国内地址&#xff09;&#xff08;Mac & Linux&#xff09;”安装brew&#xff0c; 命令&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cu…

[附源码]java毕业设计网上学车预约系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

教你十分钟在Linux系统上快速装机并安装Ansible

PS:本教程建立在VMware软件上的使用上&#xff0c;Linux版本为centos7或者centos8都可以。 一、看发行版本 cat /etc/redhat-release 二、修改主机名 hostnamectl set-hostname centos8 三、自动获取IP地址 nmcli connection modify ens160 autoconnect yes 四、设置…

软件设计(一):统一建模语言基础知识

1.UML简介 1.1 UML简介 UML语言是一种可视化的标准建模语言&#xff0c;它是一种分析和设计语言&#xff0c;通过UML可以构造软件系统的蓝图。 1.2 UML的结构 1.2.1 视图&#xff08;view&#xff09; 1.2.2 图&#xff08;daigram&#xff09; 用例图 类图 对象图 包图…

C/C++ 语言怎么保留n位小数并且四舍五入

1、普通的printf输出打印 printf()函数的用例 float date=123.456; printf("date=%.2f\n", date);//保留2位 printf("date=%.1f\n", date);//保留1位 输出 2、获取四舍五入后的数据 1、使用round函数 C ++ round()函数 (C++ round() function) round(…

ELK技术栈简介

ELK技术栈简介ELK是什么ELK组件ElasticsearchES基本概念ES适用场景LogstashInput插件Filter插件Output插件CodecsKibanaBeatsELK是什么 ELK 即 Elasticsearch Logstash Kibana&#xff0c;是指Elastic公司开发的三种免费开源软件。其中&#xff0c;Elasticsearch是一个基于A…

基于PHP+MYSQL在线小说阅读网的设计与实现

随着互联网信息的发展,人们在闲暇的时候更多的原因选择小说来进行阅读,一方面扩展自己的阅读圈,另一方面消磨闲暇时光,但是当下的很多小说网站,要么是要收取高昂的阅读法,要么就是整个网站多充斥着大量的广告,为了给广大网友一个健康,免费的阅读空间我们开发了本系统 本在线小说…

【JS】数据结构之树结构

文章目录树结构二叉树二叉搜索树平衡树&#xff08;AVL树&#xff09;红黑树回顾其他数据结构&#xff08;每种数据结构都有自己特定的应用场景&#xff09;&#xff1a; 数组&#xff1a;通过下标查询很快&#xff0c;插入和删除数据的时候&#xff0c;效率会很低&#xff0c;…

新品上线 | 企企通推出达人管理系统,助力达人营销提效增速

01、直播市场发展迅速 企企通达人管理系统应运而生 近年来&#xff0c;直播凭借其即时性、互动性、多样化的优势&#xff0c;迅速在互联网占据一席之地&#xff0c;“直播”模式不断扩展&#xff0c;直播电商应运而生。 在技术发展与市场需求双重驱动下&#xff0c;中国直播市…

day04 springmvc

day04 springmvc 第一章 SpringMVC运行原理 第一节 启动过程 1. Servlet 生命周期回顾 生命周期环节调用的方法时机次数创建对象无参构造器默认&#xff1a;第一次请求 修改&#xff1a;Web应用启动时一次初始化init(ServletConfig servletConfig)创建对象后一次处理请求se…

嵌入式Linux系统中ARM汇编语言的使用方法

大家好&#xff0c;今天主要大家聊一聊&#xff0c;如何在ARM中使用汇编语言的方法。 目录 第一&#xff1a;汇编基础简介 第二&#xff1a;处理器内部数据传输指令 第三&#xff1a;存储器访问指令 第一&#xff1a;汇编基础简介 我们在学习嵌入式Linux开发的时候是绝…

【用户画像】Redis_Jedis测试、将人群包存放到Redis中、挖掘类标签处理过程、决策树、用SparkMLLib实现决策树

文章目录一 Redis_Jedis_测试1 Jedis所需要的jar包2 连接Redis注意事项3 测试相关数据类型&#xff08;0&#xff09;测试连接&#xff08;1&#xff09;Key&#xff08;2&#xff09;String&#xff08;3&#xff09;List&#xff08;4&#xff09;set&#xff08;5&#xff0…

shiro-第一篇-基本介绍及使用

shiro 概述 shior的话&#xff0c;在第一次听说的时候单纯的任务它就是一个安全框架&#xff0c;可以对访问接口的用户进行验证等工作&#xff0c;类似拦截器或过滤器的东西&#xff0c;但是在学习后&#xff0c;发现远远不止这些&#xff0c;它的灵活性和易用性让我震惊&…