Linux下MQTT客户端消息订阅与发布实现

news2025/1/16 11:07:31

MQTT(消息队列遥测传输)是一个基于客户端-服务器的消息发布/订阅传输协议。它基于TCP协议,默认端口号为1883,为此,它也需要一个消息中间件 。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT),于此同时在智能家居、及一些小型化设备中也已广泛使用。

本文章简单介绍一下MQTT协议的具体应用实现,即客户端模式下消息的订阅与发布实现,运行环境为Linux系统,基于mosquitto这一份开源代码框架改装。具体操作步骤将在下面一一介绍。

  1. 首先下载一份开源的mosquitto代码,编译安装后将产生一系列可执行文件,常用的命令有mosquitto_sub,mosquitto_pub,mosquitto,分别为订阅客户端,发布客户端,以及服务器。

  1. 从mosquitto_sub对应的代码中拷贝一份出来,这里命名为mqtt_client.c,然后对其代码进行修改,整个内容如下:

/*
Copyright (c) 2009-2019 Roger Light <roger@atchoo.org>
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
   http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
  http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
   Roger Light - initial implementation and documentation.
*/

#include "config.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifndef WIN32
#include <unistd.h>
#include <signal.h>
#else
#include <process.h>
#include <winsock2.h>
#define snprintf sprintf_s
#endif

#include "mosquitto.h"
#include "mqtt_protocol.h"
#include "client_shared.h"
#include "pthread.h"

struct mosq_config cfg;
bool process_messages = true;
int msg_count = 0;
struct mosquitto *mosq = NULL;
int last_mid = 0;

#ifndef WIN32
void my_signal_handler(int signum)
{
    if(signum == SIGALRM){
        process_messages = false;
    }
}
#endif

void print_message(struct mosq_config *cfg, const struct mosquitto_message *message);

/*
发布消息之后运行的回调函数
*/
void my_publish_callback(struct mosquitto *mosq, void *obj, int mid, int reason_code, const mosquitto_property *properties)
{
    UNUSED(obj);
    UNUSED(reason_code);
    UNUSED(properties);
}

/*
订阅消息后触发的回调函数,可以在该函数处理接收到的消息,该回调函数必不可少。
*/
void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message, const mosquitto_property *properties)
{
    struct mosq_config *cfg;
    int i;
    bool res;

    assert(obj);
    cfg = (struct mosq_config *)obj;

    if(message->retain && cfg->no_retain) return;

//打印出订阅的主题以及消息长度
    printf("[%s]topic:%s, payloadlen:%d", __func__, message->topic, message->payloadlen);
    
    if(cfg->filter_outs)
    {
        for(i=0; i<cfg->filter_out_count; i++){
            mosquitto_topic_matches_sub(cfg->filter_outs[i], message->topic, &res);
            if(res) return;
        }
    }

    if(cfg->verbose)
    {
        if(message->payloadlen)
        {
            fwrite("topic:", 1, strlen("topic:"), stdout);
            printf("%s ", message->topic);
            fwrite(message->payload, 1, message->payloadlen, stdout);
            if(cfg->eol)
            {
                printf("\n");
            }
            
        }else{
            if(cfg->eol){
                printf("%s (null)\n", message->topic);
            }
        }
        fflush(stdout);
    }else{
        if(message->payloadlen){
//打印接收到的消息
            printf("[recv<==]%s\n", message->payload);
        }
    }
    if(cfg->msg_count>0){
        msg_count++;
        if(cfg->msg_count == msg_count){
            process_messages = false;
            
        }
    }
    
}
/*
连接MQTT服务器成功之后触发的回调函数,可在这个函数里面加入订阅主题的功能
*/
void my_connect_callback(struct mosquitto *mosq, void *obj, int result, int flags, const mosquitto_property *properties)
{
    int i;
    struct mosq_config *cfg;

    assert(obj);
    cfg = (struct mosq_config *)obj;
    int svrchn;

    if(!result)
    {
        for(i=0; i<cfg->topic_count; i++)
        {
//订阅主题的接口
            mosquitto_subscribe(mosq, NULL, cfg->topics[i], cfg->qos);
        }
    }
}
/*
订阅主题成功之后触发的回调函数
*/
void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
{
    printf("subscribe_callback_v25");
    int i;
    struct mosq_config *cfg;

    assert(obj);
    cfg = (struct mosq_config *)obj;

    if(!cfg->quiet) printf("Subscribed (mid: %d): %d\n", mid, granted_qos[0]);
    for(i=1; i<qos_count; i++){
        if(!cfg->quiet) printf("%d\n", granted_qos[i]);
    }
    if(!cfg->quiet) printf("\n");
}

void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str)
{
    UNUSED(mosq);
    UNUSED(obj);
    UNUSED(level);
    printf("%s\n", str);
}
/*
用于发布消息的回调函数,从缓存获取消息,并将该消息发布到服务端,该函数不可缺少。
*/
void mqtt_msg_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
    
    char pubtopic[128]="test/topic";
    if(msglen) {
        printf("==>len:%d, topic:%s, msg:%s\n",  msglen,pubtopic, msgbuf);
        mosquitto_publish(mosq, NULL, pubtopic, msglen, msgbuf, 1, false);
        msglen = 0;
    }
}

/*
客户端help提示
*/
void print_usage(void)
{
    int major, minor, revision;

    mosquitto_lib_version(&major, &minor, &revision);
    printf("mosquitto_sub is a simple mqtt client that will subscribe to a set of topics and print all messages it receives.\n");
    printf("mosquitto_sub version %s running on libmosquitto %d.%d.%d.\n\n", "3.1.1", major, minor, revision);
    printf("Usage: mosquitto_sub {[-h host] [-p port] [-u username] [-P password] -t topic | -L URL [-t topic]}\n");
    printf("                     [-c] [-k keepalive] [-q qos]\n");
    printf("                     [-C msg_count] [-E] [-R] [--retained-only] [--remove-retained] [-T filter_out] [-U topic ...]\n");
    printf("                     [-F format]\n");
#ifndef WIN32
    printf("                     [-W timeout_secs]\n");
#endif
#ifdef WITH_SRV
    printf("                     [-A bind_address] [-S]\n");
#else
    printf("                     [-A bind_address]\n");
#endif
    printf("                     [-i id] [-I id_prefix]\n");
    printf("                     [-d] [-N] [--quiet] [-v]\n");
    printf("                     [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n");
#ifdef WITH_TLS
    printf("                     [{--cafile file | --capath dir} [--cert file] [--key file]\n");
    printf("                       [--ciphers ciphers] [--insecure]\n");
    printf("                       [--tls-alpn protocol]\n");
    printf("                       [--tls-engine engine] [--keyform keyform] [--tls-engine-kpass-sha1]]\n");
#ifdef FINAL_WITH_TLS_PSK
    printf("                     [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n");
#endif
#endif
#ifdef WITH_SOCKS
    printf("                     [--proxy socks-url]\n");
#endif
    printf("                     [-D command identifier value]\n");
    printf("       mosquitto_sub --help\n\n");
    printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n");
    printf("      the client communicates over.\n");
    printf(" -c : disable 'clean session' (store subscription and pending messages when client disconnects).\n");
    printf(" -C : disconnect and exit after receiving the 'msg_count' messages.\n");
    printf(" -d : enable debug messages.\n");
    printf(" -D : Define MQTT v5 properties. See the documentation for more details.\n");
    printf(" -E : Exit once all subscriptions have been acknowledged by the broker.\n");
    printf(" -F : output format.\n");
    printf(" -h : mqtt host to connect to. Defaults to localhost.\n");
    printf(" -i : id to use for this client. Defaults to mosquitto_sub_ appended with the process id.\n");
    printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n");
    printf("      broker is using the clientid_prefixes option.\n");
    printf(" -k : keep alive in seconds for this client. Defaults to 60.\n");
    printf(" -L : specify user, password, hostname, port and topic as a URL in the form:\n");
    printf("      mqtt(s)://[username[:password]@]host[:port]/topic\n");
    printf(" -N : do not add an end of line character when printing the payload.\n");
    printf(" -p : network port to connect to. Defaults to 1883 for plain MQTT and 8883 for MQTT over TLS.\n");
    printf(" -P : provide a password\n");
    printf(" -q : quality of service level to use for the subscription. Defaults to 0.\n");
    printf(" -R : do not print stale messages (those with retain set).\n");
#ifdef WITH_SRV
    printf(" -S : use SRV lookups to determine which host to connect to.\n");
#endif
    printf(" -t : mqtt topic to subscribe to. May be repeated multiple times.\n");
    printf(" -T : topic string to filter out of results. May be repeated.\n");
    printf(" -u : provide a username\n");
    printf(" -U : unsubscribe from a topic. May be repeated.\n");
    printf(" -v : print published messages verbosely.\n");
    printf(" -V : specify the version of the MQTT protocol to use when connecting.\n");
    printf("      Can be mqttv5, mqttv311 or mqttv31. Defaults to mqttv311.\n");
#ifndef WIN32
    printf(" -W : Specifies a timeout in seconds how long to process incoming MQTT messages.\n");
#endif
    printf(" --help : display this message.\n");
    printf(" --quiet : don't print error messages.\n");
    printf(" --retained-only : only handle messages with the retained flag set, and exit when the\n");
    printf("                   first non-retained message is received.\n");
    printf(" --remove-retained : send a message to the server to clear any received retained messages\n");
    printf("                     Use -T to filter out messages you do not want to be cleared.\n");
    printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n");
    printf("                  unexpected disconnection. If not given and will-topic is set, a zero\n");
    printf("                  length message will be sent.\n");
    printf(" --will-qos : QoS level for the client Will.\n");
    printf(" --will-retain : if given, make the client Will retained.\n");
    printf(" --will-topic : the topic on which to publish the client Will.\n");
#ifdef WITH_TLS
    printf(" --cafile : path to a file containing trusted CA certificates to enable encrypted\n");
    printf("            certificate based communication.\n");
    printf(" --capath : path to a directory containing trusted CA certificates to enable encrypted\n");
    printf("            communication.\n");
    printf(" --cert : client certificate for authentication, if required by server.\n");
    printf(" --key : client private key for authentication, if required by server.\n");
    printf(" --keyform : keyfile type, can be either \"pem\" or \"engine\".\n");
    printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n");
    printf(" --tls-version : TLS protocol version, can be one of tlsv1.3 tlsv1.2 or tlsv1.1.\n");
    printf("                 Defaults to tlsv1.2 if available.\n");
    printf(" --insecure : do not check that the server certificate hostname matches the remote\n");
    printf("              hostname. Using this option means that you cannot be sure that the\n");
    printf("              remote host is the server you wish to connect to and so is insecure.\n");
    printf("              Do not use this option in a production environment.\n");
    printf(" --tls-engine : If set, enables the use of a SSL engine device.\n");
    printf(" --tls-engine-kpass-sha1 : SHA1 of the key password to be used with the selected SSL engine.\n");
#ifdef FINAL_WITH_TLS_PSK
    printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n");
    printf(" --psk-identity : client identity string for TLS-PSK mode.\n");
#endif
#endif
#ifdef WITH_SOCKS
    printf(" --proxy : SOCKS5 proxy URL of the form:\n");
    printf("           socks5h://[username[:password]@]hostname[:port]\n");
    printf("           Only \"none\" and \"username\" authentication is supported.\n");
#endif
    printf("\nSee https://mosquitto.org/ for more information.\n\n");
}

/*
在这里定义消息缓存及消息长度
*/
char msgbuf[512];
int msglen;

/*
从终端接手输入的信息,并存入缓存
*/
void *mqtt_publish_hander(void *arg)
{
    struct mosquitto *mosq = (struct mosquitto *)arg;
    while(1) {
        char pubtopic[128]="test/topic";
        char testbuf[512]="";
        printf("please input some thing:\n");
        fgets(msgbuf, sizeof(msgbuf), stdin);
        msglen = strlen(msgbuf);
    }
}
/*
开启一个线程,用来处理输入的信息
*/
void mqtt_msg_publish_proc(struct mosquitto *mosq)
{
    pthread_t TaskId;
    pthread_attr_t att;
    pthread_attr_init(&att);
    pthread_attr_setdetachstate(&att, PTHREAD_CREATE_DETACHED);
    
    int result = pthread_create(&TaskId, &att, mqtt_publish_hander, (void *)mosq);
}

/*
客户端程序入口函数
*/
int main(int argc, char *argv[])
{
    int rc;
#ifndef WIN32
        struct sigaction sigact;
#endif

    mosquitto_lib_init();
    rc = client_config_load(&cfg, CLIENT_SUB, argc, argv);
    if(rc){
        if(rc == 2){
            /* --help */
            print_usage();
        }else{
            fprintf(stderr, "\nUse 'mosquitto_sub --help' to see usage.\n");
        }
        goto cleanup;
    }

    if(cfg.no_retain && cfg.retained_only){
        fprintf(stderr, "\nError: Combining '-R' and '--retained-only' makes no sense.\n");
        goto cleanup;
    }

    if(client_id_generate(&cfg,"mosqsub")){
        goto cleanup;
    }

    mosq = mosquitto_new(cfg.id, cfg.clean_session, &cfg);
    if(!mosq){
        switch(errno){
            case ENOMEM:
                //err_printf(&cfg, "Error: Out of memory.\n");
                break;
            case EINVAL:
                //err_printf(&cfg, "Error: Invalid id and/or clean_session.\n");
                break;
        }
        goto cleanup;
    }
    if(client_opts_set(mosq, &cfg)){
        goto cleanup;
    }
    if(cfg.debug){
        mosquitto_log_callback_set(mosq, my_log_callback);
        mosquitto_subscribe_callback_set(mosq, my_subscribe_callback);
    }
    mosquitto_connect_v5_callback_set(mosq, my_connect_callback);
    mosquitto_message_v5_callback_set(mosq, my_message_callback);

    mosquitto_misc_callback_set(mosq, mqtt_msg_publish_callback);

    rc = client_connect(mosq, &cfg);
    if(rc){
        goto cleanup;
    }

#ifndef WIN32
    sigact.sa_handler = my_signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;

    if(sigaction(SIGALRM, &sigact, NULL) == -1){
        perror("sigaction");
        goto cleanup;
    }

    if(cfg.timeout){
        alarm(cfg.timeout);
    }
#endif

//连接成功之后,开启线程
    mqtt_msg_publish_proc(mosq);

//循环事件处理,遇到异常事件将退出该循环
    rc = mosquitto_loop_forever(mosq, -1, 1);

    mosquitto_destroy(mosq);
    mosquitto_lib_cleanup();

    if(cfg.msg_count>0 && rc == MOSQ_ERR_NO_CONN){
        rc = 0;
    }
    client_config_cleanup(&cfg);
    if(rc){
        //err_printf(&cfg, "Error: %s\n", mosquitto_strerror(rc));
    }
    return rc;

cleanup:
    mosquitto_lib_cleanup();
    client_config_cleanup(&cfg);
    return 1;
}
  1. 编译客户端程序,生成一个名为”mqtt_client”的可执行文件。

  1. 在服务端开启 mosquitto服务端。

运行命令:mosquitto -c /etc/mosquitto/mosquitto.conf&

如下图所示:

5.mqtt_client客户端消息发布。

运行客户端程序,如下图所示:

./mqtt_client -h 192.168.9.68 -p 1883 -t "subtopic/test"

参数说明:

-h:指定mqtt服务端地址

-p:指定mqtt服务端端口

-t:用于接收数据的订阅主题

运行订阅客户端mosquitto_sub,用来接收消息,如下图所示:

由上图可知,mqtt_client发布的消息已经被正确接收。

6.mqtt_client客户端消息订阅

运行发布客户端mosquitto_pub,给mqtt_client发布消息,如下所示:

从mqtt_client客户端查看消息订阅结果,如下图所示:

至此,mqtt_client客户端程序的发布与订阅功能已经基本验证完毕。

总结:经过修改的MQTT客户端程序,可以实现与服务端接入,为以后诸多领域下的终端、网关设备接入云平台提供了简单的实例,后期可基于该程序根据具体的业务进行完善以及优化,给开发人员或者运维人员提供方便。MQTT由于开放源代码,耗电量小等特点,将会其它消息推送领域会有更多的贡献,例如在物联网领域,传感器与服务器的通信,信息的收集,MQTT都可以作为考虑的方案之一。在未来MQTT会进入到我们生活的各个方面。

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

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

相关文章

蓝桥杯三月刷题 第一天

文章目录&#x1f4a5;前言&#x1f609;解题报告&#x1f4a5;数列求值&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;质数&#x1f914;一、思路:&#x1f60e;二、代码&#xff1a;&#x1f4a5;饮料换购&#x1f914;一、思路:&#x1f60e;二、代…

23.3.4打卡 AtCoder Beginner Contest 291(Sponsored by TOYOTA SYSTEMS)A~E

F题题面都看不懂嘞!开摆! 没找到合适的markdown, 截图网页翻译了我真是天才 比赛链接: https://atcoder.jp/contests/abc291 A题 题意 给出一个字符串, 找到第一个大写字母的下标 简单题就不多说了, 直接放代码 代码 void solve() {cin>>str;nstr.size();str"…

CentOS7操作系统安装nginx实战(多种方法,超详细)

文章目录前言一. 实验环境二. 使用yum安装nginx2.1 添加yum源2.1.1 使用官网提供的源地址&#xff08;方法一&#xff09;2.1.2 使用epel的方式进行安装&#xff08;方法二&#xff09;2.2 开始安装nginx2.3 启动并进行测试2.4 其他的一些用法&#xff1a;三. 编译方式安装ngin…

Kali、Metasploitable2部署

1、安装VMWare虚拟机及metasploitable2软件 链接&#xff1a;https://pan.baidu.com/s/1rqhjh1P9VJg5Q1esBgpZ-A 提取码&#xff1a;dc66 metasploitable2部署很简单&#xff0c;解压后&#xff0c;直接双击后缀.vmx文件&#xff0c;默认账户msfadmin/msfadmin&#xff0c;sud…

php实训报告

实训一 PHP语法基础 一、实训目的 掌握PHP数据类型知识。掌握PHP变量与常量的知识和运用方法。掌握PHP选择结构流程控制的知识及应用。掌握PHP循环结构流程控制的知识及应用。 二、实训工具或设备 主流 PC 机一台&#xff08;要求安装 windows 操作系统&#xff09;&#xff…

基于m-p条件查询代码生成

目录 起因 演示 使用 0.自定义注解 1.定义一个dto的条件查询类 2.调用主程序 效果图 小结 代码 注解 Dto类 完整代码 起因 最近两天一直写后台管理统计的增删改查(很少写增删改查&#xff0c;所以不是很熟练)&#xff0c;几乎每个表都要涉及到条件查询的业务&#xf…

7个常用的原生JS数组方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 7个常用的原生JS数组方法一、Array.map()二、Array.filter三、Array.reduce四、Array.forEach五、Array.find六、Array.every七、Array.some总结一、Array.map() 作用&#…

Transformer 模型:入门详解(1)

动动发财的小手&#xff0c;点个赞吧&#xff01; 简介 众所周知&#xff0c;transformer 架构是自然语言处理 (NLP) 领域的一项突破。它克服了 seq-to-seq 模型&#xff08;如 RNN 等&#xff09;无法捕获文本中的长期依赖性的局限性。事实证明&#xff0c;transformer 架构是…

【数据结构初阶】详解“树”

目录 前言 1.树概念及结构 &#xff08;1&#xff09;树的概念 &#xff08;2&#xff09;树的名词介绍 &#xff08;3&#xff09;树的表示 ​编辑 2.二叉树概念及结构 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;特殊的二叉树 &#xff08;3&#xff0…

sizeof与strlen练习

前言 本篇仅仅是为了更加了解sizeof操作符和strlen函数练习. 对于多条sizeof操作符和strlen函数出现,可能很容易造成头脑不清晰,做题时容易混乱. 目录前言一维数组字符数组情况1:情况2情况3二维数组练习之前请牢记下面这段话.这将是头脑清晰地关键. 提示: sizeof(数组名)&#…

MyBatis高频面试专题

一、介绍下MyBatis中的工作原理 1。介绍MyBatis的基本情况&#xff1a;ORM 2。原理&#xff1a; MyBatis框架的初始化操作处理SQL请求的流程 1.系统启动的时候会加载解析全局配置文件和对应映射文件。加载解析的相关信息存储在 Configuration 对象 Testpublic void test1(…

【ID:17】【20分】A. DS顺序表--类实现

时间限制1秒内存限制128兆字节题目描述用C语言和类实现顺序表属性包括&#xff1a;数组、实际长度、最大长度&#xff08;设定为1000&#xff09;操作包括&#xff1a;创建、插入、删除、查找类定义参考输入第1行先输入n表示有n个数据&#xff0c;即n是实际长度;接着输入n个数据…

HCIE-Cloud Computing LAB备考第二步:逐题攻破--第三题:迁移

迁移 题目 将一台AD服务器迁移到FusionCompute平台,并保障业务正常。 思维导图 markmap内容1 文字介绍 准备Rainbow服务器:在Windows系统安装Rainbow,必须保证其与源端主机、目的端平台互通。关闭防火墙。【首次登录rainbow时,需要注册用户名和密码,考试时根据考题要…

989. 数组形式的整数加法

989. 数组形式的整数加法https://leetcode.cn/problems/add-to-array-form-of-integer/ 难度简单226 整数的 数组形式 num 是按照从左到右的顺序表示其数字的数组。 例如&#xff0c;对于 num 1321 &#xff0c;数组形式是 [1,3,2,1] 。 给定 num &#xff0c;整数的 数组…

【Linux】进程等待 | 详解 wait/waitpid 的 status 参数

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》&#x1f448; 猛戳订阅 &#x1f525; &#x1f4ad; 写在前面&#xff1a;在上一章中我们讲解了进程创建与进程终止&#xff0c;本章我们开始讲解进程等待。进程等待这部分知识相较于前面还是较为复杂的&#xff0…

Vue2的生命周期(详解)

Vue的生命周期一、生命周期的概念二、钩子函数三、Vue2的生命周期3.1 初始化阶段3.2 挂载阶段3.3 更新阶段3.4 销毁阶段一、生命周期的概念 Vue实例的生命周期: 从创建到销毁的整个过程 二、钩子函数 Vue框架内置函数,随着组件的生命周期阶段,自动执行 作用:特定的时间点,执行特…

Unity Lighting -- 向场景中添加光源

本节笔记来实践向场景中添加光源。 除了平行光源外&#xff0c;还有两种常用的光源&#xff1a; 点光源&#xff08;Point Lights&#xff09;&#xff1a;点光源所发出的光是朝四面八方发散的&#xff0c;我们可以用点光源来模拟灯泡之类的发光物体。 聚光灯源&#xff08;Spo…

python每日学9 : windows上配置gitee的远程仓库,git的初步使用

在开发中&#xff0c;如果遇到复杂的项目&#xff0c;使用版本控制是非常有必要的&#xff0c;如果涉及到多端开发&#xff0c;那么还需要使用远程仓库。本文作个简单记录&#xff0c;记录下git初步使用。 1 下载与安装 git还有几个ui版本&#xff0c;但是开始使用的话&#…

【LeetCode】带环链表两道题

第一题&#xff1a;环形链表 问题介绍 给你一个链表的头节点head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪next指针再次到达&#xff0c;则链表中存在环。为了表示给定链表中的环&#xff0c;评测系统内部使用整数pos 来表示链表…

【Storm】【三】Storm 核心概念详解

Storm 核心概念详解 一、Storm核心概念1.1 Topologies&#xff08;拓扑&#xff09;1.2 Streams&#xff08;流&#xff09;1.3 Spouts1.4 Bolts1.5 Stream groupings&#xff08;分组策略&#xff09;二、Storm架构详解2.1 nimbus进程2.2 supervisor进程2.3 zookeeper的作用2.…