📄 upnp.cpp
字号:
#include "upnp.h"
#include "qsocket.h"
#include "qudp.h"
/*
ReloadIp: 获取本机上最类似于192.168.0.x的内网IP
如果本机没有内网IP,则返回INADDR_NONE
*/
static ulong ReloadIp()//get 192.168.0.x
{
int len = 256;
char name[256+1];
hostent *host;
ulong tmpip = INADDR_NONE,
retip = INADDR_NONE;
memset(name, 0, len+1);
if(gethostname(name,len)==0)
{
host = gethostbyname(name);
if(host&&host->h_length>=sizeof(ulong))
{
len = 0;
while(host->h_addr_list[len])
{
tmpip = *((ulong*)(host->h_addr_list[len++]));
if(((tmpip&0xFF)==192&&((tmpip>>8)&0xFF)==168&&((tmpip>>16)&0xFF)==0)
||IsLAN(tmpip)||!is_validip(retip))
{
retip = tmpip;
}
}
}
}
return retip;
}
static const ulong UPNPADDR = 0xFAFFFFEF;
static const int UPNPPORT = 1900;
static const char* URNPREFIX = "urn:schemas-upnp-org:";
static const char* LCA = "Location:";
static const char* LCB = "LOCATION:";
static const char* LCC = "location:";
/*
LCA,LCB,LCC: 有些UPnP设备以LCA作为字段名称,有些则用LCB,有些则用LCC,对于其他那些喜欢乱命名的厂商只好说抱歉了.
*/
QMyUPNP::QMyUPNP()
: m_version(1)
{
}
QMyUPNP::QMyUPNP(const QString& device)
: m_devicename(device), m_version(1)
{
}
QMyUPNP::~QMyUPNP()
{
Clear();
}
/*InternalClear: 清空对象,但保留对象*/
void QMyUPNP::InternalClear()
{
m_baseurl = "";
m_controlurl = "";
m_friendlyname = "";
m_modelname = "";
}
/*Clear: 清空对象,同时对象失效.*/
void QMyUPNP::Clear()
{
m_name = "";
m_description = "";
m_version = 1;
InternalClear();
}
/*
InternalSearch: 查找UPnP设备
实现过程:首先调用InternalSearch2进行查找的操作,如果没有找到,则设置对象的设备名为IGD,再次调用InternalSearch2查找.
之所以分两次查找是因为有些设备支持以服务名查找,而有些设备则只支持以设备名查找.
参数含义:
name: 要查找的服务/设备的名称
type: name表示的是服务或设备(service/device)
repeat: 尝试查找的次数
delay: 查找结果的最长等待时间
version: UPnP版本,默认1
ip: 查询的目标IP,默认为UPnP的广播IP0xFAFFFFEF,也可以指定IP以便当程序运行在某些主机上时也能查找.
lpCall: 一个回调函数,在查找过程中随机调用.
返回值: 成功返回true,否则返回false
*/
bool QMyUPNP::InternalSearch(const QString& name, const char* type, int repeat,
int delay, int version, ulong ip, PUPNPCALLBACK lpCall)
{
if(InternalSearch2(name,type,repeat,delay,version,ip,lpCall))return true;
QString back = m_devicename;
m_devicename = QString("urn:schemas-upnp-org:device:InternetGatewayDevice:") + m_version;
bool ok = InternalSearch2(name,type,repeat,delay,version,ip,lpCall);
m_devicename = back;
return ok;
}
/*
InternalSearch2: 执行查找操作.
实现过程:
创建UDP套接字,如果IP为0,则先对255.255.255.255的1900端口发送广播消息,再对UPnP的广播IP发送消息.
参数函数同InternalSearch.
*/
bool QMyUPNP::InternalSearch2(const QString& name, const char* type, int repeat,
int delay, int version, ulong ip, PUPNPCALLBACK lpCall)
{
static const char* USRS = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n"
"MAN: \"ssdp:discover\"\r\nMX: ";
const int RCVLEN = 4096;
Clear();
if(name.IsEmpty())return false;
if(type==NULL)type = "service";
if(repeat<=0)repeat = UPNPREPEAT;
if(delay<=0)delay = UPNPDELAY;
if(version<=0)version = 1;
m_version = version;
m_name = QString(URNPREFIX) + type + ":" + name + ":" + version;
QString finding = m_devicename.IsEmpty() ? m_name : m_devicename;
QString shrs = QString(USRS) + delay + "\r\nST: " + finding + "\r\n\r\n";
QUdpSocket udp(true);
char a[50], b[50], c[50];
char rcv[RCVLEN + 1];
int rlen = 0;
int retry = 0;
const char* lc = "";
const char* le = "";
bool broad = (!is_validip(ip)); //send to 255.255.255.255
do
{
if(retry)sleep(1);
if(!is_validip(ip)) ip = UPNPADDR;
if(broad&&!udp.SendPacket(shrs.c_str(),shrs.Length(),INADDR_BROADCAST,UPNPPORT,delay/2))continue;
if(!udp.SendPacket(shrs.c_str(),shrs.Length(),ip,UPNPPORT,delay/2)
||(rlen=udp.RecvPacket(rcv,RCVLEN,delay))<=0)continue;
if(lpCall&&!lpCall(NULL))break;
if(rlen<=0||rlen>RCVLEN)continue;
rcv[rlen] = 0;
if(SplitterString(rcv,a,b,c,40,40,40)!=3||b[0]!='2')continue;
if(strstr(rcv,finding.c_str())==NULL)continue;
lc = strstr(rcv,LCA);
if(lc==NULL) lc = strstr(rcv,LCB);
if(lc==NULL) lc = strstr(rcv,LCC);
if(lc==NULL)continue;
lc += strlen(LCA);
while(lc[0]>0&&lc[0]<=0x20) lc++;
le = strchr(lc,'\n');
if(le==NULL)continue;
m_description = QString(lc,le - lc).Trim();
}while(++retry<repeat&&!Valid());
return GetDescription(delay);
}
/*
Search: QMyUPnP的公开接口,由外部对象调用进行设备或服务的查找.
执行过程: 先对默认IP调用InternalSearch进行查找,如果没找到,再判断本机是否有局域网IP,如果有,则指定最类似于192.168.0.x的IP进行查询.
参数函数同InternalSearch.
*/
bool QMyUPNP::Search(const QString& name, const char* type, int repeat, int delay,
int version, PUPNPCALLBACK lpCall)
{
ulong rip = 0;
if(InternalSearch(name,type,repeat,delay,version,0,lpCall))return true;
rip = ReloadIp();
if(is_validip(rip))return InternalSearch(name,type,repeat,delay,version,rip,lpCall);
return false;
}
/*
SetDescriptionUrl: 由外部对象调用不通过查询过程来创建一个有效的UPnP对象.
实现过程:对指定的URL执行GetDescription操作来判断是否有效的UPnP对象.
参数含义:
name: 服务/设备名称(全称:如:urn:schemas-upnp-org:service:WANPPPConnection:1)
descri: 用于获取设备/服务描述的目标URL.
*/
bool QMyUPNP::SetDescriptionUrl(const QString& name, const QString& descri)
{
Clear();
m_name = name;
m_description = descri;
return GetDescription();
}
inline bool PPIsDigitChar(const char p)
{
return (p>='0'&&p<='9');
}
static int ParseInteger(const char* cstr)
{
const unsigned char* str = (const unsigned char*) cstr;
int value = 0;
assert(str);
if(str)
{
int retry = 20;
bool neg = (str[0]=='-');
if(neg)str++;
while(PPIsDigitChar(str[0])&&(--retry)>0)
{
value*=10;
value+=(str[0]-'0');
str++;
}
if(neg)value = -value;
}
return value;
}
static QString NGetAddressFromUrl(const QString& str, QString& post, QString& host, int& port)
{
port = 0;
post = "";
if(str.IsEmpty())return QString();
const char* src = str.c_str();
const char* start,
* pstart,
* end;
start = strstr(src,"://");
start = (start==NULL)?src:(start+3);
end = strchr(start,'/');
if(end==NULL)end = src + strlen(src);
host = QString(start,end-start);
post = (end[0]=='/')?QString(end):QString("/");
pstart = strchr(start,':');
port = (pstart==NULL||pstart>=end)?80:ParseInteger(pstart+1);
if(port==0)port = 80;
if(pstart&&pstart<end)end = pstart;
return QString(start,end-start);
}
/*
GetDescription: 在目标对象的描述URL已经获取的情况下获取该对象的描述.
实现过程: 对指定的URL实行一个UPnP规范指定的GET操作,解析返回的模板.只有成功解析了,对象才有效.
参数:
delay: 最大等待时间.
*/
bool QMyUPNP::GetDescription(int delay)
{
if(!Valid())return false;
InternalClear();
QString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_description, post, host, port);
if(addr.IsEmpty()||!is_validport(port))return false;
QString get = QString("GET ") + post + " HTTP/1.1\r\nHOST: " + host + "\r\nACCEPT-LANGUAGE: en\r\n\r\n";
QProxy proxy; //因为UPnP设备基本上是局域网内部的操作,不需要代理,为了避免全局的代理设置影响后续TCP操作,这里定义一个空的代理.
QSocket tcp;
int retry = 0;
if(delay<=0)delay = UPNPDELAY;
if(!tcp.Connect(addr.c_str(),port,false,&proxy))return false;//connection failed
tcp.SendData(get.c_str(),get.Length());
do
{
if(tcp.Write()>=0&&tcp.SendLength()>0) sleep(1); //sleep(x) = Sleep(x * 1000)
}while(++retry<delay&&tcp.SendLength()>0);
if(tcp.SendLength()>0||tcp.Read()<0)return false;
retry = 0;
do
{
if(retry)sleep(1);
}while(tcp.Read()>=0&&++retry<delay&&tcp.Read()>=0);
char a[50], b[50], c[50];
if(SplitterString(tcp.RecvBuffer(),a,b,c,40,40,40)!=3||b[0]!='2')return false;
const char* lfe = NULL;
const char* buf = tcp.RecvBuffer(); //如果当前没有数据,返回"",RecvBuffer永远不会返回NULL,所以不用担心空指针
const char* lfr = NULL;
//get friendly name
lfr = strstr(buf,"<friendlyName>");
if(lfr)
{
lfr += (sizeof("<friendlyName>") - 1);
lfe = strstr(lfr,"</friendlyName>");
if(lfe)m_friendlyname = QString(lfr, lfe - lfr).Trim();
}
//get <modelName>
lfr = strstr(buf,"<modelName>");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -