Как стать автором
Обновить
370.19
FirstVDS
Виртуальные серверы в ДЦ в Москве и Амстердаме

Один из альтернативных протоколов для интернета вещей — CoAP

Время на прочтение15 мин
Количество просмотров6.1K

Источник

Говоря об интернете вещей, мне сразу приходит в голову связка из разных устройств или датчиков, соединённых с помощью беспроводных каналов связи с удалёнными клиентами, используя посредника — Mqtt broker.

Однако интернет вещей не ограничен только этим протоколом, и в этой статье мы поговорим о другом достаточно перспективном протоколе — CoAP.

Сравнение протоколов


На картинке ниже показан пример двух протоколов и их разницы визуально.



Mqtt


Несмотря на то что оба протокола предназначены для решения вопросов IoT, они имеют существенные различия, касающиеся как логики работы, так и способа передачи данных.

Под логикой работы здесь подразумевается то, что, по сути, и клиенты, публикующие некую информацию, и клиенты, читающие эту информацию, являются равнозначными в процессе общения через брокер.

Осуществляется это общение с помощью текстовых сообщений, которые возможно оставлять по специальным адресам, которые называются топиками. Каждый из клиентов может создать топик, на обновления в котором могут подписаться другие клиенты.

При появлении некой новой информации она публикуется клиентом в топике, и это обновление сразу видят все подписанные на него.

Каждый клиент может создать произвольное количество топиков и подписаться на также произвольное количество. Таким образом, возможно образование сложной перекрёстной структуры подписок и публикации.

Говоря другими словами, здесь используется модель публикации и подписки.

CoAP


Протокол был разработан рабочей группой в 2014 году.

В отличие от Mqtt, протокол CoAP предназначен для непосредственного соединения двух узлов между собой, используя взаимодействие в рамках логики сервер/клиент: здесь, во время взаимодействия с клиентом, сервер делает доступным свои ресурсы по определённому адресу, а клиенты обращаются к том адресу с помощью стандартных HTTP-методов:

  • GET (получить ресурс);
  • POST (создать ресурс);
  • PUT (обновить ресурс);
  • DELETE ( удалить ресурс).

Поэтому протокол соответствует модели REST, и обращение к ресурсу в рамках этого протокола практически никак не отличается от обращения к обычному веб-ресурсу, и клиент может даже не знать, что он общается с локальным датчиком.

Другими словами, можно назвать его более упрощённой версией http.

В рамках протокола существует четыре типа сообщений:

  • CON — такое сообщение (запрос/ответ) требует ответного сообщения о подтверждении;
  • ACK — ответное сообщение на пришедшие сообщение типа CON;
  • NON — сообщение (запрос/ответ), не требующее ответного подтверждения;
  • RST — сброс сообщения (Reset). Такое сообщение отправляется в ответ, если полученная информация частично отсутствует или её невозможно принять. В том числе подобные сообщения можно использовать для того, чтобы пинговать узел, отправляя на узел пустое сообщение типа CON.

Более подробно о структуре сообщений можно почитать здесь.

Протокол также позволяет объединение устройств в группы, для рассылки группового сообщения.

Обмен сообщениями между сервером и клиентом возможен в асинхронном режиме, что существенно ускоряет работу системы.

Информация об имеющихся в доступе ресурсах доступна клиенту в виде списка, включая метаданные. Это позволяет клиенту понять, какие ресурсы есть в наличии и их формат.

CoAP был специально разработан для работы с устройствами, которые имеют весьма ограниченный объём ресурсов, например, порядка 10 КБ оперативной памяти и 100 КБ под прошивку.

Кроме того, спецификация протокола рассчитана на достаточно скромные сетевые возможности, и именно поэтому в его рамках не реализуются многоуровневые транспорты для передачи сообщений. Вместо этого используется протокол UDP поверх IP.

В рамках этого протокола возможно общение не только между двумя устройствами с ограниченными аппаратными и сетевыми возможностями, но и общение между слабым клиентом и более мощным сервером, например, имеется такая серверная реализация на базе java для установки на сервер или ПК, как Californium или Leshan — надстройка над Californium.

Если говорить о надёжности двух протоколов, то для критических коммуникаций имеет смысл выбирать Mqtt (там, где это возможно), так как он обеспечивает качество обслуживания (QoS), гарантирующее доставку и хранение сообщения на брокере. С другой стороны, CoAP хорош для применения в рамках сбора данных с датчиков, а QoS может быть реализован на стороне приложения.

Если же хочется протестировать работу этого протокола, существует достаточно простая библиотека CoAP-simple-library.

Она содержит в себе ряд примеров, среди которых и работа сервера/клиента на базе esp32:



Код esp32
/*
Copyright (c) 2022 Hirotaka Niisato
This software is released under the MIT License.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <WiFi.h>
#include <WiFiUdp.h>
#include <coap-simple.h>

const char* ssid     = "your-ssid";
const char* password = "your-password";

// CoAP client response callback
void callback_response(CoapPacket &packet, IPAddress ip, int port);

// CoAP server endpoint url callback
void callback_light(CoapPacket &packet, IPAddress ip, int port);

// UDP and CoAP class
// other initialize is "Coap coap(Udp, 512);"
// 2nd default parameter is COAP_BUF_MAX_SIZE(defaulit:128)
// For UDP fragmentation, it is good to set the maximum under
// 1280byte when using the internet connection.
WiFiUDP udp;
Coap coap(udp);

// LED STATE
bool LEDSTATE;

// CoAP server endpoint URL
void callback_light(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("[Light] ON/OFF");
  
  // send response
  char p[packet.payloadlen + 1];
  memcpy(p, packet.payload, packet.payloadlen);
  p[packet.payloadlen] = NULL;
  
  String message(p);

  if (message.equals("0"))
    LEDSTATE = false;
  else if(message.equals("1"))
    LEDSTATE = true;
      
  if (LEDSTATE) {
    digitalWrite(9, HIGH) ; 
    coap.sendResponse(ip, port, packet.messageid, "1");
  } else { 
    digitalWrite(9, LOW) ; 
    coap.sendResponse(ip, port, packet.messageid, "0");
  }
}

// CoAP client response callback
void callback_response(CoapPacket &packet, IPAddress ip, int port) {
  Serial.println("[Coap Response got]");
  
  char p[packet.payloadlen + 1];
  memcpy(p, packet.payload, packet.payloadlen);
  p[packet.payloadlen] = NULL;
  
  Serial.println(p);
}

void setup() {
  Serial.begin(9600);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // LED State
  pinMode(9, OUTPUT);
  digitalWrite(9, HIGH);
  LEDSTATE = true;
  
  // add server url endpoints.
  // can add multiple endpoint urls.
  // exp) coap.server(callback_switch, "switch");
  //      coap.server(callback_env, "env/temp");
  //      coap.server(callback_env, "env/humidity");
  Serial.println("Setup Callback Light");
  coap.server(callback_light, "light");

  // client response callback.
  // this endpoint is single callback.
  Serial.println("Setup Response Callback");
  coap.response(callback_response);

  // start coap server/client
  coap.start();
}

void loop() {
  delay(1000);
  coap.loop();
}
/*
if you change LED, req/res test with coap-client(libcoap), run following.
coap-client -m get coap://(arduino ip addr)/light
coap-client -e "1" -m put coap://(arduino ip addr)/light
coap-client -e "0" -m put coap://(arduino ip addr)/light
*/




Как заявляет автор библиотеки, для работы этого кода требуется ещё дополнительно и серверная часть (т.к. код библиотеки реализует клиент) на базе microcoap или lipcoap сервер.

Один из тестеров этой библиотеки пробовал наладить на её базе соединение между компьютером и микроконтроллером, однако он отметил несовершенство самой библиотеки, которое (на его взгляд) не позволило это сделать, однако он описал свой опыт соединения двух esp32 между собой.

На мой взгляд, это конечно не совсем чистый пример, так как выше мы говорили о том, что библиотека больше предназначена для достаточно малоресурсных микроконтроллеров, к которым esp32 явно не относятся.

Возвращаясь к библиотекам, с моей точки зрения, гораздо более интересным примером является Thing.CoAP, так как она не зависит от платформы и была успешно протестирована как на базе операционных систем Windows и Linux, так и микроконтроллеров ESP32 и ESP8266.

К библиотеке приложена достаточно развёрнутая база примеров, в которых показана реализация как клиента, так и сервера.

Что касается возможностей их обоих, то, как говорится, «я просто оставлю это здесь»:



Библиотека предоставляет хорошо документированный и развёрнутый пример, касательно, например, esp32 (и не только), что даёт хорошее понимание работы как серверной, так и клиентской части.

Например, в коде ниже мы видим, как настраивается простой клиент, который имеет функцию sendMessage(), которая отрабатывает при начальной инициализации и шлёт запрос GET, сразу выводя ответ сервера:

void sendMessage(){
  //Make a post
  coapClient.Get("create1", "", [](Thing::CoAP::Response response){
      std::vector<uint8_t> payload = response.GetPayload();
      std::string received(payload.begin(), payload.end());
      Serial.println("Server sent the following message:");
      Serial.println(received.c_str());
      delay(5000);
      sendMessage();
  });
}



Код простого клиента
/*
MIT License

Copyright (c) 2020 Alv3s

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <WiFi.h>

//Include Thing.CoAP
#include "Thing.CoAP.h"

//[RECOMMENDED] Alternatively, if you are NOT using Arduino IDE
//you can include each file you need as bellow: 
//#include "Thing.CoAP/Client.h"
//#include "Thing.CoAP/ESP/UDPPacketProvider.h"

//Declare our CoAP client and the packet handler
Thing::CoAP::Client coapClient;
Thing::CoAP::ESP::UDPPacketProvider udpProvider;

//Change here your WiFi settings
char* ssid = "YourWiFiSSID";
char* password = "YourWiFiPassword";

void sendMessage(){
  //Make a post
  coapClient.Get("create1", "", [](Thing::CoAP::Response response){
      std::vector<uint8_t> payload = response.GetPayload();
      std::string received(payload.begin(), payload.end());
      Serial.println("Server sent the following message:");
      Serial.println(received.c_str());
      delay(5000);
      sendMessage();
  });
}

void setup() {
  //Initializing the Serial
  Serial.begin(115200);
  Serial.println("Initializing");
  
  //Try and wait until a connection to WiFi was made
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");
  Serial.println("My IP: ");
  Serial.println(WiFi.localIP());

  //Configure our server to use our packet handler (It will use UDP)
  coapClient.SetPacketProvider(udpProvider);
  IPAddress ip(104, 196, 15, 150);

  //Connect CoAP client to a server
  coapClient.Start(ip, 5683);
  
  //Send A Message
  sendMessage();
}

void loop() {
  coapClient.Process();
}




А ниже показан уже гораздо более интересный пример. Хорошо видно, как подробно реализован процесс исследования клиентом ресурсов на сервере:



Код исследующего клиента
/*
MIT License

Copyright (c) 2020 Alv3s

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <WiFi.h>

//Include Thing.CoAP
#include "Thing.CoAP.h"

//[RECOMMENDED] Alternatively, if you are NOT using Arduino IDE
//you can include each file you need as bellow: 
//#include "Thing.CoAP/Client.h"
//#include "Thing.CoAP/ESP/UDPPacketProvider.h"
//#include "Thing.CoAP/WebLink.h"

//Declare our CoAP client and the packet handler
Thing::CoAP::Client coapClient;
Thing::CoAP::ESP::UDPPacketProvider udpProvider;

//Change here your WiFi settings
char* ssid = "YourWiFiSSID";
char* password = "YourWiFiPassword";

void setup() {
 //Initializing the Serial
 Serial.begin(115200);
 Serial.println("Initializing");

 //Try and wait until a connection to WiFi was made
 WiFi.begin(ssid, password);
 while (WiFi.status() != WL_CONNECTED) {
  delay(1000);
  Serial.println("Connecting to WiFi..");
 }
 Serial.println("Connected to the WiFi network");
 Serial.println("My IP: ");
 Serial.println(WiFi.localIP());

 //Configure our server to use our packet handler (It will use UDP)
 coapClient.SetPacketProvider(udpProvider);
 IPAddress ip(104, 196, 15, 150);

 //Connect CoAP client to a server
 coapClient.Start(ip, 5683);
 
 //Make the discovery
 coapClient.Discover([](Thing::CoAP::Response& response) {
  std::vector<uint8_t> payload = response.GetPayload();
  std::string received(payload.begin(), payload.end());

  //Parse the response to weblinks
  std::list<Thing::CoAP::WebLink> links = Thing::CoAP::WebLink::ParseString(received);
  for (auto& link : links)
  {
   //Print the URI of the resource
   Serial.println(link.GetURI().c_str());
   
   //Prints if the resource is observable or not
   Serial.print("\t");
   Serial.print("Observable: ");
   Serial.println((link.IsObservable() ? " Yes" : "No"));
   
   //Prints the content format of the resource
   Serial.print("\t");
   Serial.print("Content Format: ");
   switch (link.GetContentFormat())
   {
case Thing::CoAP::ContentFormat::ApplicationEXI: Serial.println("ApplicationEXI");
break;
case Thing::CoAP::ContentFormat::ApplicationJSon: Serial.println("ApplicationJSon");
break;
case Thing::CoAP::ContentFormat::ApplicationLinkFormat: Serial.println("ApplicationLinkFormat");
break;
case Thing::CoAP::ContentFormat::ApplicationOctetStream: Serial.println("ApplicationOctetStream");
break;
case Thing::CoAP::ContentFormat::ApplicationXML: Serial.println("ApplicationXML");
break;
case Thing::CoAP::ContentFormat::TextPlain: Serial.println("TextPlain");
break;
   }
   
   //If the link has title, print the title
   if (link.HasTitle())
   {
    Serial.print("\t");
    Serial.print("Title: ");
    Serial.println(link.GetTitle().c_str());
   }
   
   //If the link has Interface Description, print the Interface Description
   if (link.HasInterfaceDescription())
   {
    Serial.print("\t");
    Serial.print("Interface Description: ");
    Serial.println(link.GetInterfaceDescription().c_str());
   }
   
   //If the link has Maximum Size Estimate, print the Maximum Size Estimate
   if (link.HasMaximumSizeEstimate())
   {
    Serial.print("\t");
    Serial.print("Maximum Size Estimate: ");
    Serial.println(link.GetMaximumSizeEstimate());
   }
   
   //If the link has Resource Type, print the Resource Type
   if (link.HasResourceType())
   {
    Serial.print("\t");
    Serial.print("Resource Type: ");
    Serial.println(link.GetResourceType().c_str());
   }
  }
 });
}

void loop() {
  coapClient.Process();
}




В свою очередь, код сервера также снабжён подробными комментариями, и хорошо видно, как происходит «расшаривание» ресурсов LED и BUTTON:


  //Создание ресурса под названием "LED"
  //Параметр true - означает, что этот ресурс доступен для наблюдения
  server.CreateResource("LED", Thing::CoAP::ContentFormat::TextPlain, true)

  //Создание ресурса под названием "Button"
  server.CreateResource("Button", Thing::CoAP::ContentFormat::TextPlain, false)



Полный код сервера
/*
MIT License

Copyright (c) 2020 Alv3s

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <WiFi.h>

//Include Thing.CoAP
#include "Thing.CoAP.h"

//[RECOMMENDED] Alternatively, if you are NOT using Arduino IDE
//you can include each file you need as bellow: 
//#include "Thing.CoAP/Server.h"
//#include "Thing.CoAP/ESP/UDPPacketProvider.h"

//Declare our CoAP server and the packet handler
Thing::CoAP::Server server;
Thing::CoAP::ESP::UDPPacketProvider udpProvider;

//Change here your WiFi settings
char* ssid = "YourWiFiSSID";
char* password = "YourWiFiPassword";

#define LED 2
#define BUTTON 0

void setup() {
  //Initializing the Serial
  Serial.begin(115200);
  Serial.println("Initializing");

  //Configure the LED as output
  pinMode(LED, OUTPUT);
  //Configure Button
  pinMode(BUTTON, INPUT_PULLUP);
  
  //Try and wait until a connection to WiFi was made
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }
  Serial.println("Connected to the WiFi network");
  Serial.println("My IP: ");
  Serial.println(WiFi.localIP());

  //Configure our server to use our packet handler (It will use UDP)
  server.SetPacketProvider(udpProvider);

  //Create an resource called "LED"
  //True means that this resource is observable
  server.CreateResource("LED", Thing::CoAP::ContentFormat::TextPlain, true)
 
    //We are here configuring telling our server that,
    //when we receive a "GET" request to this endpoint,
    //run the the following code
    .OnGet([](Thing::CoAP::Request & request) { 
      Serial.println("GET Request received for endpoint 'LED'");

      //Read the state of our led.
      std::string result;
      if(digitalRead(LED) == HIGH)
        result = "On";
      else
        result = "Off";

       //Return the current state of our "LED".
      return Thing::CoAP::Status::Content(result);
 
    //We are here configuring telling our server that,
    //when we receive a "POST" request to this endpoint,
    //run the the following code
    }).OnPost([](Thing::CoAP::Request& request) {  
      Serial.println("POST Request received for endpoint 'LED'");

      //Get the message sent fromthe client and parse it to a string      
      auto payload = request.GetPayload();
      std::string message(payload.begin(), payload.end());
      
      Serial.print("The client sent the message: ");
      Serial.println(message.c_str());

      //If the message is "On" we will turn the LED on.
      if(message == "On") { 
        digitalWrite(LED, HIGH);

      //If it is "Off" we will turn the LED off.     
      } else if (message == "Off") {
        digitalWrite(LED, LOW);

      //In case any other message is received
      //we will respond a "BadRequest" error.     
      } else {
        return Thing::CoAP::Status::BadRequest();
      }

      //In case "On" or "Off" was received, we will return "Ok"
      //with a message saying "Command Executed".
      return Thing::CoAP::Status::Content("Command Executed");
    });

  //Create an resource called "Button"
  server.CreateResource("Button", Thing::CoAP::ContentFormat::TextPlain, false)

    
    //We are here configuring telling our server that,
    //when we receive a "GET" request to this endpoint,
    //run the the following code    
    .OnGet([](Thing::CoAP::Request & request) {
      Serial.println("GET Request received for endpoint 'Button'");

      //Read the state of our led.
      std::string result;
      if(digitalRead(BUTTON) == HIGH)
        result = "On";
      else
        result = "Off";

       //Return the current state of our "LED".
      return Thing::CoAP::Status::Content(result);
    });

    server.Start();
}

void loop() {
  server.Process();
}



Итог


В завершение следует сказать, что этот протокол является специально разработанным для применения на слабых устройствах, и сообщения в рамках протокола легко передаются по сети.

Протокол позволяет включить средства обеспечения безопасности через настройку DTLS, что является эквивалентным 3072-битному шифрованию RSA.

На сайте проекта, в разделе реализаций, представлено большое количество возможностей для разворачивания протокола как на микроконтроллерах, так и на серверах, а также предоставлены средства доступа через браузер к ресурсам конечных устройств.

По сравнению с Mqtt, он является более интересным для ряда применений, так как позволяет убрать временной лаг (как минимум), который неизменно возникает из-за наличия посредника, или же, в некоторых случаях, такая работа с посредниками является избыточной, тогда как устройства могут общаться напрямую друг с другом.

Оба протокола, и Mqtt, и CoAP, не требуют постоянного соединения, что положительно сказывается на продолжительности работы микроконтроллеров от компактных источников энергии, однако ввиду того, что CoAP использует в своей основе протокол UDP, система, построенная на его базе, более подвержена потерям сообщений, в противовес Mqtt, где с помощью QoS (Quality of Service) можно в значительной степени их минимизировать.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
Теги:
Хабы:
Всего голосов 6: ↑5 и ↓1+10
Комментарии2

Публикации

Информация

Сайт
firstvds.ru
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия
Представитель
FirstJohn