《Linux C编程实战》笔记:消息队列

消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或显示地删除一个消息队列时,该消息队列才会被真正的删除。

操作消息队列时,需要用到一些数据结构,熟悉这些数据结构是掌握消息队列的关键。下面介绍几个重要的数据结构

消息缓冲结构

向消息队列发送消息时,必须组成合理的数据结构。Linux系统定义了一个模板数据结构msgbuf

#include<linux/msg.h>
struct msgbuf {
	long mtype;          /* type of message */
	char mtext[1];                  /* message text */
};

结构体中的mtype字段代表消息类型。给消息指定类型,可以使得消息在一个队列中重复使用。mtext字段指消息内容。

mtext最然定义为char类型,并不代表消息只能是一个字符,消息内容可以为任意类型,由用户根据需要定义,如下面就是用户定义的一个消息结构

struct myMsgbuf{
    long mtype;
    struct student std;
}

消息队列中的消息的大小是受限制的,由<linux/msg.h>中的宏MSGMAX给出消息的最大长度,在实际应用中要注意这个限制。

msqid_ds内核数据结构

Linux内核中,每个消息队列都维护一个结构体msqid_qs,此结构体保存着消息队列当前的状态信息。该结构体定义在头文件linux/msg.h中,具体如下

struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;		/* first message on queue,unused  */
	struct msg *msg_last;		/* last message in queue,unused */
	__kernel_old_time_t msg_stime;	/* last msgsnd time */
	__kernel_old_time_t msg_rtime;	/* last msgrcv time */
	__kernel_old_time_t msg_ctime;	/* last change time */
	unsigned long  msg_lcbytes;	/* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes;	/* ditto */
	unsigned short msg_cbytes;	/* current number of bytes on queue */
	unsigned short msg_qnum;	/* number of messages in queue */
	unsigned short msg_qbytes;	/* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;	/* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;	/* last receive pid */
};
  1. msg_perm:用于存储IPC对象的权限信息以及队列的用户ID,组ID等信息。
  2. msg_first:队列中的第一个消息的指针。
  3. msg_last:队列中的最后一个消息的指针。
  4. msg_stime:上次发送消息的时间。
  5. msg_rtime:上次接收消息的时间。
  6. msg_ctime:上次修改消息队列的时间。
  7. msg_lcbytes:用于32位系统的字段,未使用。
  8. msg_lqbytes:用于32位系统的字段,未使用。
  9. msg_cbytes:当前队列中消息的总字节数。
  10. msg_qnum:队列中当前存在的消息数量。
  11. msg_qbytes:队列中允许的最大字节数。
  12. msg_lspid:最后一次发送消息的进程ID。
  13. msg_lrpid:最后一次接收消息的进程ID。

ipc_perm内核数据结构

结构体ipc_perm保存着消息队列的一些重要信息,比如消息队列关联的键值,消息队列的用户ID,组ID等,它定义在头文件linux/ipc.h中

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;
};
  1. key:创建消息队列用到的键值key。
  2. uid:消息队列的用户ID(User ID)。
  3. gid:消息队列的组ID(Group ID)。
  4. cuid:创建消息队列的用户ID(User ID)。
  5. cgid:创建消息队列的组ID(Group ID)。
  6. mode:权限模式(Permission Mode),包括读、写、执行权限等。
  7. seq:序列号,用于确保唯一性和顺序性。

消息队列的创建与读写

创建消息队列

消息队列是随着内核的存在而存在的,每个消息队列在系统范围内对应唯一的键值。要获得一个消息队列的描述符,只需要提供该消息队列的键值即可,该键值通常通过ftok函数返回。该函数定义在头文件sys/ipc.h中

#include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

ftok 函数通过将给定的文件路径名与给定的项目ID进行哈希运算生成一个唯一的键值。失败返回-1.

示例代码1

演示ftok的使用

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
int main(int argc,char **argv,char **environ){
    for(int i=1;i<=5;i++)
        printf("key[%d] = %u \n",i,ftok(".",i));
    return 0;
}

根据路径名和proj_id就会得到一个键值。

注意:参数pathname在系统中一定要存在且进程有权访问,参数proj_id的取值范围为1-255

ftok返回的键值可以提供给函数msgget。msgget根据这个键值创建一个新的消息队列或者访问一个已经存在的消息队列。msgget定义在头文件sys/msg.h中。

int msgget(key_t key, int msgflg);

该函数接受两个参数:

  1. key:消息队列的键值,通常使用 ftok 函数生成。它用于标识消息队列。

  2. msgflg:用于指定消息队列的创建和访问权限的标志。通常情况下,它可以是以下几个值的按位或组合:

    • IPC_CREAT:如果消息队列不存在,则创建一个新的消息队列。
    • IPC_EXCL:与 IPC_CREAT 一起使用时,如果消息队列已经存在,则返回错误。
    • 权限掩码:用于设置消息队列的访问权限,例如 0666

msgget 函数返回一个标识符,它是消息队列的唯一标识符。如果成功,返回值是一个非负整数;如果失败,返回值为 -1,并设置 errno 来指示错误的原因。

写消息队列

函数msgsnd用于向消息队列发送数据。该函数定义在头文件sys/msg.h中

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

该函数接受四个参数:

  1. msqid:消息队列的标识符,通常由 msgget 函数返回。

  2. msgp:指向消息数据的指针,通常是一个用户定义的结构体指针,用于存储要发送的数据。

  3. msgsz:消息的大小,以字节为单位。该值应该是 msgp 指向的数据结构的大小。

  4. msgflg:用于指定消息发送的行为标志,可以是以下之一或它们的按位或组合:

    • IPC_NOWAIT:如果消息队列已满,则立即返回,而不是等待空闲空间。
    • 0:默认行为,如果消息队列已满,则进程将被阻塞,直到有足够的空间将消息发送到队列中。

msgsnd 函数返回一个整数值,如果成功,返回值为 0;如果失败,返回值为 -1,并设置 errno 来指示错误的原因。

使用 msgsnd 函数时,需要确保指定的消息队列标识符有效,并且消息队列具有足够的空间来容纳要发送的消息。

示例程序2

演示往消息队列发消息

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main(int argc,char **argv,char **environ){
    //用户自定义消息缓存
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    //获取键值
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    //创建消息队列
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //填充消息结构,发送到消息队列
    msgbuffer.msgtype=3;
    strcpy(msgbuffer.ctrlstring,"hello,message queue");
    msglen=sizeof(struct mymsgbuf)-4;
    if(msgsnd(qid,&msgbuffer,msglen,0)==-1){
        perror("msgget error!\n");
        exit(1);
    }
    exit(0);
}

ipcs 命令是用于列出当前系统上的 System V IPC(Inter-Process Communication)资源的信息,包括消息队列、信号量和共享内存。

从结果看,系统内部生成了一个消息队列,其中含有一条消息。

读消息队列

消息队列中放入数据后,其他进程就可以读取其中的信息了。读取消息队列的函数是msgrcv,该函数定义在头文件sys/msg.h中

#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
  1. msqid:消息队列的标识符,用于指定从哪个消息队列接收消息。
  2. msgp:一个指向消息缓冲区的指针,用于存储接收到的消息。
  3. msgsz:消息缓冲区的大小,即 msgp 指向的缓冲区的大小。
  4. msgtyp:指定要接收的消息类型。如果 msgtyp 为正数,则函数将接收第一个消息类型为 msgtyp 的消息。如果 msgtyp 为 0,则接收队列中的第一个消息,无论其类型是什么。如果 msgtyp 为负数,则接收队列中类型值最小且小于等于 msgtyp 绝对值的消息。
  5. msgflg:附加标志,用于控制函数的行为。可以使用 IPC_NOWAIT(如果没有可用消息,则立即返回)和/或 MSG_NOERROR(如果消息大于 msgsz,则截断消息)的组合。
  6. 如果函数成功接收到消息,则返回接收到的消息的字节数量。
  7. 如果函数调用失败,则返回 -1,并设置 errno 来指示错误的类型。

如果不设置IPC_NOWAIT的话,msgrcv也会阻塞进程

示例程序3

下面的例子就来读取之前发送的消息

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //前面的都一样
    msglen=sizeof(struct mymsgbuf)-4;
    //第四个参数为什么是3呢,因为我们发的时候设置的就是3
    if(msgrcv(qid,&msgbuffer,msglen,3,0)==-1){
        perror("msgrcv error!\n");
        exit(1);
    }
    printf("Get message %s \n",msgbuffer.ctrlstring);
    exit(0);
}

因为给 ftok提供的参数是一样的,所以能得到对应的消息队列。因为我们已经创建了该消息队列,所以msgget就只是得到qid。

获取和设置消息队列的属性

上面介绍到了消息队列的属性保存在数据结构msqid_ds中,用户可以通过函数msgctl获取或设置消息队列的属性。msgctl定义在头文件sys/msg.h中

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  • msqid 是消息队列的标识符,由msgget函数返回。
  • cmd 是命令,指定了你想执行的操作。常见的命令包括:
    • IPC_STAT:获取消息队列的当前状态。
    • IPC_SET:设置消息队列的属性。
    • IPC_RMID:删除消息队列。
  • buf 是一个指向msqid_ds结构的指针,用于存储或传递消息队列的状态信息。

示例程序4

演示如何获取和设置消息队列的属性

#include<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<string.h>
#include<time.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
void getmsgattr(int msgid,struct msqid_ds msg_info);
int main(void){
    //自定义的消息缓冲区
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    struct msqid_ds msg_attr;
    //获取键值
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    //获取下奥西队列的标识符
    if((qid=msgget(msgkey,IPC_CREAT|0666))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//输出属性

    //发送一条消息到消息队列
    msgbuffer.msgtype=2;
    strcpy(msgbuffer.ctrlstring,"Aother message");
    msglen=sizeof(struct mymsgbuf)-4;
    if(msgsnd(qid,&msgbuffer,msglen,0)==-1){
        perror("msgget error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//再次查看消息队列的属性

    //这次手动设置消息队列的属性
    msg_attr.msg_perm.uid=3;
    msg_attr.msg_perm.gid=2;
    if(msgctl(qid,IPC_SET,&msg_attr)==-1){//用msgctl函数修改
        perror("msg set error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//输出修改后的属性

    //删除消息队列
    if(msgctl(qid,IPC_RMID,NULL)==-1){
        perror("delete msg error!\n");
        exit(1);
    }
    getmsgattr(qid,msg_attr);//删除后再观察属性
    exit(0);
}
void getmsgattr(int msgid,struct msqid_ds msg_info){
    if(msgctl(msgid,IPC_STAT,&msg_info)==-1){//通过msgctl获取消息队列的属性
        perror("msgctl error!\n");
        return;
    }
    //输出的内容参照前文对struct msqid_ds结构体的介绍
    printf("****information of message queue%d****\n",msgid);
    printf("last msgsnd to msg time is %s\n",ctime(&(msg_info.msg_stime)));
    printf("last msgrcv time from msg is %s\n",ctime(&(msg_info.msg_rtime)));
    printf("last change msg time is %s\n",ctime(&(msg_info.msg_ctime)));
    printf("current number of bytes on queue is %d\n",msg_info.__msg_cbytes);
    printf("number of messages in queue is %d\n",msg_info.msg_qnum);
    printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes);
    printf("pid of last msgsnd is %d\n",msg_info.msg_lspid);
    printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid);

    printf("msg uid is %d\n",msg_info.msg_perm.uid);
    printf("msg gid is %d",msg_info.msg_perm.gid);
    printf("****infromation end!****\n");
}

执行结果的片段,因为太长截不下。

getmsgattr函数好像不需要第二个参数啊,直接在函数内部声明一个临时的就行,反正都是从消息队列里重新获取的。不知道书上为什么这么写。

示例程序5

上一节使用了有名管道实现了服务器和客户进程的通信,这回使用消息队列来完成。

server端:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
#define SERVER_MSG 1
#define CLIENT_MSG 2    
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    //前面基本都一样,就是获取键值,获取消息队列的qid
    while(1){
        printf("server: ");
        fgets(msgbuffer.ctrlstring,BUF_SIZE,stdin);//从标准输入读
        if(strncmp("exit",msgbuffer.ctrlstring,4)==0){//如果是exit则删除消息队列退出
            msgctl(qid,IPC_RMID,NULL);
            break;
        }
        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';
        msgbuffer.msgtype=SERVER_MSG;//标记是来自服务器端的信息
        if(msgsnd(qid,&msgbuffer,strlen(msgbuffer.ctrlstring)+1,0)==-1){//+1是因为还有\0
            perror("Server msgsnd error!\n");
            exit(1);
        }
        if(msgrcv(qid,&msgbuffer,BUF_SIZE,CLIENT_MSG,0)==-1){//接收来自客户端的信息
            perror("Server msgrcv error!\n");
            exit(1);
        }
        printf("Clinet:%s\n",msgbuffer.ctrlstring);
    }
    exit(0);
}

这里要解释一下msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';这一句代码

fgets会保存输入时的换行符,也就是说整个字符串是包括一个\n的,当然最后也包括\0,但是我们发送的信息不需要这个换行符,所以这句代码的意思就是把换行符\n改成\0。因为strlen是计算从开始到\0之前的长度,比如说一个字符串是 abc\n\0,那么strlen得到的是4,\n的下标位置是3.

client端:程序基本就是改了个顺序,先接收来自服务器端的数据,再发送数据

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/stat.h>
#include<sys/msg.h>
#define BUF_SIZE 256
#define PROJ_ID 32
#define PATH_NAME "."
#define SERVER_MSG 1
#define CLIENT_MSG 2    
int main(int argc,char **argv,char **environ){
    struct mymsgbuf{
        long msgtype;
        char ctrlstring[BUF_SIZE];
    }msgbuffer;
    int qid;
    int msglen;
    key_t msgkey;
    if((msgkey=ftok(PATH_NAME,PROJ_ID))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((qid=msgget(msgkey,IPC_CREAT|0660))==-1){
        perror("msgget error!\n");
        exit(1);
    }
    while(1){
        if(msgrcv(qid,&msgbuffer,BUF_SIZE,SERVER_MSG,0)==-1){
            perror("Server msgrcv error!\n");
            exit(1);
        }
        printf("server:%s\n",msgbuffer.ctrlstring);
        printf("client: ");
        fgets(msgbuffer.ctrlstring,BUF_SIZE,stdin);
        if(strncmp("exit",msgbuffer.ctrlstring,4)==0){
            break;
        }
        msgbuffer.ctrlstring[strlen(msgbuffer.ctrlstring)-1]='\0';
        msgbuffer.msgtype=CLIENT_MSG;
        if(msgsnd(qid,&msgbuffer,strlen(msgbuffer.ctrlstring)+1,0)==-1){
            perror("client msgsnd error!\n");
            exit(1);
        }

    }
    exit(0);
}

先运行server程序,再在一个终端运行client程序,这样两个程序之间就可以进行聊天

这篇写了真的很久,内容超级多而且很杂,内容又很新,我自己学习也花了很久

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

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

相关文章

英伟达推出免训练,可生成连贯图片的文生图模型

目前&#xff0c;多数文生图模型皆使用的是随机采样模式&#xff0c;使得每次生成的图像效果皆不同&#xff0c;在生成连贯的图像方面非常差。 例如&#xff0c;想通过AI生成一套图像连环画&#xff0c;即便使用同类的提示词也很难实现。虽然DALLE 3和Midjourney可以对图像实现…

如何修改unity的背景颜色

要在Unity中将背景颜色设为黑色&#xff0c;可以按照以下步骤进行&#xff1a; 1、在Unity编辑器中&#xff0c;选择你想要修改背景颜色的摄像机对象&#xff08;一般是Main Camera&#xff09;。 2、在Inspector面板中&#xff0c;找到"Clear Flags"&#xff08;清…

掌握Redis核心:常用数据类型的高效运用秘籍!

在数据驱动的时代&#xff0c;高效地存储和处理数据成为了开发者们的重要任务。Redis&#xff0c;作为一个开源的高性能键值对&#xff08;key-value&#xff09;数据库&#xff0c;以其独特的数据结构和丰富的功能&#xff0c;成为了众多项目的首选。 今天&#xff0c;我们就…

高精度双向计量旋翼干式脉冲水表

在现代社会&#xff0c;水资源的合理利用与精确管理至关重要。随着科技的不断进步&#xff0c;水表技术也在迭代升级。今天&#xff0c;我们将探讨一种高科技水表——高精度双向计量旋翼干式脉冲水表&#xff0c;它如何改变我们的用水习惯&#xff0c;提升水资源管理的效率。 1…

Stable Diffusion 模型分享:FenrisXL(芬里斯XL)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 FenrisXL 是一个拟人化的 SDXL 模型&#xff0c;可以为动物们穿上人类的衣服&…

吴恩达机器学习全课程笔记第三篇

目录 前言 P42-P48 神经元和大脑 神经网络中的层 更复杂的神经网络 前向传播&#xff08;做出预测&#xff09; P49-P53 代码中的推理 构建一个神经网络 P54-P60 矩阵乘法 TensorFlow框架实现神经网络 前言 这是吴恩达机器学习笔记的第三篇&#xff0c;第二篇笔记…

苍穹外卖学习-----2024/02/21

1.新增员工 /*** 处理SQL异常* param sqlIntegrityConstraintViolationException* return*/ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException sqlIntegrityConstraintViolationException){//String message sqlIntegrityConstraintV…

IDEA启动Springboot报错:无效的目标发行版:17 的解决办法

无效的目标发行版&#xff1a;17 的解决办法 一般有两个原因&#xff0c;一可能是本地没有安装JDK17&#xff0c;需要安装后然后在IDEA中选择对应版本&#xff1b;二可能是因为IDEA版本太低&#xff0c;不支持17&#xff0c;需要升级IDEA版本。然后在File->Project Struct…

Zabbix 6.2.1 安装

目录 1、监控介绍 监控的重要性 网站的可用性 监控范畴 如何监控 2、Zabbix 介绍 zabbix 简介 zabbix 主要功能 zabbix 监控范畴 Zabbix 监控组件 zabbix 常见进程 zabbix agentd 工作模式 zabbix 环境监控中概念 3、搭建LNMP 拓扑规划 安装MySQL 安装 Nginx …

libpng编译-android端(libpng官网下载是没有android编译脚本)

libpng编译-android端&#xff08;libpng官网下载是没有android编译脚本&#xff09; 环境配置&#xff08;mac的原理一样可以自己配置&#xff0c;我在linux编译&#xff09; 配置ubuntu的ndk环境 1、执行 sudo vim /etc/profile&#xff0c;对Path的路径文件进行更改&…

python(23)——while循环

前言 在Python中&#xff0c;while 循环用于重复执行一段代码块&#xff0c;只要指定的条件保持为真&#xff08;True&#xff09;。一旦条件变为假&#xff08;False&#xff09;&#xff0c;循环就会终止。while 循环通常用于在不知道循环将执行多少次的情况下进行迭代。 w…

linux下开发,stm32和arduino,我该何去何从?

linux下开发&#xff0c;stm32和arduino&#xff0c;我该何去何从&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「stm3的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共…

React 模态框的设计(一)拖动组件的设计

春节终结束了&#xff0c;忙得我头疼。终于有时间弄自己的东西了。今天来写一个关于拖动的实例讲解。先看效果&#xff1a; 这是一个简单的组件设计&#xff0c;如果用原生的js设计就很简单&#xff0c;但在React中有些事件必须要多考虑一些。这是一个系列的文章&#xff0c;…

Megalinter 初体验

简介 MegaLinter 是一个多语言、多工具的集成代码检查工具&#xff0c;它能够通过一个统一的工作流来运行多个静态代码分析工具&#xff0c;从而提供全面的代码质量检查。 官网&#xff1a;https://megalinter.io/latest/ MegaLinter 的特点&#xff1a; 多语言支持&#x…

深入理解指针(c语言)

目录 一、使用指针访问数组二、数组名的理解1、数组首元素的地址2、整个数组 三、一维数组传参的本质四、冒泡排序五、二级指针六、指针数组 一、使用指针访问数组 可以使用指针来访问数组元素。例如&#xff0c;可以声明一个指针变量并将其指向数组的第一个元素&#xff0c;然…

QT-模拟电梯上下楼

QT-模拟电梯上下楼 一、演示效果二、核心程序三、下载链接 一、演示效果 二、核心程序 #include "ElevatorController.h" #include <QGridLayout> #include <QLabel> #include <QGroupBox> #include <QGridLayout> #include <QPushButto…

2024「诺奖风向标」斯隆奖出炉:杨笛一、杜少雷、清华校友姜楠、北大校友金驰等入选​

今天&#xff0c;斯隆基金会公布了 2024年度斯隆研究奖&#xff08;Sloan Research Fellowship&#xff09;获得者名单&#xff0c;本次斯隆研究奖共颁发给了126 人&#xff0c;其中计算机科学领域 共有 7名华人学者当选。 包括斯坦福大学杨笛一&#xff08;上海交大ACM班校友…

2.5《Python3 网络爬虫开发实战》学习之实例实战1

目录 1 实战内容 2 确定思路 3 代码实操 3.1 实现一个个网页的爬取 3.2 爬取每一个网页的电影详情页url ​编辑 3.3 连接链接&#xff0c;针对每个详情页链接进行爬取、汇总内容 3.4 存储在txt文件中 4 结尾&#xff1a;整体代码 1 实战内容 爬取Scrape | Movie中所有…

24款奔驰S400L升级原厂360全景影像 效果分享

今天给大家分享一款 接近百万豪车 奔驰S400L商务版 连360 小柏林都没有&#xff0c;奔驰果然是不坑穷人的&#xff0c;所以很多车主一提到车就升级成豪华型的配置&#xff0c;你说五米多才的车 没有原厂360全景影像 是多么的难受 倒车和狭窄路段很麻烦&#xff0c;所以车主找到…

基于EasyCVR视频汇聚系统的公安网视频联网共享视频云平台建设思路分析(一)

随着社会的发展和科技的进步&#xff0c;视频监控系统在各个领域的应用越来越广泛&#xff0c;视频云平台建设已经成为了行业数字化转型的重要一环。公安网视频汇聚联网共享云的建设需要充分考虑技术、架构、安全、存储、计算等多方面因素&#xff0c;以确保平台的稳定性和可用…