Upnp端口映射 C++类 实现

半玩半弄地研究了好几天,终于基本弄清楚了Upnp端口映射的原理,写了一个方便的C++类,虽然代码杂乱。。。
本模块使用了 tinyxml2 库

参考了以下文章
http://www.cnblogs.com/hnrainll/archive/2012/07/27/2612152.html
http://blog.csdn.net/braddoris/article/details/41646789

编写环境 VS2015

c++代码 + 测试,内附tinyxml2库,可以直接编译
360云盘炸了…
已更新…
下载地址 http://cloud.189.cn/t/vIJRfymAVnI3 UpnpPortMapping.7z

使用流程
首先要初始化类
依次调用
Init() 初始化WSA支持库,重复初始化也是没有问题
SearchUpnpRootDevice() 搜索本局域网内的根设备,路由器也是根设备
IsSupportPortMapping() 检查根设备是否支持端口映射功能

如果初始化成功的话,然后就可以随意调用 控制Upnp端口映射的方法了

//增加一个映射
    /*
        参数说明:
        externalPort    路由器的外部端口,路由器暴露在外网的端口号
        protocol        协议,只能选 TCP 或 UDP
        internalPort    本机端口,路由器暴露在外网的端口映射到本机哪个端口
        internalClient  本机IP地址,示例 192.168.0.100

        timeout_seconds 多少时间后此映射自动失效,找不到足够的资料,忽略,可能的话以后补上
    */
    bool AddPortMapping(unsigned short externalPort, Protocol protocol, unsigned short internalPort, std::string internalClient, std::string portMappingDescription = "Mapping By UpnpPortMapping" /*, unsigned int timeout_seconds*/);

    //删除一个映射
    /*
        externalPort    路由器的外部端口,路由器暴露在外网的端口号
        protocol        协议,只能选 TCP 或 UDP
    */
    bool DeletePortMapping(unsigned short externalPort, Protocol protocol);

//查询映射信息
    bool GetGenericPortMappingEntry(std::vector &_result);

/******************************分界线*****************************************/

main.cpp

#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#include 
#include 
#include 
#include 
#include "UpnpPortMapping.h"
#include 

#pragma comment(lib,"ws2_32.lib")
#pragma warning(disable:4996)

using namespace std;

void main()
{
    UpnpPortMapping upm;
    vector result;

    //初始化
    bool b = upm.Init();
    assert(b);
    b = upm.SearchUpnpRootDevice();
    assert(b);
    b = upm.IsSupportPortMapping();
    assert(b);

    //调用命令
    cout << "增加端口映射前" << endl;
    b = upm.GetGenericPortMappingEntry(result);
    assert(b);
    for (auto i : result)
    {
        cout << "remoteHost\t\t" << i.remoteHost << endl;
        cout << "externalPort\t\t" << i.externalPort << endl;
        cout << "protocol\t\t" << i.protocol << endl;
        cout << "internalPort\t\t" << i.internalPort << endl;
        cout << "internalClient\t\t" << i.internalClient << endl;
        cout << "enabled\t\t\t" << i.enabled << endl;
        cout << "portMappingDescription\t" << i.portMappingDescription << endl;
        cout << "leaseDuration\t\t" << i.leaseDuration << endl << endl;
    }
    getchar();

    b = upm.AddPortMapping(25550, UpnpPortMapping::TCP, 25550, "192.168.0.105");
    assert(b);
    b = upm.AddPortMapping(25550, UpnpPortMapping::UDP, 25550, "192.168.0.105");
    assert(b);

    cout << "增加端口映射后" << endl;
    b = upm.GetGenericPortMappingEntry(result);
    assert(b);
    for (auto i : result)
    {
        cout << "remoteHost\t\t" << i.remoteHost << endl;
        cout << "externalPort\t\t" << i.externalPort << endl;
        cout << "protocol\t\t" << i.protocol << endl;
        cout << "internalPort\t\t" << i.internalPort << endl;
        cout << "internalClient\t\t" << i.internalClient << endl;
        cout << "enabled\t\t\t" << i.enabled << endl;
        cout << "portMappingDescription\t" << i.portMappingDescription << endl;
        cout << "leaseDuration\t\t" << i.leaseDuration << endl << endl;
    }
    getchar();

    b = upm.DeletePortMapping(25550, UpnpPortMapping::TCP);
    assert(b);
    b = upm.DeletePortMapping(25550, UpnpPortMapping::UDP);
    assert(b);
    b = upm.GetGenericPortMappingEntry(result);
    assert(b);

    cout << "删除端口映射后" << endl;
    for (auto i : result)
    {
        cout << "remoteHost\t\t" << i.remoteHost << endl;
        cout << "externalPort\t\t" << i.externalPort << endl;
        cout << "protocol\t\t" << i.protocol << endl;
        cout << "internalPort\t\t" << i.internalPort << endl;
        cout << "internalClient\t\t" << i.internalClient << endl;
        cout << "enabled\t\t\t" << i.enabled << endl;
        cout << "portMappingDescription\t" << i.portMappingDescription << endl;
        cout << "leaseDuration\t\t" << i.leaseDuration << endl << endl;
    }
    getchar();
}

UpnpPortMapping.h

#pragma once
#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#include 
#include 

class UpnpPortMapping
{
public:
    enum Protocol
    {
        TCP,
        UDP
    };
    struct PortMappingInformation
    {
        std::string remoteHost;
        std::string externalPort;
        std::string protocol;
        std::string internalPort;
        std::string internalClient;
        std::string enabled;
        std::string portMappingDescription;
        std::string leaseDuration;
    };

    //初始化
    bool Init();
    //搜索本网络中的Upnp设备,默认30秒超时
    bool SearchUpnpRootDevice(unsigned int timeout_seconds = 30);
    //检查目标Upnp设备是否支持端口映射功能
    bool IsSupportPortMapping();
    //得到最后执行的命令的结果
    std::string GetErrorString();


    //增加一个映射
    /*
        参数说明:
        externalPort    路由器的外部端口,路由器暴露在外网的端口号
        protocol        协议,只能选 TCP 或 UDP
        internalPort    本机端口,路由器暴露在外网的端口映射到本机哪个端口
        internalClient  本机IP地址,示例 192.168.0.100

        timeout_seconds 多少时间后此映射自动失效,找不到足够的资料,忽略,可能的话以后补上
    */
    bool AddPortMapping(unsigned short externalPort, Protocol protocol, unsigned short internalPort, std::string internalClient, std::string portMappingDescription = "Mapping By UpnpPortMapping" /*, unsigned int timeout_seconds*/);

    //删除一个映射
    /*
        externalPort    路由器的外部端口,路由器暴露在外网的端口号
        protocol        协议,只能选 TCP 或 UDP
    */
    bool DeletePortMapping(unsigned short externalPort, Protocol protocol);

    //查询映射信息
    bool GetGenericPortMappingEntry(std::vector &_result);


private:
    std::string result;
    std::string igdPath;
    std::string routerIp;
    std::string routerPort;
    std::string controlURL;
    std::string serviceType;
};

UpnpPortMapping.cpp

#include "UpnpPortMapping.h"
#include <WinSock2.h>
#include <windows.h>
#include "tinyxml2\tinyxml2.h"

#pragma comment(lib,"ws2_32.lib")

using namespace std;

bool UpnpPortMapping::Init()
{
    int err;
    WORD versionRequired;
    WSADATA wsaData;
    versionRequired=MAKEWORD(2, 0);
    err = WSAStartup(versionRequired, &wsaData);//协议库的版本信息
    if (err)
    {
        result = "WSA 初始化失败";
        return false;
    }
    result = "NoError";
    return true;
}
bool UpnpPortMapping::SearchUpnpRootDevice(unsigned int timeout_seconds)
{
    SOCKET su;
    su = socket(AF_INET, SOCK_DGRAM, 0);
    if (su == INVALID_SOCKET)
    {
        result = "创建UDP socket 失败";
        return false;
    }
    int nNetTimeout = 2000;         //设置超时,两秒
    setsockopt(su, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(int));

    int err = 0;
    string data;
    for (size_t i = 0; i < timeout_seconds / 2 + 1; i++)
    {
        string findRootDevice = string() +
                                "M-SEARCH * HTTP/1.1\r\n" +
                                "HOST: 239.255.255.250:1900\r\n" +
                                "MAN:\"ssdp:discover\"\r\n" +
                                "MX:3\r\n" +
                                "ST:UPnP:rootdevice\r\n\r\n";

        sockaddr_in server ={0};
        server.sin_family = AF_INET;
        server.sin_port = htons(1900);
        server.sin_addr.s_addr = inet_addr("239.255.255.250");
        err = sendto(su, findRootDevice.data(), findRootDevice.size(), 0, (sockaddr*)&server, sizeof(server));
        if (err == -1)
        {
            result = "发送搜索根设备请求失败";
            return false;
        }
        data.resize(1024);
        int len = sizeof(server);
        err = recvfrom(su, (char *)data.data(), data.size(), 0, (sockaddr*)&server, &len);
        if (err == -1)
            continue;
        break;
    }
    closesocket(su);
    if (err == -1)
    {
        result = "没有发现根设备";
        return false;
    }
    data.resize(err);
    if (data.find("200 OK", 0) == string::npos)
    {
        result = "接收到坏消息,可能是网络不稳定或根设备繁忙";
        return false;
    }
    string loc = "LOCATION: ";
    auto pos = data.find(loc, 0);
    igdPath = data.substr(pos + loc.size(), data.find("\r\n", pos) - pos - loc.size());
    routerIp = string(igdPath.substr(igdPath.find("//") + 2, igdPath.rfind("/") - igdPath.find("//") - 2));
    routerPort = routerIp.substr(routerIp.find(':') + 1);
    routerIp = routerIp.substr(0, routerIp.find(':'));

    result = "NoError";
    return true;
}
bool UpnpPortMapping::IsSupportPortMapping()
{
    string getXml = string() +
                    "GET /igd.xml HTTP/1.1\r\n" +
                    "Host: host:port\r\n\r\n";
    getXml.replace(getXml.find("/igd.xml"), string("/igd.xml").size(), igdPath.substr(igdPath.rfind('/')));
    getXml.replace(getXml.find("host:port"), string("host:port").size(), routerIp + ':' + routerPort);

    SOCKET st = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server ={0};
    server.sin_family = AF_INET;
    server.sin_port = htons(1900);
    server.sin_addr.s_addr = inet_addr(routerIp.c_str());
    int err = connect(st, (sockaddr *)&server, sizeof(server));
    if (err != 0)
    {
        result = "无法连接Upnp根设备的1900端口";
        return false;
    }

    err = send(st, getXml.c_str(), getXml.size(), 0);
    if (err == -1)
    {
        result = "发送Upnp描述请求信息失败";
        closesocket(st);
        return false;
    }

    string data;
    data.resize(8192);
    err = recv(st, (char *)data.data(), data.size(), MSG_WAITALL);          //第四个参数设置为等待所有的数据后才返回
    if (err == -1)
    {
        result = "接收Upnp描述信息失败";
        closesocket(st);
        return false;
    }
    data.resize(err);
    closesocket(st);

    if (data.find("200 OK", 0) == string::npos)
    {
        result = "接收Upnp描述信息失败";
        return false;
    }

    data.erase(0, data.find("));      //删除http头
    tinyxml2::XMLDocument igd;
    tinyxml2::XMLPrinter printx;
    err = igd.Parse(data.c_str(), data.size());
    if (err != tinyxml2::XML_SUCCESS)
    {
        result = "转换为内存xml失败";
        return false;
    }
    string controlURI, serviceType, URLBase;
    tinyxml2::XMLElement *bele = nullptr, *ele =  igd.FirstChildElement("root");
    if (bele = ele->FirstChildElement("URLBase"))
        URLBase = bele->GetText() ? bele->GetText() : "";

    if (ele = ele->FirstChildElement("device"))
    {
        if ((bele = ele->FirstChildElement("deviceType")) &&
            (string(bele->GetText()) == "urn:schemas-upnp-org:device:InternetGatewayDevice:1") &&
            (ele = ele->FirstChildElement("deviceList")) &&
            (ele = ele->FirstChildElement("device")))
        {
            do
            {
                if ((bele = ele->FirstChildElement("deviceType")) &&
                    (string(bele->GetText()) == "urn:schemas-upnp-org:device:WANDevice:1") &&
                    (ele = ele->FirstChildElement("deviceList")) &&
                    (ele = ele->FirstChildElement("device")))
                {
                    do
                    {
                        if ((bele = ele->FirstChildElement("deviceType")) &&
                            (string(bele->GetText()) == "urn:schemas-upnp-org:device:WANConnectionDevice:1") &&
                            (ele = ele->FirstChildElement("serviceList")) &&
                            (ele = ele->FirstChildElement("service")))
                        {
                            do
                            {
                                if ((bele = ele->FirstChildElement("serviceType")) &&
                                    ((string(serviceType = bele->GetText()) == "urn:schemas-upnp-org:service:WANIPConnection:1") || (string(serviceType = bele->GetText()) == "urn:schemas-upnp-org:service:WANPPPConnection:1")))
                                {
                                    controlURI = (bele = ele->FirstChildElement("controlURL")) ? bele->GetText() : "";
                                    goto _end;
                                }
                            } while (ele = ele->NextSiblingElement("service"));
                        }
                    } while (ele = ele->NextSiblingElement("device"));
                }
            } while (ele = ele->NextSiblingElement("device"));
        }
    }
_end:
    if (controlURI.empty())
    {
        result = "目标路由不支持UPNP端口映射服务";
        return false;
    }
    this->controlURL = URLBase + controlURI;
    this->serviceType = serviceType;
    result = "NoError";
    return true;
}


bool UpnpPortMapping::AddPortMapping(unsigned short externalPort, Protocol protocol, unsigned short internalPort, string internalClient, string portMappingDescription /*, unsigned int timeout_seconds*/)
{
    string Protocal, actionName = "AddPortMapping";

    switch (protocol)
    {
        case UpnpPortMapping::TCP:
            Protocal = "TCP";
            break;
        case UpnpPortMapping::UDP:
            Protocal = "UDP";
            break;
        default:
            result = "该协议不存在";
            return false;
            break;
    }
    tinyxml2::XMLDocument xml;

    //构建xml文档
    xml.LinkEndChild(xml.NewDeclaration());

    auto *Envelope = xml.NewElement("s:Envelope");
    xml.LinkEndChild(Envelope);
    Envelope->SetAttribute("xmlns:s", "http://schemas.xmlsoap.org/soap/envelope/");
    Envelope->SetAttribute("s:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");

    auto *Body = xml.NewElement("s:Body");
    Envelope->LinkEndChild(Body);

    auto *Action = xml.NewElement((string("u:") + actionName).c_str());
    Body->LinkEndChild(Action);
    Action->SetAttribute("xmlns:u", serviceType.c_str());

    auto *ExternalPort1             = xml.NewElement("NewExternalPort");
    auto *InternalPort1             = xml.NewElement("NewInternalPort");
    auto *Protocal1                 = xml.NewElement("NewProtocol");
    auto *InternalClient1           = xml.NewElement("NewInternalClient");
    auto *LeaseDuration1            = xml.NewElement("NewLeaseDuration");
    auto *PortMappingDescription1   = xml.NewElement("NewPortMappingDescription");
    ExternalPort1->SetText(to_string(externalPort).c_str());
    InternalPort1->SetText(to_string(internalPort).c_str());
    Protocal1->SetText(Protocal.c_str());
    InternalClient1->SetText(internalClient.c_str());
    LeaseDuration1->SetText(""/*LeaseDuration.c_str()*/);
    PortMappingDescription1->SetText(portMappingDescription.c_str());
    Action->LinkEndChild(ExternalPort1);
    Action->LinkEndChild(InternalPort1);
    Action->LinkEndChild(Protocal1);
    Action->LinkEndChild(InternalClient1);
    Action->LinkEndChild(LeaseDuration1);
    Action->LinkEndChild(PortMappingDescription1);

    auto *NewRemoteHost             = xml.NewElement("NewRemoteHost");
    auto *NewEnabled                = xml.NewElement("NewEnabled");
    NewRemoteHost->SetText("");
    NewEnabled->SetText(true);
    Action->LinkEndChild(NewRemoteHost);
    Action->LinkEndChild(NewEnabled);

    tinyxml2::XMLPrinter pr;
    xml.Print(&pr);
    string xml1 = pr.CStr();

    string actionDo =   string() +
                        "POST /control/url HTTP/1.1\r\n" +
                        "HOST: host:port\r\n" +
                        "CONTENT-TYPE: text/xml;charset=\"utf-8\"\r\n" +
                        "CONTENT-LENGTH: length\r\n" +
                        "SOAPACTION:\"serviceType#actionName\"\r\n\r\n";

    actionDo.replace(actionDo.find("host:port"), string("host:port").size(), routerIp);
    actionDo.replace(actionDo.find("/control/url"), string("/control/url").size(), controlURL);
    actionDo.replace(actionDo.find("length"), string("length").size(), to_string(xml1.size()).c_str());
    actionDo.replace(actionDo.find("serviceType"), string("serviceType").size(), serviceType.c_str());
    actionDo.replace(actionDo.find("actionName"), string("actionName").size(), actionName.c_str());

    string postMsg = actionDo + xml1;

    SOCKET st = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server ={0};
    server.sin_family = AF_INET;
    server.sin_port = htons((unsigned short)stoul(routerPort));
    server.sin_addr.s_addr = inet_addr(routerIp.c_str());
    int err = connect(st, (sockaddr *)&server, sizeof(server));
    if (err != 0)
    {
        result = "打开路由Upnp端口失败";
        closesocket(st);
        return false;
    }

    err = send(st, postMsg.c_str(), postMsg.size(), 0);

    postMsg.resize(8192);
    err = recv(st, (char *)postMsg.data(), postMsg.size(), MSG_WAITALL);            //第四个参数设置为等待所有的数据
    postMsg.resize(err);
    closesocket(st);

    if (postMsg.find("200 OK", 0) == string::npos)
    {
        result = "创建端口映射失败";
        return false;
    }
    result = "NoError";
    return true;

}
bool UpnpPortMapping::DeletePortMapping(unsigned short externalPort, Protocol protocol)
{
    string Protocal, actionName = "DeletePortMapping";

    switch (protocol)
    {
        case UpnpPortMapping::TCP:
            Protocal = "TCP";
            break;
        case UpnpPortMapping::UDP:
            Protocal = "UDP";
            break;
        default:
            result = "该协议不存在";
            return false;
            break;
    }

    tinyxml2::XMLDocument xml;

    //构建xml文档
    xml.LinkEndChild(xml.NewDeclaration());

    auto *Envelope = xml.NewElement("s:Envelope");
    xml.LinkEndChild(Envelope);
    Envelope->SetAttribute("xmlns:s", "http://schemas.xmlsoap.org/soap/envelope/");
    Envelope->SetAttribute("s:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");

    auto *Body = xml.NewElement("s:Body");
    Envelope->LinkEndChild(Body);

    auto *Action = xml.NewElement((string("u:") + actionName).c_str());
    Body->LinkEndChild(Action);
    Action->SetAttribute("xmlns:u", serviceType.c_str());

    auto *ExternalPort1             = xml.NewElement("NewExternalPort");
    auto *Protocal1                 = xml.NewElement("NewProtocol");
    ExternalPort1->SetText(to_string(externalPort).c_str());
    Protocal1->SetText(Protocal.c_str());
    Action->LinkEndChild(ExternalPort1);
    Action->LinkEndChild(Protocal1);

    auto *NewRemoteHost             = xml.NewElement("NewRemoteHost");
    NewRemoteHost->SetText("");
    Action->LinkEndChild(NewRemoteHost);

    tinyxml2::XMLPrinter pr;
    xml.Print(&pr);
    string xml1 = pr.CStr();

    string actionDo =   string() +
        "POST /control/url HTTP/1.1\r\n" +
        "HOST: host:port\r\n" +
        "CONTENT-TYPE: text/xml;charset=\"utf-8\"\r\n" +
        "CONTENT-LENGTH: length\r\n" +
        "SOAPACTION:\"serviceType#actionName\"\r\n\r\n";

    actionDo.replace(actionDo.find("host:port"), string("host:port").size(), routerIp);
    actionDo.replace(actionDo.find("/control/url"), string("/control/url").size(), controlURL);
    actionDo.replace(actionDo.find("length"), string("length").size(), to_string(xml1.size()).c_str());
    actionDo.replace(actionDo.find("serviceType"), string("serviceType").size(), serviceType.c_str());
    actionDo.replace(actionDo.find("actionName"), string("actionName").size(), actionName.c_str());

    string postMsg = actionDo + xml1;

    SOCKET st = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server ={0};
    server.sin_family = AF_INET;
    server.sin_port = htons((unsigned short)stoul(routerPort));
    server.sin_addr.s_addr = inet_addr(routerIp.c_str());
    int err = connect(st, (sockaddr *)&server, sizeof(server));
    if (err != 0)
    {
        result = "打开路由Upnp端口失败";
        closesocket(st);
        return false;
    }

    err = send(st, postMsg.c_str(), postMsg.size(), 0);

    postMsg.resize(8192);
    err = recv(st, (char *)postMsg.data(), postMsg.size(), MSG_WAITALL);            //第四个参数设置为等待所有的数据
    postMsg.resize(err);
    closesocket(st);

    if (postMsg.find("200 OK", 0) == string::npos)
    {
        result = "删除端口映射失败";
        return false;
    }
    result = "NoError";
    return true;
}
bool UpnpPortMapping::GetGenericPortMappingEntry(vector<PortMappingInformation> &_result)
{
    vector<PortMappingInformation> pl;
    for (int i = 0;; ++i)
    {
        string actionName = "GetGenericPortMappingEntry";

        tinyxml2::XMLDocument xml;

        //构建xml文档
        xml.LinkEndChild(xml.NewDeclaration());

        auto *Envelope = xml.NewElement("s:Envelope");
        xml.LinkEndChild(Envelope);
        Envelope->SetAttribute("xmlns:s", "http://schemas.xmlsoap.org/soap/envelope/");
        Envelope->SetAttribute("s:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");

        auto *Body = xml.NewElement("s:Body");
        Envelope->LinkEndChild(Body);

        auto *Action = xml.NewElement((string("u:") + actionName).c_str());
        Body->LinkEndChild(Action);
        Action->SetAttribute("xmlns:u", serviceType.c_str());

        auto *PortMappingIndex              = xml.NewElement("NewPortMappingIndex");
        PortMappingIndex->SetText(to_string(i).c_str());
        Action->LinkEndChild(PortMappingIndex);

        tinyxml2::XMLPrinter pr;
        xml.Print(&pr);
        string xml1 = pr.CStr();

        string actionDo =   string() +
            "POST /control/url HTTP/1.1\r\n" +
            "HOST: host:port\r\n" +
            "CONTENT-TYPE: text/xml;charset=\"utf-8\"\r\n" +
            "CONTENT-LENGTH: length\r\n" +
            "SOAPACTION:\"serviceType#actionName\"\r\n\r\n";

        actionDo.replace(actionDo.find("host:port"), string("host:port").size(), routerIp);
        actionDo.replace(actionDo.find("/control/url"), string("/control/url").size(), controlURL);
        actionDo.replace(actionDo.find("length"), string("length").size(), to_string(xml1.size()).c_str());
        actionDo.replace(actionDo.find("serviceType"), string("serviceType").size(), serviceType.c_str());
        actionDo.replace(actionDo.find("actionName"), string("actionName").size(), actionName.c_str());

        string postMsg = actionDo + xml1;

        SOCKET st = socket(AF_INET, SOCK_STREAM, 0);
        sockaddr_in server ={0};
        server.sin_family = AF_INET;
        server.sin_port = htons((unsigned short)stoul(routerPort));
        server.sin_addr.s_addr = inet_addr(routerIp.c_str());
        int err = connect(st, (sockaddr *)&server, sizeof(server));
        if (err != 0)
        {
            result = "打开路由Upnp端口失败";
            closesocket(st);
            return false;
        }

        err = send(st, postMsg.c_str(), postMsg.size(), 0);
        if (err == -1)
        {
            result = "发送Upnp端口映射查询信息失败";
            closesocket(st);
            return false;
        }

        postMsg.resize(8192);
        err = recv(st, (char *)postMsg.data(), postMsg.size(), MSG_WAITALL);            //第四个参数设置为等待所有的数据
        if (err == -1)
        {
            result = "接收Upnp端口映射查询信息失败";
            closesocket(st);
            return false;
        }
        postMsg.resize(err);
        closesocket(st);

        if (postMsg.find("200 OK") != string::npos)
        {
            postMsg.erase(0, postMsg.find("));
            xml.Clear();
            auto err = xml.Parse(postMsg.c_str());
            if (err != tinyxml2::XML_SUCCESS)
            {
                result = "转换为内存xml失败";
                return false;
            }
            tinyxml2::XMLElement *ele = xml.FirstChildElement("SOAP-ENV:Envelope");
            if (ele != nullptr)
            {
                ele = ele->FirstChildElement("SOAP-ENV:Body");
                if (ele != nullptr)
                {
                    ele = ele->FirstChildElement("u:GetGenericPortMappingEntryResponse");
                    if (ele != nullptr)
                    {
                        //不同路由情况不同,也许路由不一定把所有的信息都发过来,不一定每个过来的条目都能转换为文本,值有可能为空,此时GetText返回的是0而不是""
                        auto GetEle = [](char *name, tinyxml2::XMLElement *ele) -> string
                        {
                            return ele->FirstChildElement(name) ? (ele->FirstChildElement(name)->GetText() ? ele->FirstChildElement(name)->GetText() : "") : "";
                        };
                        PortMappingInformation pmi;

                        pmi.remoteHost      = GetEle("NewRemoteHost", ele);
                        pmi.externalPort    = GetEle("NewExternalPort", ele);
                        pmi.protocol        = GetEle("NewProtocol", ele);
                        pmi.internalPort    = GetEle("NewInternalPort", ele);
                        pmi.internalClient  = GetEle("NewInternalClient", ele);
                        pmi.enabled         = GetEle("NewEnabled", ele);
                        pmi.portMappingDescription = GetEle("NewPortMappingDescription", ele);
                        pmi.leaseDuration   = GetEle("NewLeaseDuration", ele);

                        pl.push_back(pmi);
                    }
                }
            }
        }
        else
            break;
    }
    result = "NoError";
    _result = pl;
    return true;
}

你可能感兴趣的:(windows)