第十三章 W55MH32 UPnP端口转发示例

目录

1 UPnP协议简介

2 UPnP协议特点

3 UPnP应用场景

4 UPnP设置端口转发的工作流程

5 报文讲解

6 实现过程

7 运行结果

8 总结


1 UPnP协议简介

UPnP(Universal Plug and Play)协议是一种支持设备在局域网中实现自动发现和通信的网络协议。其端口转发功能由IGD Profile提供,允许局域网设备动态请求路由器为其开放指定的端口,以实现外部设备访问内部服务。这种功能消除了手动配置端口转发的复杂性,特别适用于需要穿透NAT(网络地址转换)环境的应用场景。

IGDInternet Gateway Device,互联网网关设备)是UPnP(Universal Plug and Play)协议的一部分,主要用于管理网络中的网关设备(如路由器)的服务和资源。IGD扩展定义了一套标准接口,允许局域网设备与网关设备通信,动态配置网络设置,例如端口转发、带宽管理和连接状态查询等。

2 UPnP协议特点

  1. 自动化配置:无需用户手动设置,减少了配置错误的风险。
  2. 动态灵活:端口映射规则可以根据需求动态添加或删除。
  3. 设备友好:支持即插即用,简化了设备的联网和部署过程。
  4. 跨设备兼容:UPnP基于标准化协议,广泛支持各种设备和平台。

3 UPnP应用场景

通过UPnP端口转发功能,我们可以使用W55MH32实现以下功能:

  1. 远程访问:将外部请求转发到局域网设备(如NAS、监控摄像头),实现外部远程访问内部设备。
  2. 远程控制:外部设备通过UPnP转换的端口,可以实现远程控制局域网内部设备(智能门锁、灯光控制器)。

4 UPnP设置端口转发的工作流程

  1. 设备发现:W55MH32通过SSDP(Simple Service Discovery Protocol向局域网中发送组播请求(HTTP M-SEARCH报文),搜索支持IGD的网关设备。
  2. 获取服务描述:W55MH32访问网关设备(路由器)获取服务描述文件,了解支持的服务和接口。
  3. 订阅IGD事件:通过事件订阅,W55MH32可以在不主动轮询的情况下,接收实时通知。
  4. 调用服务接口:使用UPnP的SOAT消息调用IGD提供的端口映射接口。
  5. 数据交互测试:外部通过访问映射的端口及路由器地址和局域网内部设备进行通信。

5 报文讲解

设备搜索

上文我们提到,设备搜索时使用SSDP协议,SSDP(Simple Service Discovery Protocol)是 UPnP 协议中的关键协议,用于设备发现和服务发布。它通过 HTTP over UDP 的形式在局域网内广播和接收报文,采用多播地址 239.255.255.250 和端口 1900。

SSDP报文主要分为以下几类:

  1. NOTIFY 消息(设备主动广播通知):用于设备向网络通告自己的存在或离线状态。
  2. M-SEARCH 消息(客户端主动搜索):客户端发送搜索请求以发现设备或服务。
  3. HTTP/1.1 响应消息(设备对 M-SEARCH 的响应):设备对搜索请求的响应,提供设备描述文件的位置及服务信息。

SSDP报文基于HTTP协议,有固定的格式,主要包括以下字段:

  1. HOST:目标地址和端口,固定为 239.255.255.250:1900。
  2. MAN:用于标识搜索消息,固定为 "ssdp:discover"(仅在 M-SEARCH 中使用)。
  3. MX:最大响应时间,指定设备在多长时间内响应(单位:秒)。
  4. ST:搜索目标,标识要查找的设备类型或服务类型。
  5. NT:通知类型,表示设备或服务的类型(在 NOTIFY 消息中使用)。
  6. USN:唯一服务名称,设备或服务的唯一标识符。
  7. LOCATION:设备描述文件的 URL,包含设备的详细信息。
  8. CACHE-CONTROL:设备信息的缓存时间,表示在多长时间内有效。

M-SEARCH请求报文实例:

M-SEARCH * HTTP/1.1

Host:239.255.255.250:1900

ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1

Man:"ssdp:discover"

MX:3

字段解析:

M-SEARCH * HTTP/1.1:表明是一个搜索请求。

Host:多播地址和端口。

ST:搜索目标类型,这里是IGD设备。

MX:最大响应事件,设备需要在3秒内返回响应。

Man:搜索请求类型,固定。

M-SEARCH响应报文实例:

HTTP/1.1 200 OK

CACHE-CONTROL: max-age=60

DATE: Tue, 07 Jan 2025 06:43:49 GMT

EXT:

LOCATION: http://192.168.100.1:1900/igd.xml

SERVER: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0

ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1

USN: uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9::urn:schemas-upnp-org:device:InternetGatewayDevice:1

HTTP/1.1 200 OK:表示响应成功。

CACHE-CONTROL:响应有效时间为60秒。

DATE:响应的时间戳。

EXT:保留字段,目前为空。

LOCATION:设备描述文件的URL。

SERVER:设备的操作系统,UPnP版本和设备名称。

ST:搜索目标类型,和请求中的ST字段一致。

USN:唯一设备标识符。

获取设备标识符

这一步会通过HTTP GET方式去请求xml文件,有关HTTP GET报文以及HTTP响应报文这里不过多讲解,有兴趣的可以参考 HTTP Client章节。

请求示例:

GET /igd.xml HTTP/1.1

Accept: text/xml, application/xml

User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)

Host: 192.168.100.1:1900

Connection: Keep-Alive

Cache-Control: no-cache

Pragma: no-cache

响应示例:

HTTP/1.1 200 OK

Content-Type: text/xml;charset=UTF-8

Content-Length: 2580

Connection: close

Cache-control: no-cache

<?xml version="1.0"?>

<root xmlns="urn:schemas-upnp-org:device-1-0">

<specVersion>

<major>1</major>

<minor>0</minor>

</specVersion>

<device>

<deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType>

<presentationURL>http://192.168.100.1:80 </presentationURL>

<friendlyName>Wireless N Router TL-WR886N</friendlyName>

<manufacturer>TP-LINK</manufacturer>

<manufacturerURL>http://www.tp-link.com.cn</manufacturerURL>

<modelDescription>TL-WR886N 6.0</modelDescription>

<modelName>TL-WR886N</modelName>

<modelNumber>6.0</modelNumber>

<UDN>uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9</UDN>

<UPC>123456789001</UPC>

<serviceList>

<service>

<serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>

<serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId>

<controlURL>/l3f</controlURL>

<eventSubURL>/l3f</eventSubURL>

<SCPDURL>/l3f.xml</SCPDURL>

</service>

</serviceList>

<deviceList>

<device>

<deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType>

<friendlyName>WAN Device</friendlyName>

<manufacturer>TP-LINK</manufacturer>

<manufacturerURL>http://www.tp-link.com.cn</manufacturerURL>

<modelDescription>WAN Device</modelDescription>

<modelName>WAN Device</modelName>

<modelNumber>1.0</modelNumber>

<modelURL></modelURL>

<serialNumber>12345678900001</serialNumber>

<UDN>uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9</UDN>

<UPC>123456789001</UPC>

<serviceList>

<service>

<serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType>

<serviceId>urn:upnp-org:serviceId:WANCommonInterfaceConfig</serviceId>

<controlURL>/ifc</controlURL>

<eventSubURL>/ifc</eventSubURL>

<SCPDURL>/ifc.xml</SCPDURL>

</service>

</serviceList>

<deviceList>

<device>

<deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType>

<friendlyName>WAN Connection Device</friendlyName>

<manufacturer>TP-LINK</manufacturer>

<manufacturerURL>http://www.tp-link.com.cn</manufacturerURL>

<modelDescription>WAN Connection Device</modelDescription>

<modelName>WAN Connection Device</modelName>

<modelNumber>1.0</modelNumber>

<modelURL></modelURL>

<serialNumber>12345678900001</serialNumber>

<UDN>uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9</UDN>

<UPC>123456789001</UPC>

<serviceList>

<service>

<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>

<serviceId>urn:upnp-org:serviceId:WANIPConnection</serviceId>

<controlURL>/ipc</controlURL>

<eventSubURL>/ipc</eventSubURL>

<SCPDURL>/ipc.xml</SCPDURL>

</service>

</serviceList>

</device>

</deviceList>

</device>

</deviceList>

</device>

</root>

订阅IGD事件

通过HTTP SUBSCRIBE订阅IGD事件,示例:

SUBSCRIBE /ipc HTTP/1.1

Host: 192.168.100.1:1900

USER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1)

CALLBACK: <http://192.168.100.101:5002/>

NT: upnp:event

TIMEOUT: Second-1800

响应示例:

HTTP/1.1 200 OK

Content-Type: text/xml;charset=UTF-8

Content-Length: 0

Connection: close

Cache-control: no-cache

Server: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0

Timeout: Second-1800

SID: uuid:82-2150160019

添加映射端口报文

例如,我们想映射TCP协议的内部端口8000到外部端口1000上,可以按照以下示例进行HTTP请求:

POST /ipc HTTP/1.1

Content-Type: text/xml; charset="utf-8"

SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping"

User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)

Host: 192.168.100.1:1900

Content-Length: 1131

Connection: Keep-Alive

Cache-Control: no-cache

Pragma: no-cache

<?xml version="1.0"?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<SOAP-ENV:Body>

<m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">

<NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">

</NewRemoteHost>

<NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2">

1000</NewExternalPort>

<NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">

TCP</NewProtocol>

<NewInternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2">

8000</NewInternalPort>

<NewInternalClient xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">

192.168.100.101</NewInternalClient>

<NewEnabled xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="boolean">1</NewEnabled>

<NewPortMappingDescription xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">W5500_uPnPGetway</NewPortMappingDescription>

<NewLeaseDuration xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4">0</NewLeaseDuration>

</m:AddPortMapping>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

主要字段的描述如下:

m:AddPortMapping:添加端口映射

NewExternalPort:外部端口号

NewProtocol:协议类型

NewInternalPort:内部端口号

NewInternalClient:内部地址

响应内容:

HTTP/1.1 200 OK

Content-Type: text/xml;charset=UTF-8

Content-Length: 289

Connection: close

Cache-control: no-cache

Server: vxWorks/5.5 UPnP/1.0 TL-WR886N/6.0

<?xml version="1.0"?>

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"></u:AddPortMappingResponse></s:Body></s:Envelope>

删除端口映射报文

例如,我们想删除上面映射的1000端口,可以按照以下示例进行HTTP请求:

POST /ipc HTTP/1.1

Content-Type: text/xml; charset="utf-8"

SOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#DeletePortMapping"

User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)

Host: 192.168.100.1:1900

Content-Length: 604

Connection: Keep-Alive

Cache-Control: no-cache

Pragma: no-cache

<?xml version="1.0"?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body>

<m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">

<NewRemoteHost xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string"></NewRemoteHost>

<NewExternalPort xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui2">1000</NewExternalPort>

<NewProtocol xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">TCP</NewProtocol>

</m:DeletePortMapping>

</SOAP-ENV:Body></SOAP-ENV:Envelope>

主要字段的描述如下:

m:DeletePortMapping:删除端口映射

NewExternalPort:外部端口号

NewProtocol:协议类型

NewRemoteHost:外部访问来源,可以为空

6 实现过程

在这个例程中,我们实现了通过串口控制LED灯开关、获取和设置网络地址信息、TCP和UDP回环数据测试以及UPnP添加映射端口和删除映射端口的功能。

注意:测试实例需要W55MH32接入在支持UPnP端口转发的路由器下。

步骤1:设置以太网缓存大小

static uint8_t  tx_size[_WIZCHIP_SOCK_NUM_]         = {4, 4, 2, 1, 1, 1, 1, 2};
static uint8_t  rx_size[_WIZCHIP_SOCK_NUM_]         = {4, 4, 2, 1, 1, 1, 1, 2};



    /* socket rx and tx buff init */
    wizchip_init(tx_size, rx_size);

在这里我们给socket0-7的收发缓存分别设置为4KB,4KB,2KB,1KB,1KB,1KB,1KB,2KB。

其中socket0用于UPnP协议处理,socket1用于TCP和UDP回环处理,socket2用于监听IGD事件。

步骤2:LED控制函数注册

UserLED_Control_Init(set_user_led_status);

set_user_led_status()函数为控制LED的函数,具体内容如下:

 1. void set_user_led_status(uint8_t val)

 2. {

 3.     if (val)

 4.     {

 5.         GPIO_SetBits(GPIOD, GPIO_Pin_14);

 6.     }

 7.     else

 8.     {

 9.         GPIO_ResetBits(GPIOD, GPIO_Pin_14);

10.     }

11. }

12.  

步骤3:搜索UPnP设备

    do
    {
        printf("Send SSDP.. \r\n");
    } while (SSDPProcess(SOCKET_ID) != 0); // SSDP Search discovery


/**< SSDP Header */
unsigned char SSDP[] = "\
M-SEARCH * HTTP/1.1\r\n\
Host:239.255.255.250:1900\r\n\
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\
Man:\"ssdp:discover\"\r\n\
MX:3\r\n\
\r\n\
";

/**
 * @brief       This function processes the SSDP message.
 * @return      0: success, -1: reply packet timeout, 1: received SSDP parse error
 */
signed char SSDPProcess(SOCKET sockfd)
{
    char          ret_value     = 0;
    long          endTime       = 0;
    unsigned char mcast_addr[4] = {239, 255, 255, 250};
    // unsigned char_t_t mcast_mac[6] = {0x28, 0x2C, 0xB2, 0xE9, 0x42, 0xD6};
    unsigned char  recv_addr[4];
    unsigned short recv_port;

    // UDP Socket Open
    close(sockfd);
    socket(sockfd, Sn_MR_UDP, PORT_SSDP, 0); /*Initialize socket for socket 0*/
    while (getSn_SR(sockfd) != SOCK_UDP);

    #ifdef UPNP_DEBUG
    printf("%s\r\n", SSDP);
    #endif

    // Send SSDP
    if (sendto(sockfd, SSDP, strlen((char *)SSDP), mcast_addr, 1900) <= 0)
        printf("SSDP Send error!!!!!!!\r\n");

    // Receive Reply
    memset(recv_buffer, '\0', RECV_BUFFER_SIZE);
    endTime = my_time + 3;
    while (recvfrom(sockfd, (unsigned char *)recv_buffer, RECV_BUFFER_SIZE, recv_addr, &recv_port) <= 0 && my_time < endTime); // Check Receive Buffer
    if (my_time >= endTime)
    {                                                                                                                          // Check Timeout
        close(sockfd);
        return -1;
    }

    // UDP Socket Close
    close(sockfd);

#ifdef UPNP_DEBUG
    printf("\r\nReceiveData\r\n%s\r\n", recv_buffer);
#endif

    // Parse SSDP Message
    if ((ret_value = parseSSDP(recv_buffer)) == 0)
        UPnP_Step = 1;
    return ret_value;
}

在这个函数中,主要是使用SSDP协议搜索IGD设备,发送报文和前面我们介绍的一致。

步骤4:获取IGD设备描述

    if (GetDescriptionProcess(SOCKET_ID) == 0) // GET IGD description
    {
        printf("GetDescription Success!!\r\n");
    }
    else
    {
        printf("GetDescription Fail!!\r\n");
    }


/**
 * @brief        This function gets the description message from IGD(Internet Gateway Device).
 * @return      0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error
 */
signed char GetDescriptionProcess(
    SOCKET sockfd /**< a socket number. */
)
{
    char           ret_value = 0;
    long           endTime   = 0;
    unsigned long  ipaddr;
    unsigned short port;

    // Check UPnP Step
    if (UPnP_Step < 1) return -2;

    // Make HTTP GET Header
    memset(send_buffer, '\0', SEND_BUFFER_SIZE);
    MakeGETHeader(send_buffer);

#ifdef UPNP_DEBUG
    printf("%s\r\n", send_buffer);
#endif

    ipaddr = inet_addr((unsigned char *)descIP);
    ipaddr = swapl(ipaddr);
    port   = ATOI(descPORT, 10);

    // Connect to IGD(Internet Gateway Device)
    close(sockfd);
    socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
    while (getSn_SR(sockfd) != SOCK_INIT)
    {
        delay_ms(100);
    }

    if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
        printf("TCP Socket Error!!\r\n");

    // Send Get Discription Message
    while ((getSn_SR(sockfd) != SOCK_ESTABLISHED));
    send(sockfd, (void *)send_buffer, strlen(send_buffer));

    // Receive Reply
    memset(recv_buffer, '\0', RECV_BUFFER_SIZE);
    delay_ms(500);
    endTime = my_time + 3;
    while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
    if (my_time >= endTime)
    {                                                                                      // Check Timeout
        close(sockfd);
        return -1;
    }

    // TCP Socket Close
    close(sockfd);

#ifdef UPNP_DEBUG
    printf("\r\nReceiveData\r\n%s\r\n", recv_buffer);
#endif

    // Parse Discription Message
    if ((ret_value = parseDescription(recv_buffer)) == 0) UPnP_Step = 2;
    return ret_value;
}

 请求报文通过MakeGETHeader()函数进行组包,具体报文如下:

/**
 * @brief   This function makes the HTTP GET header.
 * @param   dest:Target string pointer
 * @return  none
 */
void MakeGETHeader(char *dest)
{
    char local_port[6] = {'\0'};
    strcat(dest, "GET ");
    strcat(dest, descLOCATION);
    strcat(dest, " HTTP/1.1\r\n");
    strcat(dest, "Accept: text/xml, application/xml\r\n");
    strcat(dest, "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n");
    strcat(dest, "Host: ");
    strcat(dest, descIP);
    sprintf(local_port, ":%s", descPORT);
    strcat(dest, local_port);
    strcat(dest, "\r\nConnection: Keep-Alive\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n");
}

然后将接收到的内容,通过parseDescription()函数进行解析,如果设备描述中不支持WANIPConnection服务,则说明不支持端口映射,返回错误。

parseDescription()函数内容如下:

/**
 * @brief       This function parses the received description message from IGD(Internet Gateway Device).
 * @return      0: success, 1: received xml parse error
 */
signed char parseDescription(
    const char *xml /**< string for parse */
)
{
    const char controlURL_[]  = "<controlURL>";
    const char eventSubURL_[] = "<eventSubURL>";
    char      *URL_start = 0, *URL_end = 0;

    if (parseHTTP(xml) != 0) return 1;
    //printf("\r\n%s\r\n", xml);
    // Find Control URL("/etc/linuxigd/gateconnSCPD.ctl")
    if ((URL_start = strstr(xml, "urn:schemas-upnp-org:service:WANIPConnection:1")) == NULL) return 1;
    if ((URL_start = strstr(URL_start, controlURL_)) == NULL) return 1;
    if ((URL_end = strstr(URL_start, "</controlURL>")) == NULL) return 1;

    strncpy(controlURL, URL_start + strlen(controlURL_), URL_end - URL_start - strlen(controlURL_));

    // Find Eventing Subscription URL("/etc/linuxigd/gateconnSCPD.evt")
    if ((URL_start = strstr(xml, "urn:schemas-upnp-org:service:WANIPConnection:1")) == NULL) return 1;
    if ((URL_start = strstr(URL_start, eventSubURL_)) == NULL) return 1;
    if ((URL_end = strstr(URL_start, "</eventSubURL>")) == NULL) return 1;

    strncpy(eventSubURL, URL_start + strlen(eventSubURL_), URL_end - URL_start - strlen(eventSubURL_));

    return 0;
}

步骤5:订阅IGD事件

if (SetEventing(SOCKET_ID) == 0) // Subscribes IGD event messages
    {
        printf("SetEventing Success!!\r\n");
    }
    else
    {
        printf("SetEventing Fail!!\r\n");
    }

SetEventing()函数内容如下:

/**
 * @brief       This function subscribes to the eventing message from IGD(Internet Gateway Device).
 * @return      0: success, -2: Invalid UPnP Step, -1: reply packet timeout
 */
signed char SetEventing(
    SOCKET sockfd /**< a socket number. */
)
{
    long          endTime = 0;
    unsigned long ipaddr;
    unsigned short port;

    // Check UPnP Step
    if (UPnP_Step < 2) return -2;

    // Make Subscription message
    memset(send_buffer, '\0', SEND_BUFFER_SIZE);
    MakeSubscribe(send_buffer, PORT_UPNP_EVENTING);

#ifdef UPNP_DEBUG
    printf("%s\r\n", send_buffer);
#endif

    ipaddr = inet_addr((unsigned char *)descIP);
    ipaddr = swapl(ipaddr);
    port   = ATOI(descPORT, 10);

    // Connect to IGD(Internet Gateway Device)
    close(sockfd);
    socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
    while (getSn_SR(sockfd) != SOCK_INIT)
    {
        delay_ms(100);
    }

    if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
        printf("TCP Socket Error!!\r\n");

    // Send Get Discription Message
    while ((getSn_SR(sockfd) != SOCK_ESTABLISHED));
    send(sockfd, (void *)send_buffer, strlen(send_buffer));

    // Receive Reply
    memset(recv_buffer, '\0', RECV_BUFFER_SIZE);
    delay_ms(500);
    endTime = my_time + 3;
    while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
    if (my_time >= endTime)
    {                                                                                   // Check Timeout
        close(sockfd);
        return -1;
    }

    // TCP Socket Close
    close(sockfd);

#ifdef UPNP_DEBUG
    printf("\r\nReceiveData\r\n%s\r\n", recv_buffer);
#endif

    return parseHTTP(recv_buffer);
}

请求报文通过MakeSubscribe()函数进行组包,具体报文如下:

/**
 * @brief   This function makes the Subscription message.
 * @param   dest:Target string pointer
 * @param   listen_port:Listen port
 * @return  none
 */
void MakeSubscribe(char *dest, const unsigned int listen_port)
{
    char          local_port[6] = {'\0'}, ipaddr[16] = {'\0'};
    unsigned char ip[4];
    strcat(dest, "SUBSCRIBE ");
    strcat(dest, eventSubURL);
    strcat(dest, " HTTP/1.1\r\n");
    strcat(dest, "Host: ");
    strcat(dest, descIP);
    sprintf(local_port, ":%s", descPORT);
    strcat(dest, local_port);
    strcat(dest, "\r\nUSER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1)\r\n");
    strcat(dest, "CALLBACK: <http://");
    getSIPR(ip);
    sprintf(ipaddr, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
    strcat(dest, ipaddr);
    sprintf(local_port, ":%d/>", listen_port);
    strcat(dest, local_port);
    strcat(dest, "\r\nNT: upnp:event\r\nTIMEOUT: Second-1800\r\n\r\n");
}

最后,通过parseHTTP()函数解析HTTP响应报文,判断是否订阅成功。

parseHTTP()函数如下:

/*-----String Parse Functions-----*/

/**
 * @brief       This function parses the HTTP header.
 * @return      0: success, 1: received xml parse error
 */
signed char parseHTTP(
    const char *xml /**< string for parse */
)
{
    char *loc = 0;
    if (strstr(xml, "200 OK") != NULL)
        return 0;
    else
    {
        loc = strstr(xml, "\r\n");
        memset(content, '\0', CONT_BUFFER_SIZE);
        strncpy(content, xml, loc - xml);
        printf("\r\nHTTP Error:\r\n%s\r\n\r\n", content);
        return 1;
    }
}

步骤6:执行UPnP主程序

    Main_Menu(SOCKET_ID, SOCKET_ID + 1, SOCKET_ID + 2, ethernet_buf, tcps_port, udps_port); // Main menu

/**
 * @brief   Display/Manage a Menu on HyperTerminal Window
 * @param   sn: use for SSDP; sn2: use for run tcp/udp loopback; sn3: use for listenes IGD event message
 * @param   buf: use for tcp/udp loopback rx/tx buff; tcps_port: use for tcp loopback listen; udps_port: use for udp loopback receive
 * @return  none
 */
void Main_Menu(uint8_t sn, uint8_t sn2, uint8_t sn3, uint8_t *buf, uint16_t tcps_port, uint16_t udps_port)
{
    static char           choice[3];
    static char           msg[256], ipaddr[12], protocol[4];
    static unsigned short ret, external_port, internal_port;
    static uint8_t        bTreat;
    static uint8_t        Sip[4];

    while (1)
    {
        /* Display Menu on HyperTerminal Window */
        bTreat = RESET;
        printf("\r\n====================== WIZnet Chip Control Point ===================\r\n");
        printf("This Application is basic example of UART interface with\r\n");
        printf("Windows Hyper Terminal. \r\n");
        printf("\r\n==========================================================\r\n");
        printf("                          APPLICATION MENU :\r\n");
        printf("\r\n==========================================================\r\n\n");
        printf(" 1 - Set LED on \r\n");
        printf(" 2 - Set LED off \r\n");
        printf(" 3 - Show network setting\r\n");
        printf(" 4 - Set  network setting\r\n");
        printf(" 5 - Run TCP Loopback\r\n");
        printf(" 6 - Run UDP Loopback\r\n");
        printf(" 7 - UPnP PortForwarding: AddPort\r\n");
        printf(" 8 - UPnP PortForwarding: DeletePort\r\n");

        printf("Enter your choice : ");
        memset(choice, 0, sizeof(choice));
        scanf("%s", choice);
        printf("%c\r\n", choice[0]);
}

在这里会执行一个用户选项菜单,选项1和2控制LED开关,选项3和4打印和设置网络地址信息,选项5运行一个TCP回环测试程序(回环测试程序可参考TCP Server章节),选项6运行一个UDP回环测试程序(回环测试程序可参考UDP章节)。选项7添加一个UPnP端口映射表,选项8删除一个UPnP端口映射表。这里我们主要讲解UPnP相关的选项7和选项8。

步骤7:添加一个UPnP端口映射表

代码如下:

        if (choice[0] == '7')
        {
            bTreat = SET;

            printf("\r\nType a Protocol(TCP/UDP) : ");
            memset(msg, 0, sizeof(msg));
            scanf("%s", msg);
            printf("%s\r\n", msg);
            strncpy(protocol, msg, 3);
            protocol[3] = '\0';

            printf("\r\nType a External Port Number : ");
            memset(msg, 0, sizeof(msg));
            scanf("%s", msg);
            printf("%s\r\n", msg);
            external_port = ATOI(msg, 10);

            printf("\r\nType a Internal Port Number : ");
            memset(msg, 0, sizeof(msg));
            scanf("%s", msg);
            printf("%s\r\n", msg);
            internal_port = ATOI(msg, 10);
                                            if(strcmp(protocol,"tcptcp") || strcmp(protocol,"TCP"))
                tcps_port     = internal_port;
                                                    else
                                                        udps_port     = internal_port;
            close(sn2);
            // Try to Add Port Action
            getSIPR(Sip);
            sprintf(ipaddr, "%d.%d.%d.%d", Sip[0], Sip[1], Sip[2], Sip[3]);
            if ((ret = AddPortProcess(sn, protocol, external_port, ipaddr, internal_port, "W5500_uPnPGetway")) == 0)
                printf("AddPort Success!!\r\n");
            else
                printf("AddPort Error Code is %d\r\n", ret);
        }

在这里,我们需要外部输入端口映射的协议类型(TCP或UDP),以及外部端口号和内部端口号。输入完成后,选项5或选项6的端口号会替换为输入的内部端口号,然后通过AddPortProcess()函数执行添加端口映射处理。AddPortProcess()函数内容如下:

/**
 * @brief       This function processes the add port to IGD(Internet Gateway Device).
 * @return      0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error, other: UPnP error code
 */
signed short AddPortProcess(
    SOCKET             sockfd,         /**< a socket number. */
    const char        *protocol,       /**< a procotol name. "TCP" or "UDP" */
    const unsigned int extertnal_port, /**< an external port number. */
    const char        *internal_ip,    /**< an internal ip address. */
    const unsigned int internal_port,  /**< an internal port number. */
    const char        *description     /**< a description of this portforward. */
)
{
    short          len     = 0;
    long           endTime = 0;
    unsigned long  ipaddr;
    unsigned short port;
 
    // Check UPnP Step
    if (UPnP_Step < 2) return -2;
 
    // Make "Add Port" XML(SOAP)
    memset(content, '\0', CONT_BUFFER_SIZE);
    MakeSOAPAddControl(content, protocol, extertnal_port, internal_ip, internal_port, description);
 
    // Make HTTP POST Header
    memset(send_buffer, '\0', SEND_BUFFER_SIZE);
    len = strlen(content);
    MakePOSTHeader(send_buffer, len, ADD_PORT);
    strcat(send_buffer, content);
 
    //#ifdef UPNP_DEBUG
    printf("%s\r\n", send_buffer);
    //#endif
 
    ipaddr = inet_addr((unsigned char *)descIP);
    ipaddr = swapl(ipaddr);
    port   = ATOI(descPORT, 10);
 
    // Connect to IGD(Internet Gateway Device)
    socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
    while (getSn_SR(sockfd) != SOCK_INIT);
 
    if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
        printf("TCP Socket Error!!\r\n");
 
    // Send "Delete Port" Message
    while (getSn_SR(sockfd) != SOCK_ESTABLISHED);
    send(sockfd, (void *)send_buffer, strlen(send_buffer));
 
    // Receive Reply
    memset(recv_buffer, '\0', RECV_BUFFER_SIZE);
    delay_ms(500);
    endTime = my_time
    while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
    if (my_time >= endTime)
    {                                                                                      // Check Timeout
        close(sockfd);
        return -1;
    }
 
    // TCP Socket Close
    close(sockfd);
 
    //#ifdef UPNP_DEBUG
    printf("\r\nReceiveData\r\n%s\r\n", recv_buffer);
    //#endif
 
    // Parse Replied Message
    return parseAddPort(recv_buffer);
}

程序首先会通过MakeSOAPAddControl()函数组装请求报文中的XML部分,具体内容如下:

/**< SOAP header & tail */
const char soap_start[] =
    "\
<?xml version=\"1.0\"?>\r\n\
<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><SOAP-ENV:Body>\
";

const char soap_end[] =
    "\
</SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n\
";

/**< Delete Port Mapping */
const char DeletePortMapping_[] = "<m:DeletePortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">";
const char _DeletePortMapping[] = "</m:DeletePortMapping>";

/**< New Remote Host */
const char NewRemoteHost_[] = "<NewRemoteHost xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">";
const char _NewRemoteHost[] = "</NewRemoteHost>";

/**< New External Port */
const char NewExternalPort_[] = "<NewExternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">";
const char _NewExternalPort[] = "</NewExternalPort>";

/**< New Protocol */
const char NewProtocol_[] = "<NewProtocol xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">";
const char _NewProtocol[] = "</NewProtocol>";

/**< Add Port Mapping */
const char AddPortMapping_[] = "<m:AddPortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANIPConnection:1\">";
const char _AddPortMapping[] = "</m:AddPortMapping>";

/**< New Internal Port */
const char NewInternalPort_[] = "<NewInternalPort xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui2\">";
const char _NewInternalPort[] = "</NewInternalPort>";

/**< New Internal Client */
const char NewInternalClient_[] = "<NewInternalClient xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">";
const char _NewInternalClient[] = "</NewInternalClient>";

/**< New Enabled */
const char NewEnabled[]  = "<NewEnabled xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"boolean\">1</NewEnabled>";
const char NewEnabled_[] = "<NewEnabled xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"boolean\">";
const char _NewEnabled[] = "</NewEnabled>";

/**< New Port Mapping Description */
const char NewPortMappingDescription_[] = "<NewPortMappingDescription xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"string\">";
const char _NewPortMappingDescription[] = "</NewPortMappingDescription>";

/**< New Lease Duration */
const char NewLeaseDuration[]  = "<NewLeaseDuration xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui4\">0</NewLeaseDuration>";
const char NewLeaseDuration_[] = "<NewLeaseDuration xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"ui4\">";
const char _NewLeaseDuration[] = "</NewLeaseDuration>";

/**
 * @brief   This function makes the Add Port Control message in SOAP.
 * @param   dest:Target string pointer
 * @param   protocol:Protocol type
 * @param   extertnal_port:External port
 * @param   internal_ip:Internal IP address
 * @param   internal_port:Internal port
 * @param   description:Description
 * @return  none
 */
void MakeSOAPAddControl(char *dest, const char *protocol, const unsigned int extertnal_port, const char * internal_ip, const unsigned int internal_port, const char *description)
{
    char local_port[6] = {'\0'};
    strcat(dest, soap_start);
    strcat(dest, AddPortMapping_);
    strcat(dest, NewRemoteHost_);
    strcat(dest, _NewRemoteHost);
    strcat(dest, NewExternalPort_);
    sprintf(local_port, "%d", extertnal_port);
    strcat(dest, local_port);
    strcat(dest, _NewExternalPort);
    strcat(dest, NewProtocol_);
    strcat(dest, protocol);
    strcat(dest, _NewProtocol);
    strcat(dest, NewInternalPort_);
    sprintf(local_port, "%d", internal_port);
    strcat(dest, local_port);
    strcat(dest, _NewInternalPort);
    strcat(dest, NewInternalClient_);
    strcat(dest, internal_ip);
    strcat(dest, _NewInternalClient);
    strcat(dest, NewEnabled);
    strcat(dest, NewPortMappingDescription_);
    strcat(dest, description);
    strcat(dest, _NewPortMappingDescription);
    strcat(dest, NewLeaseDuration);
    strcat(dest, _AddPortMapping);
    strcat(dest, soap_end);
}

然后通过MakePOSTHeader()函数制作HTTP头部内容,具体内容如下:

/**
 * @brief   This function makes the HTTP POST Header.
 * @param   dest:Target string pointer
 * @param   content_length: content length
 * @param   action: action type
 * @return  none
 */
void MakePOSTHeader(char *dest, int content_length, int action)
{
    char local_length[6] = {'\0'}, local_port[6] = {'\0'};
    sprintf(local_length, "%d", content_length);
    strcat(dest, "POST ");
    strcat(dest, controlURL);
    strcat(dest, " HTTP/1.1\r\n");
    strcat(dest, "Content-Type: text/xml; charset=\"utf-8\"\r\n");
    strcat(dest, "SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#");
    switch (action)
    {
    case DELETE_PORT:
        strcat(dest, "DeletePortMapping\"");
        break;
    case ADD_PORT:
        strcat(dest, "AddPortMapping\"");
        break;
    }
    strcat(dest, "\r\nUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n");
    strcat(dest, "Host: ");
    strcat(dest, descIP);
    sprintf(local_port, ":%s", descPORT);
    strcat(dest, local_port);
    strcat(dest, "\r\nContent-Length: ");
    strcat(dest, local_length);
    strcat(dest, "\r\nConnection: Keep-Alive\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n");
}

最后则是发送请求,然后通过parseAddPort()函数解析响应内容判断是否添加端口映射成功。

/**
 * @brief       This function parses the received add port message from IGD(Internet Gateway Device).
 * @return      0: success, 1: received xml parse error, other: UPnP error code
 */
signed short parseAddPort(
    const char *xml /**< string for parse */
)
{
    parseHTTP(xml);
    if (strstr(xml, "u:AddPortMappingResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"") == NULL)
    {
        return parseError(xml);
    }

    return 0;
}

步骤8:删除一个UPnP端口映射表

        if (choice[0] == '8')
        {
            bTreat = SET;

            printf("\r\nType a Protocol(TCP/UDP) : ");
            memset(msg, 0, sizeof(msg));
            scanf("%s", msg);
            printf("%s\r\n", msg);
            //GetInputString(msg);
            strncpy(protocol, msg, 3);
            protocol[3] = '\0';

            printf("\r\nType a External Port Number : ");

            //                      TCP_LISTEN_PORT=num;
            //                      UDP_LISTEN_PORT=num;
            //                      printf("%d\r\n",TCP_LISTEN_PORT);
            memset(msg, 0, sizeof(msg));
            scanf("%s", msg);
            printf("%s\r\n", msg);
            external_port = ATOI(msg, 10);

            // Try to Delete Port Action
            if ((ret = DeletePortProcess(sn, protocol, external_port)) == 0)
                printf("DeletePort Success!!\r\n");
            else
                printf("DeletePort Error Code is %d\r\n", ret);
        }

        /* OTHERS CHOICE*/
        if (bTreat == RESET)
        {
            printf(" wrong choice  \r\n");
        }

        eventing_listener(sn3);

         }   /* While(1)*/
         } /* Main_Menu */

在这里,我们需要外部输入删除端口映射的协议类型(TCP或UDP),以及外部端口号。输入完成后,通过DeletePortProcess()函数执行添加端口映射处理。DeletePortProcess()函数内容如下:

/**
 * @brief       This function processes the delete port to IGD(Internet Gateway Device).
 * @return      0: success, -2: Invalid UPnP Step, -1: reply packet timeout, 1: received xml parse error, other: UPnP error code
 */
signed short DeletePortProcess(
    SOCKET             sockfd,        /**< a socket number. */
    const char        *protocol,      /**< a procotol name. "TCP" or "UDP" */
    const unsigned int extertnal_port /**< an external port number. */
)
{
    short          len     = 0;
    long           endTime = 0;
    unsigned long  ipaddr;
    unsigned short port;

    // Check UPnP Step
    if (UPnP_Step < 2) return -2;

    // Make "Delete Port" XML(SOAP)
    memset(content, '\0', CONT_BUFFER_SIZE);
    MakeSOAPDeleteControl(content, protocol, extertnal_port);

    // Make HTTP POST Header
    memset(send_buffer, '\0', SEND_BUFFER_SIZE);
    len = strlen(content);
    MakePOSTHeader(send_buffer, len, DELETE_PORT);
    strcat(send_buffer, content);

    //#ifdef UPNP_DEBUG
    printf("%s\r\n", send_buffer);
    //#endif

    ipaddr = inet_addr((unsigned char *)descIP);
    ipaddr = swapl(ipaddr);
    port   = ATOI(descPORT, 10);

    // Connect to IGD(Internet Gateway Device)
    close(sockfd);
    socket(sockfd, Sn_MR_TCP, PORT_UPNP, Sn_MR_ND); /*Open a port of the socket*/
    while (getSn_SR(sockfd) != SOCK_INIT);

    if (connect(sockfd, (unsigned char *)&ipaddr, port) == 0)
        printf("TCP Socket Error!!\r\n");

    // Send "Delete Port" Message
    while (getSn_SR(sockfd) != SOCK_ESTABLISHED);
    send(sockfd, (void *)send_buffer, strlen(send_buffer));

    // Receive Reply
    memset(recv_buffer, '\0', RECV_BUFFER_SIZE);
    delay_ms(500);
    endTime = my_time + 3;
    while (recv(sockfd, (void *)recv_buffer, RECV_BUFFER_SIZE) <= 0 && my_time < endTime); // Check Receive Buffer
    if (my_time >= endTime)
    {                                                                                      // Check Timeout
        close(sockfd);
        return -1;
    }

    // TCP Socket Close
    close(sockfd);

    //#ifdef UPNP_DEBUG
    printf("\r\nReceiveData\r\n%s\r\n", recv_buffer);
    //#endif

    // Parse Replied Message
    return parseDeletePort(recv_buffer);
    
}

首先会通过MakeSOAPDeleteControl()函数组装请求报文中的XML部分,具体内容如下:

/**
 * @brief   This function makes the Delete Port Control message in SOAP.
 * @param   dest:Target string pointer
 * @param   protocol:Protocol type
 * @param   extertnal_port:External port
 * @return  none
 */
void MakeSOAPDeleteControl(char *dest, const char *protocol, const unsigned int extertnal_port)
{
    char local_port[6] = {'\0'};
    strcat(dest, soap_start);
    strcat(dest, DeletePortMapping_);
    strcat(dest, NewRemoteHost_);
    strcat(dest, _NewRemoteHost);
    strcat(dest, NewExternalPort_);
    sprintf(local_port, "%d", extertnal_port);
    strcat(dest, local_port);
    strcat(dest, _NewExternalPort);
    strcat(dest, NewProtocol_);
    strcat(dest, protocol);
    strcat(dest, _NewProtocol);
    strcat(dest, _DeletePortMapping);
    strcat(dest, soap_end);
}

然后通过MakePOSTHeader()函数制作HTTP头部内容,具体内容如下:

/**
 * @brief   This function makes the HTTP POST Header.
 * @param   dest:Target string pointer
 * @param   content_length: content length
 * @param   action: action type
 * @return  none
 */
void MakePOSTHeader(char *dest, int content_length, int action)
{
    char local_length[6] = {'\0'}, local_port[6] = {'\0'};
    sprintf(local_length, "%d", content_length);
    strcat(dest, "POST ");
    strcat(dest, controlURL);
    strcat(dest, " HTTP/1.1\r\n");
    strcat(dest, "Content-Type: text/xml; charset=\"utf-8\"\r\n");
    strcat(dest, "SOAPAction: \"urn:schemas-upnp-org:service:WANIPConnection:1#");
    switch (action)
    {
    case DELETE_PORT:
        strcat(dest, "DeletePortMapping\"");
        break;
    case ADD_PORT:
        strcat(dest, "AddPortMapping\"");
        break;
    }
    strcat(dest, "\r\nUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n");
    strcat(dest, "Host: ");
    strcat(dest, descIP);
    sprintf(local_port, ":%s", descPORT);
    strcat(dest, local_port);
    strcat(dest, "\r\nContent-Length: ");
    strcat(dest, local_length);
    strcat(dest, "\r\nConnection: Keep-Alive\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n");
}

最后则是发送请求,然后通过parseDeletePort()函数解析响应内容判断是否添加端口映射成功。

/**
 * @brief       This function parses the received delete port message from IGD(Internet Gateway Device).
 * @return      0: success, 1: received xml parse error, other: UPnP error code
 */
signed short parseDeletePort(
    const char *xml /**< string for parse */
)
{
    parseHTTP(xml);
    if (strstr(xml, "u:DeletePortMappingResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\"") == NULL)
    {
        return parseError(xml);
    }

    return 0;
}

7 运行结果

烧录例程运行后,首先进行了PHY链路检测,然后是通过DHCP获取网络地址并打印网络地址信息:

接下来是搜索IGD设备,搜索成功后会进行获取设备描述以及设置订阅IGD事件,全部成功后则进入主菜单。

接着,我们输入7,添加一个TCP协议的端口映射,外部端口为12345,内部端口为8000。

打开UPnP Wizard软件,点击刷新后可以看到我们添加的端口映射表。(UPnP Wizard下载链接:https://upnp-wizard.en.softonic.com/

然后我们输入5,打开TCP回环测试程序。

随后,我们打开一个网络调试助手,例如SocketTester,选择为TCP Client模式,服务器地址为外部IP地址也就是192.168.1.135,端口号为外部端口号12345,点击”Connect”连接后,可以看到成功连接到内部的W55MH32上了。UDP也是同样进行操作,这里不再演示。

接着我们输入Q退出回环测试程序,然后输入8,将之前添加的TCP协议的12345外部端口删除。在UPnP Wizard上点击刷新,可以看到已经成功删除,再次执行回环测试程序,已经无法连接上内部的W55MH32上。

8 总结

本文讲解了如何在 W55MH32 芯片上实现 UPnP 协议的端口转发功能,通过实战例程详细展示了从设备搜索、获取设备描述、订阅事件到添加和删除端口映射的完整流程,包括各步骤涉及的协议报文、函数实现和具体操作。文章还对 UPnP 协议的简介、特点、应用场景进行了分析,帮助读者理解其在网络设备互联互通中的实际应用价值。

下一篇文章将聚焦 TFTP 协议,解析其核心原理及在文件传输中的应用,同时讲解如何在W55MH32上实现 TFTP 功能,敬请期待!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值