MQTT实现命令FTP

基于Mac

Posted by Gavin on October 30, 2019

空光远流浪

铜柱从年消

概念

MQTT

MQTT是一种轻量级的、灵活的网络协议,适用于IoT场景,其优点如下:

  • 这个轻量级协议可在严重受限的设备硬件和高延迟/带宽有限的网络上实现
  • 它的灵活性使得为 IoT 设备和服务的多样化应用场景提供支持成为可能

为什么IoT场景下不使用其他网络协议,主要还是没钱,物联网设备不可能使用高昂的硬件,便宜的东西性能就差,因此必须压榨性能。而例如HTTP协议,是需要保持客户端和服务器之间的连接的,这对物联网设备来说是不经济的。物联网环境下,功耗低,网络带宽低,延迟高,不能支持复杂的网络协议,而MQTT恰好可以使用于此。

原理

MQTT的架构是基于发布和订阅模型实现的,在MQTT网络中,定义了一个消息代理,多个客户端。代理是一个服务器,它从客户端接收所有消息,然后将这些消息路由到相关的目标客户端。客户端连接到代理,订阅代理中的消息主题;客户端可以将消息和主题发送给代理,发布某个主题内的消息;代理将消息转发给每个订阅该主题的客户端。


实践

mac下安装和使用mosquitto

  1. 安装mosquitto

     brew install mosquitto
    
  2. 启动mosquitto

     brew services start mosquitto
    
  3. 一个终端订阅消息

     mosquitto_sub -t "demo"
    
  4. 一个终端发布消息

     mosquitto_pub -t "demo" -m "hello world!"
    

实现命令FTP功能

  1. 设计
    • 客户端
      • 要求用户验证
      • 发布命令(发布‘命令’话题)
      • 接收服务端传回的命令调用结果(订阅‘结果’话题)
    • 服务端
      • 预先设置用户名和密码
      • 接收客户端命令(订阅‘命令’话题)并执行系统调用
      • 发布调用结果(发布‘结果’话题)
  2. 利用mosquitto封装的接口实现功能

    mosquitto的接口主要封装在mosquitto.h头文件中,利用cmake链接好libmosquitto库即可使用

    CMakeLists如下:

     cmake_minimum_required(VERSION 3.15)
     project(MosquittoFTP)
    	
     set(CMAKE_CXX_STANDARD 14)
    	
     link_libraries(/usr/local/Cellar/mosquitto/1.6.7/lib)
    	
     add_executable(MosquittoFTP myoperator.cpp)
     target_link_libraries(MosquittoFTP -lmosquitto)
    
  3. 服务端
    • 设置用户名密码

        mosq = mosquitto_new(nullptr,session,nullptr);
        mosquitto_username_pw_set(mosq, "test", "123456");
      
    • 处理命令的关键回调函数

        //exec system call
        std::string exec(const char* cmd) {
       std::array<char, 128> buffer;
            std::string result;
            std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
            if (!pipe) {
                throw std::runtime_error("popen() failed!");
            }
            while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
                result += buffer.data();
            }
            return result;
        }
      		
        //exec client command
        void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
      {
            if(message->payloadlen){
            	char *head = static_cast<char*>(message->payload);
            	std::string command(head,head+message->payloadlen);
            	std::cout << message->topic << " " << command << std::endl;
            	std::string result = exec(head);
            	//std::cout << result << std::endl;
            	mosquitto_publish(mosq,nullptr,"Result:",result.length()+1,result.c_str(),0,0);
            }else{
            	std::cout << message->topic << " (NULL)" << std::endl;
            }
      }
      
  4. 客户端

    • 验证

        mosq = mosquitto_new(nullptr,session,nullptr);
        std::string username,password;
        std::cout << "Please input your username!" << std::endl;
        std::cin >> username;
        std::cout << "Please input your password!" << std::endl;
        std::cin >> password;
        mosquitto_username_pw_set(mosq, username.c_str(), password.c_str());
      
    • 接收服务端回显结果

        void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
      {
            if(message->payloadlen){
            	char *head = static_cast<char*>(message->payload);
            	std::string command(head,head+message->payloadlen);
            	std::cout << message->topic << " " << command << std::endl;
            }else{
            	std::cout << message->topic << " (NULL)" << std::endl;
            }
      }
      
  5. 结果
    • 服务端
    • 客户端

参考和源码