關于socket多點傳播和ssdp(一)
1、說明
在制作的過程中,實際上ssdp發現協定特别簡單,隻是加入多點傳播後,發送搜尋的字元串,然後再在單點傳播上接收,如果是發送,則要發送到多點傳播位址,而且,發送的字元串不能出錯,這裡說明作者的一個錯誤,開始時,“MAN: “ssdp:discover”\r\n”,一直寫成了"MAN: ssdp:discover\r\n",是以在單點傳播上沒有收到資料,值得注意!
2、show me the code,以下用boost庫來做多點傳播的接收和發送
socket.bind(
udp::endpoint(boost::asio::ip::address_v4::any(),
receiver ? port ));
以上這句話比較重要,在使用發送的時候,使用多點傳播端口,接收的時候,使用本地的一個端口,切記!
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
// 3rd party includes.
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <string>
static const char msearchmsgfmt[] = "M-SEARCH * HTTP/1.1\r\n"
"Connection: close"
"HOST: 239.255.255.250:1900\r\n"
"ST: %s\r\n"
"MAN: \"ssdp:discover\"\r\n"
"USER-AGENT: qbupnp 1.0"
"MX: 3\r\n\r\n";
void read(boost::asio::ip::udp::socket& socket)
{
boost::asio::ip::udp::endpoint sender;
std::vector<char> buffer;
std::size_t bytes_readable = 0;
for (;;)
{
// Poll until data is available.
while (!bytes_readable)
{
// Issue command to socket to get number of bytes readable.
boost::asio::socket_base::bytes_readable num_of_bytes_readable(true);
socket.io_control(num_of_bytes_readable);
// Get the value from the command.
bytes_readable = num_of_bytes_readable.get();
// If there is no data available, then sleep.
if (!bytes_readable)
{
boost::this_thread::sleep(boost::posix_time::seconds(1));
}
}
// Resize the buffer to store all available data.
buffer.resize(bytes_readable);
// Read available data.
socket.receive_from(
boost::asio::buffer(buffer, bytes_readable),
sender);
// Extract data from the buffer.
std::string message(buffer.begin(), buffer.end());
// Output data.
std::cout << "Received message: ";
std::cout << message << std::endl;
}
}
void write(boost::asio::ip::udp::socket& socket,
boost::asio::ip::udp::endpoint& destination)
{
std::string message;
char buffer[256];
sprintf(buffer, msearchmsgfmt, "upnp:rootdevice");
/*socket.async_send_to(
boost::asio::buffer(buffer, strlen(buffer)), endpoint_,
boost::bind(handle_send_to, this,
boost::asio::placeholders::error));*/
for (unsigned int i = 0; i < 3; ++i)
{
//std::ostringstream stream;
//stream << i;
//message = stream.str();
socket.send_to(boost::asio::buffer(msearchmsgfmt, strlen(msearchmsgfmt)), destination);
std::cout << "Sent message: " << message << std::endl;
}
}
int main(int argc, char* argv[])
{
// Extract command-line arguments.
bool receiver = false;// std::string(argv[1]) == "receive";
boost::asio::ip::address address =
boost::asio::ip::address::from_string("239.255.255.250");
unsigned short port = 1900;
// Create socket.
using boost::asio::ip::udp;
boost::asio::io_service service;
udp::socket socket(service);
socket.open(boost::asio::ip::udp::v4());
// Allow other processes to reuse the address, permitting other processes on
// the same machine to use the multicast address.
socket.set_option(udp::socket::reuse_address(true));
socket.set_option(boost::asio::ip::multicast::enable_loopback(true));
socket.bind(
udp::endpoint(boost::asio::ip::address_v4::any(),
receiver ? port /* same as multicast port */
: 6200 /* any */));
udp::endpoint destination(address, port);
// Join group.
namespace ip = boost::asio::ip;
socket.set_option(ip::multicast::join_group(address));
// Start read or write loops based on command line options.
if (receiver)
read(socket);
else
write(socket, destination);
return 0;
}
3、有關于wireshark 抓包
注意使用port 1900 的包在區域網路裡面應該是有很多的,包含網關的,chrome 發送的ssdp,等等,讀者可以适當自己過濾加上not host 192.168.0.1 諸如此類的網關過濾
4 、 接下來使用http去接收和發送資料
建議
1 使用boost asio庫直接接收發送,甚至直接使用asio
2 使用httplib接收發送
3 使用nlohmann 解析json
4 xml 庫可以使用ixml,或者直接手動解析:
如何手動解析xml
以上為什麼推薦使用httplib,和 nlohmann,這兩個隻要加入頭檔案即可,不用編譯。為什麼使用asio,可以直接使用頭檔案,簡化加載庫,以及asio封裝得非常友善。
5、總結
使用以上工具和方法,作者做出了一個ssdp搜尋和控制工具,仿照libpnp來做的,研讀了很多代碼,讀者可以沿着作者走過的路走一遍。
列印控制點
投屏服務用戶端
總之,dlna和libupnp協定并不難懂,其實就是一系列協定的組合,甚至大量使用了http協定,在檔案點播和直播方面,可以使用rtsp協定和http協定等等,隻要深度研究這些基礎協定,就可以做出相應的産品。
以上都是作者的事件經驗,讀者需要交流,可以使用[email protected],或者微信聯系。