Поиск устройств в сети по SSDP с помощью Poco

  • Tutorial
В данной небольшой заметке-примере я опишу как найти устройства в сети по протоколу SSDP (Simple Service Discovery Protocol) используя библиотеку Poco на C++.

Оговорю, что в платную полную версию Poco входят классы для работы UpnP. Но для моих целей вполне хватило базовой версии Poco, которая и так умет работать с UDP.

На счет протокола SSDP, он довольно старый единственной нормальной документацией по нему которую я смог найти оказался черновик официальной спецификации. С довольной большим количеством буковок. ;-)

Суть работы протокола следующая:

Послать в сети широковещательный (broadcast) запрос — UDP пакет по адресу 239.255.255.250, порт назначения 1900.

Само тело запроса (пакета) можно посмотреть в исходном коде. Оговорюсь, что единственным полем, значение которого возможно придется меня это ST: в нем указывается тип устройств от которых мы хотим получить ответ.

Так как это протокол UDP, тут нет гарантированного ответа как вы могли привыкнуть при работе с HTTP. HTTP работает по принципу запрос-ответ.

В нашем же случае просто все устройства которые анонсируют себя в сеть, посылают UDP пакет в ответ на адрес с которого был послан запрос, ВАЖНО, ответ приходит не на 1900 порт, а на порт с которого был послан запрос (Source Port).

Так как UPD не дает никаких гарантий кроме целостности самих пакетов. То будем на протяжении 3 секунд слушать Socket (порт) с которого был отправлен запрос.

Собираем все ответы, а потом парсим ответы с помощью регулярных выражений с той же библиотеки Poco.

Есть другой вариант, просто слушать MulticastSocket, этот вариант приведен в документации к Poco на странице 17.
Но мне он не подошел, так как искомое мной устройство не анонсируют себя в сеть.

В запросе поле ST может принимать значения:

  • upnp:rootdevice
  • ssdp:all

Это для поиска всех устройств. В моем случае здесь я указываю конкретный класс устройств от которых хочу получить. Но для статьи я оставил upnp:rootdevice

Также оговорюсь, что C++ для меня новый язык.

Итак:

#include <iostream>

#include "Poco/Net/DatagramSocket.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/Timespan.h"
#include "Poco/Exception.h"
#include "Poco/RegularExpression.h"
#include "Poco/String.h"

using std::string;
using std::vector;
using std::cin;
using std::cout;
using std::endl;

using Poco::Net::SocketAddress;
using Poco::Net::DatagramSocket;
using Poco::Timespan;
using Poco::RegularExpression;

void MakeSsdpRequest(vector<string>& responses,string st = "") {
	if (st.empty()) st = "upnp:rootdevice";
	//if (st.empty()) st = "ssdp:all";

	string message = "M-SEARCH * HTTP/1.1\r\n"
		"HOST: 239.255.255.250:1900\r\n"
		"ST:" + st + "\r\n"
		"MAN: \"ssdp:discover\"\r\n"
		"MX:1\r\n\r\n";

	DatagramSocket dgs;
	SocketAddress destAddress("239.255.255.250", 1900);
	dgs.sendTo(message.data(), message.size(), destAddress);
	dgs.setSendTimeout(Timespan(1, 0));
	dgs.setReceiveTimeout(Timespan(3, 0));
	char buffer[1024];
	try {
		// Здесь можно и бесконечный цикл, так как отвалимся по timeout. Но на всякий ограничиваю 1000 пакетами, так как, если кто-то решит отвечать постоянно, timeout не наступит.
		for (int i = 0; i < 1000; i++) {
			int n = dgs.receiveBytes(buffer, sizeof(buffer));
			buffer[n] = '\0';
			responses.push_back(string(buffer));
		}
	}
	catch (const Poco::TimeoutException &) { }
}

string ParseIP(string str) {
	try {
		RegularExpression re("(location:.*://)([a-zA-Z_0-9\\.]*)([:/])", RegularExpression::RE_CASELESS);
		vector<string> vec;
		re.split(str, 0, vec);
		if (vec.size() > 2) return vec[2];
	}
	catch (const Poco::RegularExpressionException&) { cout << "RegularExpressionException" << endl; }
	return "";
}

int main()
{
	vector<string> ips, responses;
	MakeSsdpRequest(responses);

	for (string response : responses) {
		// Проверяю статус ответа.
		if (response.find("HTTP/1.1 200 OK", 0) == 0) {
			string ip = ParseIP(response);
			if (!ip.empty()) ips.push_back(ip);
		}
	}

	sort(ips.begin(), ips.end());
	ips.erase(unique(ips.begin(), ips.end()), ips.end());
	for (const string& ip : ips) {
		cout << "IP: " << ip << endl;
	}

	cout << "Press Enter" << endl;
	cin.get();
	return  0;
}
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 10

  • UFO just landed and posted this here
      –1
      Мне нужен!
      И уверен другим может пригодиться.
      Poco идет под свободной лицензией Boost Software License

      https://ru.wikipedia.org/wiki/Boost_Software_License

      На счет ваше реализации, супер!
      Предлагаю Вам оформить в виде отдельно статьи.

      Кроме того заметил, что у вас в списке ОС нет Windows.
      Мой же вариант полностью кросс платформенный.
        +2
        Господа, перестаньте закапывать сей ресурс подобными статьями
        • UFO just landed and posted this here
            0
            Ну как бы если выкинуть корпоративную чушь и такие статьи как эта, то вполне себе ничего, ну будет по 1-2 статьи в день, тоже неплохо.
        +2
        Так как Вы пишете, что являетесь новичком в C++, то позвольте покритиковать фрагмент Вашего кода:
        string message = "M-SEARCH * HTTP/1.1\r\n";
        	message += "HOST: 239.255.255.250:1900\r\n";
        	message += "ST:" + st + "\r\n";
        	message += "MAN: \"ssdp:discover\"\r\n";
        	message += "MX:1\r\n\r\n";
        

        Это лучше написать так:
        string message = "M-SEARCH * HTTP/1.1\r\n"
        	"HOST: 239.255.255.250:1900\r\n"
        	"ST:" + st + "\r\n"
        	"MAN: \"ssdp:discover\"\r\n"
        	"MX:1\r\n\r\n";
        


          0
          Спасибо, подправил.
          0
          Раз вы C++ еще только изучаете, то еще несколько советов:

          Исключения не следует ловить по значению, т.к. может произойти «срезка» объекта. Лучше ловить по константной ссылке:
          catch (const Poco::TimeoutException &) { }


          Вот здесь вам не нужна копия IP-адреса для печати:
          for (string ip : ips) {
          		cout << "IP: " << ip << endl;
          	}

          Лучше брать ссылку на очередное значение, причем константную ссылку дабы подчеркнуть, что ничего изменяться не будет:
          for (const string & ip : ips) {
          		cout << "IP: " << ip << endl;
          	}

          Аналогично, полагаю, имеет смысл сделать и с циклом по responses.

          В C++11 и выше функцию MakeSsdpRequest имеет смысл переписать так, чтобы она возвращала вектор, а не получала его по неконстантной ссылке:
          vector<string> MakeSsdpRequest(string st = "") {
            vector<string> responses;
            ...
            return responses;

          Что позволит использовать ее вот так:
          int main()
          {
          	auto responses = MakeSsdpRequest();

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

          В C++ редко доводиться видеть конструкции вида:
          using std::string;
          using std::vector;
          using std::cin;
          using std::cout;
          using std::endl;
          
          using Poco::Net::SocketAddress;
          using Poco::Net::DatagramSocket;
          using Poco::Timespan;
          using Poco::RegularExpression;

          Обычно довольствуются using namespace, особенно в таких коротких программах примерах:
          using namespace std;
          using namespace Poco;
          using namespace Poco::Net;
            0
            Спасибо за конструктивный комментарий.

            const и & — подправил.
            using namespace xxx не использую, что бы меня тут не заплевали, мол засоряю…

            А вот на счет возврата vector как результат функции, ведь в этом случае произойдет копирование вектора.
            На счет того, что такой подход удобнее в использовании, конечно согласен.
            Подскажите пожалуйста чем он удобнее в защите от исключений?

              0
              А вот на счет возврата vector как результат функции, ведь в этом случае произойдет копирование вектора.

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

              У вас может быть так:
              std::vector<string> responses;
              try {
                MakeSspdRequest(responses);
              } catch(...) { ... }
              for(const auto & r : responses) // В каком состоянии сейчас responses?
              

              Если у вас вот такой код:
              try {
                auto responses = MakeSspdRequest();
                ...
              } catch(...) {...}
              То в случае исключения у вас responses просто не останется и не возникнет вопросов о том, что там внутри находится.

          Only users with full accounts can post comments. Log in, please.