原理:
1.将socket加入239.255.255.250,端口 1900
2.客户端:通过设置setsockopt +IPPROTO_IP,IP_ADD_MEMBERSHIP属性,可向ssdp组进行组播。
3.服务端:通过设置绑定239.255.255.250:1900进行数据接收,通过setsockopt +IPPROTO_IP,IP_ADD_MEMBERSHIP属性 加入组播。
容易错误的地方:
服务端打印sendto成功,但是通过wireshark抓包发现没有组播发送到239.255.255.250
这是因为:sendto的时候,用的sockaddr_in是通过recvfrom得到的:recvfrom得到的是具体的某个从组播拷贝的数据的源ip,这是单播。如果要组播发送,则要发送到这个地址:
m_cddr.sin_family = AF_INET;
m_cddr.sin_addr.s_addr = inet_addr("239.255.255.250");
m_cddr.sin_port = htons(1900);
客户端代码:
/*
Author:zhuoyong
Date:2022-04-07
Mark:人脸识别客户端代码
*/
#ifndef CSSDPCLIENT_H
#define CSSDPCLIENT_H
#include<string>
#include<string.h>
#include<queue>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<thread>
#include<chrono>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<net/if.h>
#include<pthread.h>
#include<fcntl.h>
#define ERR printf
#define RTM printf
#define MAXSSDPSIZE 1024
using namespace std;
class CSSDPClient
{
public:
static void Discover();
//最终加密后的pssword=:md5(<uuid>:<uid>:<pwd>)
static void ModifyNet(string &strUuid,string&strUid,string&strPwd,string &strMask,string&strGateway,string &strDns);
//strCryptPwd=base64(AES(pwd))
static void ModifyUser(string &strUuid,string&strUid,string&strPwd);
protected:
CSSDPClient();
~CSSDPClient();
void SendMsg(const string&str);
void RecvMsg();
void NotifyOne(const string&str);
string MakeSSDHeader();
string MakeSearchMsg();
string MakeModifyNetMsg();
string MakeModifyUserMsg();
static CSSDPClient *Ins();
void InerDiscover(const string&str);
void InitSocket();
private:
static CSSDPClient * g_in;
sockaddr_in m_sddr ;
int m_ssdps;
queue<string> m_que;
pthread_cond_t m_cd;
pthread_mutex_t m_mt;
};
#endif // CSSDPCLIENT_H
/*
* ===========================================================================
*
* Filename: SSDPClient.cpp
* Description: 客户端设备发现 xml协议
* Version: 1.0
* Created: 20220406-12:07
* Revision: none
* Compiler: g++
* Author: (zhouyongku),
* Company:
*
* ===========================================================================
*/
#include"SSDPClient.h"
CSSDPClient *CSSDPClient::g_in=NULL;
//全局变量
//解析IP地址
CSSDPClient::CSSDPClient()
{
m_mt=PTHREAD_MUTEX_INITIALIZER;
m_cd=PTHREAD_COND_INITIALIZER;
std::thread t([&]
{
InitSocket();
while(true)
{
pthread_mutex_lock(&m_mt); // 拿到互斥锁,进入临界区
if( m_que.size()<=0 )pthread_cond_wait(&m_cd,&m_mt); // 令线程等待在条件变量上
pthread_mutex_unlock(&m_mt); // 释放互斥锁
InerDiscover(m_que.front());
m_que.pop();
}
});
t.detach();
}
CSSDPClient::~CSSDPClient()
{
}
CSSDPClient *CSSDPClient::Ins()
{
if( CSSDPClient::g_in == nullptr )
{
CSSDPClient::g_in = new CSSDPClient();
}
return CSSDPClient::g_in;
}
void CSSDPClient::InerDiscover(const string&str)
{
SendMsg( str );
RecvMsg( );
}
void CSSDPClient::InitSocket()
{
struct timeval TimeOut;
TimeOut.tv_sec = 1;
TimeOut.tv_usec = 0;
m_ssdps=socket(AF_INET,SOCK_DGRAM,0);
if( m_ssdps < 0)
{
ERR("CSSDPClient::InitSocket failed of create socket 239.255.255.250:1900");
return;
}
bzero(&m_sddr, sizeof(m_sddr));
m_sddr.sin_family = AF_INET;
m_sddr.sin_addr.s_addr = inet_addr("239.255.255.250");
m_sddr.sin_port = htons(1900);
setsockopt(m_ssdps, SOL_SOCKET, SO_RCVTIMEO, (char *)&TimeOut, sizeof(TimeOut));
ip_mreq mreq;
bzero(&mreq,sizeof(mreq));
mreq.imr_multiaddr.s_addr =inet_addr("239.255.255.250");
mreq.imr_interface.s_addr =htonl(INADDR_ANY);
if( setsockopt(m_ssdps, IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) <0 )
{
ERR("CSSDPServer::InitSocket failed of setsockopt IP_ADD_MEMBERSHIP");
return;
}
// if( bind(m_ssdps, (struct sockaddr *)&m_sddr, sizeof(m_sddr)) < 0 )
// {
// ERR("CSSDPClient::InitSocket failed of bind socket %s:%d",SSDP_MCAST_ADDR,SSDP_PORT);
// }
}
void CSSDPClient::Discover()
{
string str=CSSDPClient::Ins()->MakeSearchMsg();
CSSDPClient::Ins()->NotifyOne(str);
}
void CSSDPClient::ModifyNet(string &strUuid, string &strUid, string &strPwd, string &strMask, string &strGateway, string &strDns)
{
string str=CSSDPClient::Ins()->MakeModifyNetMsg();
CSSDPClient::Ins()->NotifyOne(str);
}
void CSSDPClient::ModifyUser(string &strUuid, string &strUid, string &strPwd)
{
string str=CSSDPClient::Ins()->MakeModifyUserMsg();
CSSDPClient::Ins()->NotifyOne(str);
}
void CSSDPClient::NotifyOne(const string&str)
{
RTM("CSSDPClient::NotifyOne quesize=%d",m_que.size());
pthread_mutex_lock(&m_mt); // 拿到互斥锁,进入临界区
m_que.push( str );
pthread_cond_signal(&m_cd); // 通知等待在条件变量上的消费者
pthread_mutex_unlock(&m_mt); // 释放互斥锁
}
string CSSDPClient::MakeSSDHeader()
{
string strMsg="M-SEARCH * HTTP/1.1\n";
strMsg +="HOST: 239.255.255.250:1900\n";
strMsg +="MAN: \"ssdp:discover\"\n";
strMsg +="MX: 1\n";
strMsg +="ST: urn:dial-multiscreen-org:service:dial:1\n";
strMsg +="USER-AGENT:arm\n";
return strMsg;
}
string CSSDPClient::MakeSearchMsg()
{
string strMsg=MakeSSDHeader();
strMsg +="<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
strMsg +="<Probe>\n";
strMsg +=" <Uuid>";
strMsg += "uuid-test";
strMsg +="</Uuid>\n";
strMsg +=" <Types>inquiry</Types>\n";
strMsg +=" <DeviceType>";
strMsg += "wodepingban";
strMsg +="</DeviceType>\n";
strMsg +="</Probe>";
return strMsg;
}
string CSSDPClient::MakeModifyNetMsg()
{
string strMsg=MakeSSDHeader();
char szMsg[MAXSSDPSIZE]={0};
sprintf( szMsg,
"%s\n"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<Probe>\n"
"<Uuid>testuuid</Uuid>\n"
"<Types>update</Types>\n"
"<DeviceSN>123456sdflsdfjl1023-1asdfgh</DeviceSN>\n"
"<method>modifyipdns</method>\n"
"<MAC>ab-cd-ef-gh-kh</MAC>\n"
"<IPv4Address>192.168.66.24</IPv4Address>\n"
"<IPv4SubnetMask>255.255.255.0</IPv4SubnetMask>\n"
"<IPv4Gateway>192.168.66.254</IPv4Gateway>\n"
"<IPv6Address>::</IPv6Address>\n"
"<IPv6Gateway>::</IPv6Gateway>\n"
"<IPv6MaskLen>64</IPv6MaskLen>\n"
"<DHCP>false</DHCP>\n"
"<Password>kFnsMaQrzmGi89g+6txepC1RNnZMSi/fA16x+UdjFOmqBmoVCc/zeZ8X6oZmLBdWaXnvwTxjLIQBsLsDP0xjHw==</Password>\n"
"</Probe>\n",
strMsg.c_str());
return string(szMsg);
}
string CSSDPClient::MakeModifyUserMsg()
{
string strMsg=MakeSSDHeader();
char szMsg[MAXSSDPSIZE]={0};
sprintf( szMsg,
"%s\n"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<Probe>\n"
"<Uuid>your uuid>\n"
"<Types>update</Types>\n"
"<DeviceSN>your device sn</DeviceSN>\n"
"<method>modifypassword</method>\n"
"<newpassword>your new md5 password</newpassword>\n"
"<Password>your md5 password</Password>\n"
"</Probe>\n");
return string(szMsg);
}
//发送ssdp:discover消息
void CSSDPClient::SendMsg(const string&str)
{
RTM("CSSDPClient::SendMsg msg=%s",str.c_str());
socklen_t len = sizeof(m_sddr);
int nRet=sendto(m_ssdps,str.c_str(),str.length(),0,(sockaddr*)&m_sddr,len);
if( nRet >0 )
{
RTM("CSSDPClient::SendMsg leng=%d",nRet);
}
char szMsg[MAXSSDPSIZE]={0};
nRet = recvfrom( m_ssdps,szMsg,MAXSSDPSIZE,0,(sockaddr*)&m_sddr,&len);
if( nRet >0 )
{
RTM("CSSDPClient::SendMsg RECV MSG=%s",szMsg);
}
}
void CSSDPClient::RecvMsg()
{
socklen_t len = sizeof(m_sddr);
char szMsg[MAXSSDPSIZE]={0};
int num = recvfrom(m_ssdps,szMsg,MAXSSDPSIZE,0,(sockaddr*)&m_sddr,&len);
if( num >0 )
{
string str;
//ParseMsg(buf);
}
}
服务端:
#ifndef CSSDPSERVER_H
#define CSSDPSERVER_H
/*
Author:zhuoyong
Date:2022-04-07
Mark:设备发现服务端代码
*/
#include<string>
#include<string.h>
#include<queue>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<thread>
#include<chrono>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<net/if.h>
#include<pthread.h>
#include<fcntl.h>
#include<mutex>
#define ERR printf
#define RTM printf
#define MAXSSDPSIZE 1024
using namespace std;
class CSSDPServer
{
public:
static void Init( );
protected:
CSSDPServer( );
~CSSDPServer();
void SendMsg(const string&str);
void RecvMsg();
string MakeSSDHeader();
string MakeResponseSearchMsg( );
static CSSDPServer *Ins();
void InitSendSocket();
void InitRecvSocket();
void ProcesMsg( const char *strMsg );
private:
static CSSDPServer * g_in;
sockaddr_in m_sddr ;
sockaddr_in m_cddr ;
int m_ssdps;
int m_ssdpc;
queue<string> m_que;
pthread_cond_t m_cd;
pthread_mutex_t m_mt;
};
#endif // CSSDPSERVER_H
/*
* ===========================================================================
*
* Filename: SSDPServer.cpp
* Description: 服务端设备发现 xml协议
* Version: 1.0
* Created: 20220406-12:07
* Revision: none
* Compiler: g++
* Author: (zhouyongku),
* Company:
*
* ===========================================================================
*/
#include"SSDPServer.h"
CSSDPServer *CSSDPServer::g_in=NULL;
//全局变量
//解析IP地址
CSSDPServer::CSSDPServer( )
{
m_mt=PTHREAD_MUTEX_INITIALIZER;
m_cd=PTHREAD_COND_INITIALIZER;
RTM("CSSDPServer::CSSDPServer");
std::thread t([&]
{
RTM("CSSDPServer::CSSDPServer create thread");
InitSendSocket();
InitRecvSocket();
char szMsg[MAXSSDPSIZE]={0};
socklen_t len = sizeof(m_sddr);
while( true )
{
if((recvfrom(m_ssdps, szMsg, MAXSSDPSIZE, 0, (sockaddr*)&m_sddr, &len)) >= 0)
{
//printf("recvfrom success ip=%s,msg=%s",inet_ntoa(m_sddr.sin_addr),szMsg);
ProcesMsg( szMsg );
memset(szMsg,0,sizeof(szMsg));
}
std::this_thread::sleep_for(std::chrono::seconds(10));
}
});
t.detach();
}
void CSSDPServer::ProcesMsg( const char *strMsg )
{
//and your code here
SendMsg( MakeResponseSearchMsg( ) );
}
CSSDPServer::~CSSDPServer()
{
}
CSSDPServer *CSSDPServer::Ins( )
{
if( CSSDPServer::g_in == nullptr )
{
CSSDPServer::g_in = new CSSDPServer( );
}
return CSSDPServer::g_in;
}
void CSSDPServer::InitSendSocket()
{
struct timeval TimeOut;
TimeOut.tv_sec = 1;
TimeOut.tv_usec = 0;
m_ssdpc=socket(AF_INET,SOCK_DGRAM,0);
if( m_ssdpc < 0)
{
ERR("CSSDPClient::InitSocket failed of create socket 239.255.255.250:1900");
return;
}
bzero(&m_cddr, sizeof(m_cddr));
m_cddr.sin_family = AF_INET;
m_cddr.sin_addr.s_addr = inet_addr("239.255.255.250");
m_cddr.sin_port = htons(1900);
setsockopt(m_ssdpc, SOL_SOCKET, SO_RCVTIMEO, (char *)&TimeOut, sizeof(TimeOut));
ip_mreq mreq;
bzero(&mreq,sizeof(mreq));
mreq.imr_multiaddr.s_addr =inet_addr("239.255.255.250");
mreq.imr_interface.s_addr =htonl(INADDR_ANY);
if( setsockopt(m_ssdpc, IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) <0 )
{
ERR("CSSDPServer::InitSocket failed of setsockopt IP_ADD_MEMBERSHIP");
return;
}
}
void CSSDPServer::InitRecvSocket()
{
RTM("CSSDPServer::InitRecvSocket");
struct timeval TimeOut;
TimeOut.tv_sec = 1;
TimeOut.tv_usec = 0;
m_ssdps=socket(AF_INET,SOCK_DGRAM,0);
if( m_ssdps < 0)
{
ERR("CSSDPServer::InitSocket failed of create socket 239.255.255.250:1900");
return;
}
bzero(&m_sddr, sizeof(m_sddr));
m_sddr.sin_family = AF_INET;
m_sddr.sin_addr.s_addr = INADDR_ANY;
m_sddr.sin_port = htons(1900);
if( bind(m_ssdps, (struct sockaddr *)&m_sddr, sizeof(m_sddr)) < 0 )
{
ERR("CSSDPServer::InitSocket failed of bind socket 0.0.0.0:1900");
return ;
}
ip_mreq mreq;
bzero(&mreq,sizeof(mreq));
mreq.imr_multiaddr.s_addr =inet_addr("239.255.255.250");
mreq.imr_interface.s_addr =htonl(INADDR_ANY);
if( setsockopt(m_ssdps, IPPROTO_IP,IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) <0 )
{
ERR("CSSDPServer::InitSocket failed of setsockopt IP_ADD_MEMBERSHIP");
return;
}
RTM("CSSDPServer::InitRecvSocket success");
}
void CSSDPServer::Init( )
{
CSSDPServer::Ins( );
}
string CSSDPServer::MakeSSDHeader()
{
string strMsg="NOTIFY * HTTP/1.1\n"
"HOST: 239.255.255.250:1900\n"
"CACHE-CONTROL: max-age=66\n"
"LOCATION:xml\n"
"OPT:ns=01\n"
"01-NLS:0\n"
"NT: urn:schemas-upnp-org:service:AVTransport:1\n"
"NTS: ssdp:alive\n"
"SERVER: Ubuntu/16.04\n"
"X-User-Agent: redsonic\n"
"USN:1\n";
return strMsg;
}
string CSSDPServer::MakeResponseSearchMsg()
{
string strMsg=MakeSSDHeader();
char szMsg[MAXSSDPSIZE]={0};
snprintf( szMsg,MAXSSDPSIZE,
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<ProbeMatch>\n"
"<mount>your msg</mount>\n"
"<enable>your msg</enable>\n"
"<platformip>%s</platformip>\n"
"<Uuid>your msg</Uuid>\n"
"<Types>inquiry</Types>\n"
"<DeviceType>CDZSFACEBOX</DeviceType>\n"
"<DeviceSN>your msg</DeviceSN>\n"
"<DeviceName>your msg</DeviceName>\n"
"<MAC>your msg</MAC>\n"
"<IPv4Address>your msg</IPv4Address>\n"
"<IPv4SubnetMask>your msg</IPv4SubnetMask>\n"
"<IPv4Gateway>your msg</IPv4Gateway>\n"
"<IPv6Address>::</IPv6Address>\n"
"<IPv6Gateway>::</IPv6Gateway>\n"
"<IPv6MaskLen>64</IPv6MaskLen>\n"
"<DHCP>::</DHCP>\n"
"<SoftwareVersion>your msg</SoftwareVersion>\n"
"<BootTime>your msg</BootTime>\n"
"<Diskrate>your msg</Diskrate >\n"
"<Cpurate>your msg</Cpurate >\n"
"<Memoryrate>%s</Memoryrate >\n"
"</ProbeMatch>");
strMsg += szMsg;
return strMsg;
}
//发送ssdp:discover消息
void CSSDPServer::SendMsg(const string&str)
{
RTM("CSSDPServer::SendMsg msg=%s",str.c_str());
socklen_t len = sizeof(m_cddr);
int nLen = sendto(m_ssdps,str.c_str(),str.length(),0,(sockaddr*)&m_cddr,len);
if( nLen >0 )
{
RTM("CSSDPServer::SendMsg success to send to %s msg len=%d",inet_ntoa(m_cddr.sin_addr),nLen);
}
else
{
RTM("CSSDPServer::SendMsg failed to send msg");
}
}
void CSSDPServer::RecvMsg()
{
socklen_t len = sizeof(m_sddr);
char szMsg[MAXSSDPSIZE]={0};
int num = recvfrom(m_ssdps,szMsg,MAXSSDPSIZE,0,(sockaddr*)&m_sddr,&len);
if( num >0 )
{
string str;
//ParseMsg(buf);
}
}
测试代码:
#include<SSDPClient.h>
int main(int argc, char *argv[])
{
while( true )
{
CSSDPClient::Discover();
std::this_thread::sleep_for( std::chrono::seconds(10) );
}
return 0;
}
#include"SSDPServer.h"
int main(int argc, char *argv[])
{
CSSDPServer::Init( );
std::this_thread::sleep_for( std::chrono::seconds(100000) );
return 0;
}
抓包:
发送方:ssdpclient 192.168.66.44 发送 search命令
接收方 ssdpserv (我在同一个电脑上测试 ip都是192.168.66.44)