📄 upnp.cpp
字号:
if(lfr)
{
lfr += (sizeof("<modelName>") - 1);
lfe = strstr(lfr, "</modelName>");
if(lfe)m_modelname = QString(lfr, lfe - lfr).Trim();
}
//get <URLBase>
lfr = strstr(buf,"<URLBase>");
if(lfr)
{
lfr += (sizeof("<URLBase>") - 1);
lfe = strstr(lfr,"</URLBase>");
if(lfe)m_baseurl = QString(lfr, lfe - lfr).Trim();
}
if(m_baseurl.IsEmpty())m_baseurl = QString("http://") + host + "/";
if(m_baseurl[m_baseurl.Length()]!='/')m_baseurl += "/";
//get service
QString svrn = QString("<serviceType>") + m_name + "</serviceType>";
lfr = strstr(buf,svrn.c_str());
if(lfr)
{
lfe = strstr(lfr, "</service>");
if(lfe)
{
QString tmp = QString(lfr, lfe - lfr);
lfr = strstr(tmp.c_str(),"<controlURL>");
if(lfr)
{
lfr += (sizeof("<controlURL>") - 1);
lfe = strstr(lfr,"</controlURL>");
if(lfe&&lfe>lfr)
{
if(lfr[0]=='/')
{//relative url
lfr++;
m_controlurl = m_baseurl + QString(lfr, lfe - lfr);
}
else
{
m_controlurl = QString(lfr, lfe - lfr);
}
}
}
}
}
return Comfirmed();
}
/*
GetProperty: 在对象已经有效的情况下获取对象的某属性.
实现过程: 通过对目标对象的控制URL发送UPnP规范定义的POST请求并解析返回数据来获取相应的属性.
参数:
name: 针对该属性的动作,如:GetExternalIPAddress
rsp: 属性返回值的名称,如:NewExternalIPAddress
delay: 最大等待时间
*/
QString QMyUPNP::GetProperty(const QString& name, const QString& rsp, int delay)
{
if(!Comfirmed())return QString();
QString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_controlurl, post, host, port);
if(addr.IsEmpty()||!is_validport(port))return QString();
QMemoryStream cnt; //QMemoryStream 内部有智能的内存算法可以避免频繁的内存分配.
QMemoryStream psr;
cnt.Write("<s:Envelope\r\n xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\r\n ");
cnt.Write("s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n <s:Body>\r\n <u:");
cnt.Write(name.c_str());
cnt.Write(" xmlns:u=\"");
cnt.Write(m_name.c_str());
cnt.Write("\">\r\n </u:");
cnt.Write(name.c_str());
cnt.Write(">\r\n </s:Body>\r\n</s:Envelope>\r\n\r\n");
psr.Write("POST ");
psr.Write(post.c_str());
psr.Write(" HTTP/1.1\r\nHOST: ");
psr.Write(host.c_str());
psr.Write("\r\nContent-Length: ");
psr.Write(cnt.Size());
psr.Write("\r\nContent-Type: text/xml; charset=\"utf-8\"\r\nSOAPACTION: ");
psr.Write(m_name.c_str());
psr.Write("#");
psr.Write(name.c_str());
psr.Write("\r\n\r\n");
psr.Write(cnt.Data(),cnt.Size());
QProxy proxy;
QSocket tcp;
int retry = 0;
if(delay<=0)delay = UPNPDELAY;
if(!tcp.Connect(addr.c_str(),port,false,&proxy))
{
return QString();//no proxy
}
tcp.SendData(psr.Data(),psr.Size());
do
{
if(tcp.Write()>=0&&tcp.SendLength()>0) sleep(1);
}while(++retry<delay&&tcp.SendLength()>0);
if(tcp.SendLength()>0||tcp.Read()<0)
{
return QString();
}
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 QString();
}
QString schr = QString("<") + rsp;
QString echr = QString("</") + rsp + ">";
const char* lfe = NULL;
const char* buf = tcp.RecvBuffer();
const char* lfr = strstr(buf,schr.c_str());
if(lfr)
{
lfe = strstr(lfr, echr.c_str());
if(lfe)
{
QString ret = QString(lfr, lfe - lfr);
lfr = strchr(ret.c_str(),'>');
if(lfr)
{
lfr++;
return QString(lfr);
}
}
}
return QString();
}
/*
GetArgString: 将BS使用的一个QStringList的参数格式转换成符合UPnP规范的字串型参数.
*/
QString GetArgString(QStringList& args)
{
QMemoryStream ms;
for(int i=0;i<args.Count()-1;i+=2)
{
ms.Write(" <",5);
ms.Write(args.Item(i).c_str());
ms.Write(">",1);
ms.Write(args.Item(i+1).c_str());
ms.Write("</",2);
ms.Write(args.Item(i).c_str());
ms.Write(">\r\n",3);
}
return QString(ms.Data(),ms.Size());
}
/*
InvokeCommand: 在对象有效的情况下调用对象的方法.
实现过程:通过向目标对象的控制URL发送UPnP规范定义的POST方法来调用对象的方法.
参数含义:
name: 调用方法的名称.如:AddPortMapping
args: QStringList类型,其组成结构为:偶数索引的值为参数名,奇数索引的值为参数值,从0开始索引.
delay: 最大等待时间.
*/
bool QMyUPNP::InvokeCommand(const QString& name, QStringList& args, int delay)
{
if(!Comfirmed())return false;
QString post, host, addr;
int port = 0;
addr = NGetAddressFromUrl(m_controlurl, post, host, port);
if(addr.IsEmpty()||!is_validport(port))return false;
QMemoryStream cnt;
QMemoryStream psr;
cnt.Write("<s:Envelope\r\n xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\r\n ");
cnt.Write("s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n <s:Body>\r\n <u:");
cnt.Write(name.c_str());
cnt.Write(" xmlns:u=\"");
cnt.Write(m_name.c_str());
cnt.Write("\">\r\n");
cnt.Write(GetArgString(args).c_str());
cnt.Write(" </u:");
cnt.Write(name.c_str());
cnt.Write(">\r\n </s:Body>\r\n</s:Envelope>\r\n\r\n");
psr.Write("POST ");
psr.Write(post.c_str());
psr.Write(" HTTP/1.1\r\nHOST: ");
psr.Write(host.c_str());
psr.Write("\r\nContent-Length: ");
psr.Write(cnt.Size());
psr.Write("\r\nContent-Type: text/xml; charset=\"utf-8\"\r\nSOAPACTION: ");
psr.Write(m_name.c_str());
psr.Write("#");
psr.Write(name.c_str());
psr.Write("\r\n\r\n");
psr.Write(cnt.Data(),cnt.Size());
QProxy proxy;
QSocket tcp;
int retry = 0;
if(delay<=0)delay = UPNPDELAY;
if(!tcp.Connect(addr.c_str(),port,false,&proxy))return false;//no proxy
tcp.SendData(psr.Data(),psr.Size());
do
{
if(tcp.Write()>=0&&tcp.SendLength()>0) sleep(1);
}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 true;
return false;
}
/*
GetAddPortmapArgs: 生成相应可用于InvokeCommand的针对AddPortmapping的参数.
*/
void GetAddPortmapArgs(QStringList& args, int eport, int iport,
const char* iclient, const char* descri,
bool enabled, int dur, const char* type, const char* remote)
{
args.Clear();
args.Add("NewRemoteHost");
args.Add(remote);
args.Add("NewExternalPort");
args.Add(QString(eport));
args.Add("NewProtocol");
args.Add(type);
args.Add("NewInternalPort");
args.Add(QString(iport));
args.Add("NewInternalClient");
args.Add(iclient);
args.Add("NewEnabled");
args.Add(enabled?"1":"0");
args.Add("NewPortMappingDescription");
args.Add(descri);
args.Add("NewLeaseDuration");
args.Add(QString(dur));
}
void GetDeletePortmapArgs(QStringList& args, int eport, const char* type, const char* remote)
{
args.Clear();
args.Add("NewRemoteHost");
args.Add(remote);
args.Add("NewExternalPort");
args.Add(QString(eport));
args.Add("NewProtocol");
args.Add(type);
}
/*
附注:
1, 以上用到的QString,QUdpSocket,QStringList,QMemoryStream,QSocket,QProxy都是BS内部实现的类,功能与接口与一些流行的类库如MFC,VCL类似.
2, 通过QMyUPNP类来查找支持自动端口映射的设备的示例代码:
QMyUPNP Tmp;
//查找服务的示例
if(!Tmp.Comfirmed()) Tmp.Search(UPNPPORTMAP1);
if(!Tmp.Comfirmed()) Tmp.Search(UPNPPORTMAP0);
if(Tmp.Comfirmed()) MessageBox(NULL,(QString("Found: ") + Tmp.GetModelName()).c_str(),"",MB_OK);
//添加端口的示例
QStringList args;
GetAddPortmapArgs(args,FWANPort,FLocalPort,FLocalIP,FDescription,
true,0,FType,FWANIP);//FWANIP为""表示默认公网IP
Tmp.InvokeCommand(UPNPADDPORTMAP,args,6);
//删除端口的示例
QStringList args;
GetDeletePortmapArgs(args,FWANPort,FType,FWANIP);
Tmp.InvokeCommand(UPNPDELPORTMAP,args,6);
//获取公网IP的示例
QString ip = Tmp.GetProperty(UPNPGETEXTERNALIP,6);
MessageBox(NULL,(QString("Your WAN IP: ") + ip).c_str(),"",MB_OK);
3, 声明:以上代码都是BS的作者自行实现的,你可以参考,但你最好不要将代码直接拷贝到你的程序中,因为我们保留对这些代码的所有权利.
如果有任何问题,欢迎访问我们的网站或Mail到: bitspirit@lanspirit.net
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -