單芯片解決方案,開啟全新體驗——W55MH32 高性能以太網(wǎng)單片機
W55MH32是WIZnet重磅推出的高性能以太網(wǎng)單片機,它為用戶帶來前所未有的集成化體驗。這顆芯片將強大的組件集于一身,具體來說,一顆W55MH32內(nèi)置高性能Arm? Cortex-M3核心,其主頻最高可達216MHz;配備1024KB FLASH與96KB SRAM,滿足存儲與數(shù)據(jù)處理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP協(xié)議棧、內(nèi)置MAC以及PHY,擁有獨立的32KB以太網(wǎng)收發(fā)緩存,可供8個獨立硬件socket使用。如此配置,真正實現(xiàn)了All-in-One解決方案,為開發(fā)者提供極大便利。
在封裝規(guī)格上,W55MH32提供了兩種選擇:QFN100和QFN68。
W55MH32L采用QFN100封裝版本,尺寸為12x12mm,其資源豐富,專為各種復雜工控場景設計。它擁有66個GPIO、3個ADC、12通道DMA、17個定時器、2個I2C、5個串口、2個SPI接口(其中1個帶I2S接口復用)、1個CAN、1個USB2.0以及1個SDIO接口。如此豐富的外設資源,能夠輕松應對工業(yè)控制中多樣化的連接需求,無論是與各類傳感器、執(zhí)行器的通信,還是對復雜工業(yè)協(xié)議的支持,都能游刃有余,成為復雜工控領域的理想選擇。同系列還有QFN68封裝的W55MH32Q版本,該版本體積更小,僅為8x8mm,成本低,適合集成度高的網(wǎng)關模組等場景,軟件使用方法一致。更多信息和資料請進入http://www.w5500.com/網(wǎng)站或者私信獲取。
此外,本W(wǎng)55MH32支持硬件加密算法單元,WIZnet還推出TOE+SSL應用,涵蓋TCP SSL、HTTP SSL以及 MQTT SSL等,為網(wǎng)絡通信安全再添保障。
為助力開發(fā)者快速上手與深入開發(fā),基于W55MH32L這顆芯片,WIZnet精心打造了配套開發(fā)板。開發(fā)板集成WIZ-Link芯片,借助一根USB C口數(shù)據(jù)線,就能輕松實現(xiàn)調(diào)試、下載以及串口打印日志等功能。開發(fā)板將所有外設全部引出,拓展功能也大幅提升,便于開發(fā)者全面評估芯片性能。
若您想獲取芯片和開發(fā)板的更多詳細信息,包括產(chǎn)品特性、技術參數(shù)以及價格等,歡迎訪問官方網(wǎng)頁:http://www.w5500.com/,我們期待與您共同探索W55MH32的無限可能。
第十三章 W55MH32 UPnP端口轉(zhuǎn)發(fā)示例
本篇文章,我們將詳細介紹如何在W55MH32芯片上面實現(xiàn)UPnP協(xié)議。使用W55MH32的TOE引擎,我們只需進行簡單的socket編程及寄存器讀寫,便可輕松實現(xiàn)以太網(wǎng)應用。接下來我們通過實戰(zhàn)例程,為大家講解如何使用TOE引擎實現(xiàn)UPnP協(xié)議的端口轉(zhuǎn)發(fā)功能。
該例程用到的其他網(wǎng)絡協(xié)議,例如DHCP,請參考相關章節(jié)。有關W55MH32的初始化過程,請參考Network Install章節(jié),這里將不再贅述。
1 UPnP協(xié)議簡介
UPnP(Universal Plug and Play)協(xié)議是一種支持設備在局域網(wǎng)中實現(xiàn)自動發(fā)現(xiàn)和通信的網(wǎng)絡協(xié)議。其端口轉(zhuǎn)發(fā)功能由IGD Profile提供,允許局域網(wǎng)設備動態(tài)請求路由器為其開放指定的端口,以實現(xiàn)外部設備訪問內(nèi)部服務。這種功能消除了手動配置端口轉(zhuǎn)發(fā)的復雜性,特別適用于需要穿透NAT(網(wǎng)絡地址轉(zhuǎn)換)環(huán)境的應用場景。
IGD(Internet Gateway Device,互聯(lián)網(wǎng)網(wǎng)關設備)是UPnP(Universal Plug and Play)協(xié)議的一部分,主要用于管理網(wǎng)絡中的網(wǎng)關設備(如路由器)的服務和資源。IGD擴展定義了一套標準接口,允許局域網(wǎng)設備與網(wǎng)關設備通信,動態(tài)配置網(wǎng)絡設置,例如端口轉(zhuǎn)發(fā)、帶寬管理和連接狀態(tài)查詢等。
2 UPnP協(xié)議特點
自動化配置:無需用戶手動設置,減少了配置錯誤的風險。
動態(tài)靈活:端口映射規(guī)則可以根據(jù)需求動態(tài)添加或刪除。
設備友好:支持即插即用,簡化了設備的聯(lián)網(wǎng)和部署過程。
跨設備兼容:UPnP基于標準化協(xié)議,廣泛支持各種設備和平臺。
3 UPnP應用場景
通過UPnP端口轉(zhuǎn)發(fā)功能,我們可以使用W55MH32實現(xiàn)以下功能:
遠程訪問:將外部請求轉(zhuǎn)發(fā)到局域網(wǎng)設備(如NAS、監(jiān)控攝像頭),實現(xiàn)外部遠程訪問內(nèi)部設備。
遠程控制:外部設備通過UPnP轉(zhuǎn)換的端口,可以實現(xiàn)遠程控制局域網(wǎng)內(nèi)部設備(智能門鎖、燈光控制器)。
4 UPnP設置端口轉(zhuǎn)發(fā)的工作流程
設備發(fā)現(xiàn):W55MH32通過SSDP(Simple Service Discovery Protocol)向局域網(wǎng)中發(fā)送組播請求(HTTP M-SEARCH報文),搜索支持IGD的網(wǎng)關設備。
獲取服務描述:W55MH32訪問網(wǎng)關設備(路由器)獲取服務描述文件,了解支持的服務和接口。
訂閱IGD事件:通過事件訂閱,W55MH32可以在不主動輪詢的情況下,接收實時通知。
調(diào)用服務接口:使用UPnP的SOAT消息調(diào)用IGD提供的端口映射接口。
數(shù)據(jù)交互測試:外部通過訪問映射的端口及路由器地址和局域網(wǎng)內(nèi)部設備進行通信。
5報文講解
設備搜索
上文我們提到,設備搜索時使用SSDP協(xié)議,SSDP(Simple Service Discovery Protocol)是 UPnP協(xié)議中的關鍵協(xié)議,用于設備發(fā)現(xiàn)和服務發(fā)布。它通過HTTP over UDP的形式在局域網(wǎng)內(nèi)廣播和接收報文,采用多播地址 239.255.255.250和端口 1900。
SSDP報文主要分為以下幾類:
NOTIFY消息(設備主動廣播通知):用于設備向網(wǎng)絡通告自己的存在或離線狀態(tài)。
M-SEARCH消息(客戶端主動搜索):客戶端發(fā)送搜索請求以發(fā)現(xiàn)設備或服務。
HTTP/1.1響應消息(設備對 M-SEARCH的響應):設備對搜索請求的響應,提供設備描述文件的位置及服務信息。
SSDP報文基于HTTP協(xié)議,有固定的格式,主要包括以下字段:
HOST:目標地址和端口,固定為 239.255.255.250:1900。
MAN:用于標識搜索消息,固定為 "ssdp:discover"(僅在 M-SEARCH中使用)。
MX:最大響應時間,指定設備在多長時間內(nèi)響應(單位:秒)。
ST:搜索目標,標識要查找的設備類型或服務類型。
NT:通知類型,表示設備或服務的類型(在 NOTIFY消息中使用)。
USN:唯一服務名稱,設備或服務的唯一標識符。
LOCATION:設備描述文件的 URL,包含設備的詳細信息。
CACHE-CONTROL:設備信息的緩存時間,表示在多長時間內(nèi)有效。
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秒內(nèi)返回響應。
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:設備的操作系統(tǒng),UPnP版本和設備名稱。
ST:搜索目標類型,和請求中的ST字段一致。
USN:唯一設備標識符。
獲取設備標識符
這一步會通過HTTP GET方式去請求xml文件,有關HTTP GET報文以及HTTP響應報文這里不過多講解,有興趣的可以參考 HTTP Client章節(jié)。
請求示例:
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"??> 1/major?> 0/minor?> /specVersion?> urn:schemas-upnp-org:device:InternetGatewayDevice:1/deviceType?> http://192.168.100.1:80 /presentationURL?> Wireless N Router TL-WR886N/friendlyName?> TP-LINK/manufacturer?> http://www.tp-link.com.cn/manufacturerURL?> TL-WR886N 6.0/modelDescription?> TL-WR886N/modelName?> 6.0/modelNumber?> uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9/UDN?> 123456789001/UPC?> urn:schemas-upnp-org:service:Layer3Forwarding:1/serviceType?> urn:upnp-org:serviceId:L3Forwarding1/serviceId?> /l3f/controlURL?> /l3f/eventSubURL?> /l3f.xml/SCPDURL?> /service?> /serviceList?> urn:schemas-upnp-org:device:WANDevice:1/deviceType?> WAN Device/friendlyName?> TP-LINK/manufacturer?> http://www.tp-link.com.cn/manufacturerURL?> WAN Device/modelDescription?> WAN Device/modelName?> 1.0/modelNumber?> /modelURL?> 12345678900001/serialNumber?> uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9/UDN?> 123456789001/UPC?> urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1/serviceType?> urn:upnp-org:serviceId:WANCommonInterfaceConfig/serviceId?> /ifc/controlURL?> /ifc/eventSubURL?> /ifc.xml/SCPDURL?> /service?> /serviceList?> urn:schemas-upnp-org:device:WANConnectionDevice:1/deviceType?> WAN Connection Device/friendlyName?> TP-LINK/manufacturer?> http://www.tp-link.com.cn/manufacturerURL?> WAN Connection Device/modelDescription?> WAN Connection Device/modelName?> 1.0/modelNumber?> /modelURL?> 12345678900001/serialNumber?> uuid:8c15e41f-3d83-41c1-b35d-5D2A64377DE9/UDN?> 123456789001/UPC?> urn:schemas-upnp-org:service:WANIPConnection:1/serviceType?> urn:upnp-org:serviceId:WANIPConnection/serviceId?> /ipc/controlURL?> /ipc/eventSubURL?> /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: 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協(xié)議的內(nèi)部端口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"??> /NewRemoteHost?> 1000/NewExternalPort?> TCP/NewProtocol?> 8000/NewInternalPort?> 192.168.100.101/NewInternalClient?> 1/NewEnabled?> W5500_uPnPGetway/NewPortMappingDescription?> 0/NewLeaseDuration?> /m:AddPortMapping?> /SOAP-ENV:Body?> /SOAP-ENV:Envelope?>
主要字段的描述如下:
m:AddPortMapping:添加端口映射
NewExternalPort:外部端口號
NewProtocol:協(xié)議類型
NewInternalPort:內(nèi)部端口號
NewInternalClient:內(nèi)部地址
響應內(nèi)容:
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"??> /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"??> /NewRemoteHost?> 1000/NewExternalPort?> TCP/NewProtocol?> /m:DeletePortMapping?> /SOAP-ENV:Body?>/SOAP-ENV:Envelope?>
主要字段的描述如下:
m:DeletePortMapping:刪除端口映射
NewExternalPort:外部端口號
NewProtocol:協(xié)議類型
NewRemoteHost:外部訪問來源,可以為空
6實現(xiàn)過程
在這個例程中,我們實現(xiàn)了通過串口控制LED燈開關、獲取和設置網(wǎng)絡地址信息、TCP和UDP回環(huán)數(shù)據(jù)測試以及UPnP添加映射端口和刪除映射端口的功能。
注意:測試實例需要W55MH32接入在支持UPnP端口轉(zhuǎn)發(fā)的路由器下。
步驟1:設置以太網(wǎng)緩存大小
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的收發(fā)緩存分別設置為4KB,4KB,2KB,1KB,1KB,1KB,1KB,2KB。
其中socket0用于UPnP協(xié)議處理,socket1用于TCP和UDP回環(huán)處理,socket2用于監(jiān)聽IGD事件。
步驟2:LED控制函數(shù)注冊
UserLED_Control_Init(set_user_led_status);
set_user_led_status()函數(shù)為控制LED的函數(shù),具體內(nèi)容如下:
/*void set_user_led_status(uint8_t val) { if (val) { GPIO_SetBits(GPIOD, GPIO_Pin_14); } else { GPIO_ResetBits(GPIOD, GPIO_Pin_14); } }
步驟3:搜索UPnP設備
do { printf("Send SSDP.. rn"); } while (SSDPProcess(SOCKET_ID) != 0); // SSDP Search discovery
/**< SSDP Header */ unsigned char SSDP[] = " M-SEARCH * HTTP/1.1rn Host:239.255.255.250:1900rn ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1rn Man:"ssdp:discover"rn MX:3rn rn "; /** * @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("%srn", SSDP); #endif // Send SSDP if (sendto(sockfd, SSDP, strlen((char *)SSDP), mcast_addr, 1900) <= 0) printf("SSDP Send error!!!!!!!rn"); // Receive Reply memset(recv_buffer, '', 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("rnReceiveDatarn%srn", recv_buffer); #endif // Parse SSDP Message if ((ret_value = parseSSDP(recv_buffer)) == 0) UPnP_Step = 1; return ret_value; }
在這個函數(shù)中,主要是使用SSDP協(xié)議搜索IGD設備,發(fā)送報文和前面我們介紹的一致。
步驟4:獲取IGD設備描述
if (GetDescriptionProcess(SOCKET_ID) == 0) // GET IGD description { printf("GetDescription Success!!rn"); } else { printf("GetDescription Fail!!rn"); }
/** * @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, '', SEND_BUFFER_SIZE); MakeGETHeader(send_buffer); #ifdef UPNP_DEBUG printf("%srn", 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!!rn"); // Send Get Discription Message while ((getSn_SR(sockfd) != SOCK_ESTABLISHED)); send(sockfd, (void *)send_buffer, strlen(send_buffer)); // Receive Reply memset(recv_buffer, '', 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("rnReceiveDatarn%srn", recv_buffer); #endif // Parse Discription Message if ((ret_value = parseDescription(recv_buffer)) == 0) UPnP_Step = 2; return ret_value; }
請求報文通過MakeGETHeader()函數(shù)進行組包,具體報文如下:
/** * @brief This function makes the HTTP GET header. * @param dest:Target string pointer * @return none */ void MakeGETHeader(char *dest) { char local_port[6] = {''}; strcat(dest, "GET "); strcat(dest, descLOCATION); strcat(dest, " HTTP/1.1rn"); strcat(dest, "Accept: text/xml, application/xmlrn"); strcat(dest, "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn"); strcat(dest, "Host: "); strcat(dest, descIP); sprintf(local_port, ":%s", descPORT); strcat(dest, local_port); strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn"); }
然后將接收到的內(nèi)容,通過parseDescription()函數(shù)進行解析,如果設備描述中不支持WANIPConnection服務,則說明不支持端口映射,返回錯誤。
parseDescription()函數(shù)內(nèi)容如下:
/** * @brief This function parses the received description message from IGD(Internet Gateway Dev * @return 0: success, 1: received xml parse error */ signed char parseDescription( const char *xml /**< string for parse */ ) { const char controlURL_[] = ""; const char eventSubURL_[] = ""; char *URL_start = 0, *URL_end = 0; if (parseHTTP(xml) != 0) return 1; //printf("rn%srn", xml); // Find Control URL("/etc/linuxigd/gateconnSCPD.ctl") if ((URL_start = strstr(xml, "urn:schemas-upnp-org:service:WANIPConnection:1")) == NULL) retur if ((URL_start = strstr(URL_start, controlURL_)) == NULL) return 1; if ((URL_end = strstr(URL_start, "")) == 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) retur if ((URL_start = strstr(URL_start, eventSubURL_)) == NULL) return 1; if ((URL_end = strstr(URL_start, "")) == NULL) return 1; strncpy(eventSubURL, URL_start + strlen(eventSubURL_), URL_end - URL_start - strlen(eventSubUR return 0; }
步驟5:訂閱IGD事件
if (SetEventing(SOCKET_ID) == 0) // Subscribes IGD event messages { printf("SetEventing Success!!rn"); } else { printf("SetEventing Fail!!rn"); }
SetEventing()函數(shù)內(nèi)容如下:
/** * @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, '', SEND_BUFFER_SIZE); MakeSubscribe(send_buffer, PORT_UPNP_EVENTING); #ifdef UPNP_DEBUG printf("%srn", 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!!rn"); // Send Get Discription Message while ((getSn_SR(sockfd) != SOCK_ESTABLISHED)); send(sockfd, (void *)send_buffer, strlen(send_buffer)); // Receive Reply memset(recv_buffer, '', 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("rnReceiveDatarn%srn", recv_buffer); #endif return parseHTTP(recv_buffer); }
請求報文通過MakeSubscribe()函數(shù)進行組包,具體報文如下:
/** * @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] = {''}, ipaddr[16] = {''}; unsigned char ip[4]; strcat(dest, "SUBSCRIBE "); strcat(dest, eventSubURL); strcat(dest, " HTTP/1.1rn"); strcat(dest, "Host: "); strcat(dest, descIP); sprintf(local_port, ":%s", descPORT); strcat(dest, local_port); strcat(dest, "rnUSER-AGENT: Mozilla/4.0 (compatible; UPnP/1.1; Windows NT/5.1)rn"); strcat(dest, "CALLBACK: ", listen_port); strcat(dest, local_port); strcat(dest, "rnNT: upnp:eventrnTIMEOUT: Second-1800rnrn"); }
最后,通過parseHTTP()函數(shù)解析HTTP響應報文,判斷是否訂閱成功。
parseHTTP()函數(shù)如下:
/*-----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, "rn"); memset(content, '', CONT_BUFFER_SIZE); strncpy(content, xml, loc - xml); printf("rnHTTP Error:rn%srnrn", content); return 1; } }
步驟6:執(zhí)行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("rn====================== WIZnet Chip Control Point ===================rn"); printf("This Application is basic example of UART interface withrn"); printf("Windows Hyper Terminal. rn"); printf("rn==========================================================rn"); printf(" APPLICATION MENU :rn"); printf("rn==========================================================rnn"); printf(" 1 - Set LED on rn"); printf(" 2 - Set LED off rn"); printf(" 3 - Show network settingrn"); printf(" 4 - Set network settingrn"); printf(" 5 - Run TCP Loopbackrn"); printf(" 6 - Run UDP Loopbackrn"); printf(" 7 - UPnP PortForwarding: AddPortrn"); printf(" 8 - UPnP PortForwarding: DeletePortrn"); printf("Enter your choice : "); memset(choice, 0, sizeof(choice)); scanf("%s", choice); printf("%crn", choice[0]);
在這里會執(zhí)行一個用戶選項菜單,選項1和2控制LED開關,選項3和4打印和設置網(wǎng)絡地址信息,選項5運行一個TCP回環(huán)測試程序(回環(huán)測試程序可參考TCP Server章節(jié)),選項6運行一個UDP回環(huán)測試程序(回環(huán)測試程序可參考UDP章節(jié))。選項7添加一個UPnP端口映射表,選項8刪除一個UPnP端口映射表。這里我們主要講解UPnP相關的選項7和選項8。
步驟7:添加一個UPnP端口映射表
代碼如下:
if (choice[0] == '7') { bTreat = SET; printf("rnType a Protocol(TCP/UDP) : "); memset(msg, 0, sizeof(msg)); scanf("%s", msg); printf("%srn", msg); strncpy(protocol, msg, 3); protocol[3] = ''; printf("rnType a External Port Number : "); memset(msg, 0, sizeof(msg)); scanf("%s", msg); printf("%srn", msg); external_port = ATOI(msg, 10); printf("rnType a Internal Port Number : "); memset(msg, 0, sizeof(msg)); scanf("%s", msg); printf("%srn", msg); internal_port = ATOI(msg, 10); if(strcmp(protocol,"tcp") || 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!!rn"); else printf("AddPort Error Code is %drn", ret); }
在這里,我們需要外部輸入端口映射的協(xié)議類型(TCP或UDP),以及外部端口號和內(nèi)部端口號。輸入完成后,選項5或選項6的端口號會替換為輸入的內(nèi)部端口號,然后通過AddPortProcess()函數(shù)執(zhí)行添加端口映射處理。AddPortProcess()函數(shù)內(nèi)容如下:
/** * @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, '', CONT_BUFFER_SIZE); MakeSOAPAddControl(content, protocol, extertnal_port, internal_ip, internal_port, description); // Make HTTP POST Header memset(send_buffer, '', SEND_BUFFER_SIZE); len = strlen(content); MakePOSTHeader(send_buffer, len, ADD_PORT); strcat(send_buffer, content); //#ifdef UPNP_DEBUG printf("%srn", 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!!rn"); // Send "Delete Port" Message while (getSn_SR(sockfd) != SOCK_ESTABLISHED); send(sockfd, (void *)send_buffer, strlen(send_buffer)); // Receive Reply memset(recv_buffer, '', 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("rnReceiveDatarn%srn", recv_buffer); //#endif // Parse Replied Message return parseAddPort(recv_buffer); }
程序首先會通過MakeSOAPAddControl()函數(shù)組裝請求報文中的XML部分,具體內(nèi)容如下:
/**< SOAP header & tail */ const char soap_start[] = " ?xml version="1.0"??>rn "; const char soap_end[] = " /SOAP-ENV:Body?>/SOAP-ENV:Envelope?>rn "; /**< Delete Port Mapping */ const char DeletePortMapping_[] = ""; const char _DeletePortMapping[] = "/m:DeletePortMapping?>"; /**< New Remote Host */ const char NewRemoteHost_[] = ""; const char _NewRemoteHost[] = "/NewRemoteHost?>"; /**< New External Port */ const char NewExternalPort_[] = ""; const char _NewExternalPort[] = "/NewExternalPort?>"; /**< New Protocol */ const char NewProtocol_[] = ""; const char _NewProtocol[] = "/NewProtocol?>"; /**< Add Port Mapping */ const char AddPortMapping_[] = ""; const char _AddPortMapping[] = "/m:AddPortMapping?>"; /**< New Internal Port */ const char NewInternalPort_[] = ""; const char _NewInternalPort[] = "/NewInternalPort?>"; /**< New Internal Client */ const char NewInternalClient_[] = ""; const char _NewInternalClient[] = "/NewInternalClient?>"; /**< New Enabled */ const char NewEnabled[] = "1/NewEnabled?>"; const char NewEnabled_[] = ""; const char _NewEnabled[] = "/NewEnabled?>"; /**< New Port Mapping Description */ const char NewPortMappingDescription_[] = ""; const char _NewPortMappingDescription[] = "/NewPortMappingDescription?>"; /**< New Lease Duration */ const char NewLeaseDuration[] = "0/NewLeaseDuration?>"; const char NewLeaseDuration_[] = ""; 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] = {''}; 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()函數(shù)制作HTTP頭部內(nèi)容,具體內(nèi)容如下:
/** * @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] = {''}, local_port[6] = {''}; sprintf(local_length, "%d", content_length); strcat(dest, "POST "); strcat(dest, controlURL); strcat(dest, " HTTP/1.1rn"); strcat(dest, "Content-Type: text/xml; charset="utf-8"rn"); 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, "rnUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn"); strcat(dest, "Host: "); strcat(dest, descIP); sprintf(local_port, ":%s", descPORT); strcat(dest, local_port); strcat(dest, "rnContent-Length: "); strcat(dest, local_length); strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn"); }
最后則是發(fā)送請求,然后通過parseAddPort()函數(shù)解析響應內(nèi)容判斷是否添加端口映射成功。
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("rnType a Protocol(TCP/UDP) : "); memset(msg, 0, sizeof(msg)); scanf("%s", msg); printf("%srn", msg); //GetInputString(msg); strncpy(protocol, msg, 3); protocol[3] = ''; printf("rnType a External Port Number : "); // TCP_LISTEN_PORT=num; // UDP_LISTEN_PORT=num; // printf("%drn",TCP_LISTEN_PORT); memset(msg, 0, sizeof(msg)); scanf("%s", msg); printf("%srn", msg); external_port = ATOI(msg, 10); // Try to Delete Port Action if ((ret = DeletePortProcess(sn, protocol, external_port)) == 0) printf("DeletePort Success!!rn"); else printf("DeletePort Error Code is %drn", ret); } /* OTHERS CHOICE*/ if (bTreat == RESET) { printf(" wrong choice rn"); }
在這里,我們需要外部輸入刪除端口映射的協(xié)議類型(TCP或UDP),以及外部端口號。輸入完成后,通過DeletePortProcess()函數(shù)執(zhí)行添加端口映射處理。DeletePortProcess()函數(shù)內(nèi)容如下:
/** * @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, '', CONT_BUFFER_SIZE); MakeSOAPDeleteControl(content, protocol, extertnal_port); // Make HTTP POST Header memset(send_buffer, '', SEND_BUFFER_SIZE); len = strlen(content); MakePOSTHeader(send_buffer, len, DELETE_PORT); strcat(send_buffer, content); //#ifdef UPNP_DEBUG printf("%srn", 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!!rn"); // Send "Delete Port" Message while (getSn_SR(sockfd) != SOCK_ESTABLISHED); send(sockfd, (void *)send_buffer, strlen(send_buffer)); // Receive Reply memset(recv_buffer, '', 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("rnReceiveDatarn%srn", recv_buffer); //#endif // Parse Replied Message return parseDeletePort(recv_buffer); }
首先會通過MakeSOAPDeleteControl()函數(shù)組裝請求報文中的XML部分,具體內(nèi)容如下:
/** * @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] = {''}; 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()函數(shù)制作HTTP頭部內(nèi)容,具體內(nèi)容如下:
/** * @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] = {''}, local_port[6] = {''}; sprintf(local_length, "%d", content_length); strcat(dest, "POST "); strcat(dest, controlURL); strcat(dest, " HTTP/1.1rn"); strcat(dest, "Content-Type: text/xml; charset="utf-8"rn"); 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, "rnUser-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)rn"); strcat(dest, "Host: "); strcat(dest, descIP); sprintf(local_port, ":%s", descPORT); strcat(dest, local_port); strcat(dest, "rnContent-Length: "); strcat(dest, local_length); strcat(dest, "rnConnection: Keep-AlivernCache-Control: no-cachernPragma: no-cachernrn"); }
最后則是發(fā)送請求,然后通過parseDeletePort()函數(shù)解析響應內(nèi)容判斷是否添加端口映射成功。
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運行結(jié)果
燒錄例程運行后,首先進行了PHY鏈路檢測,然后是通過DHCP獲取網(wǎng)絡地址并打印網(wǎng)絡地址信息:
接下來是搜索IGD設備,搜索成功后會進行獲取設備描述以及設置訂閱IGD事件,全部成功后則進入主菜單。
接著,我們輸入7,添加一個TCP協(xié)議的端口映射,外部端口為12345,內(nèi)部端口為8000。
打開UPnP Wizard軟件,點擊刷新后可以看到我們添加的端口映射表。(UPnP Wizard下載鏈接:https://upnp-wizard.en.softonic.com/)
然后我們輸入5,打開TCP回環(huán)測試程序。
隨后,我們打開一個網(wǎng)絡調(diào)試助手,例如SocketTester,選擇為TCP Client模式,服務器地址為外部IP地址也就是192.168.1.135,端口號為外部端口號12345,點擊”Connect”連接后,可以看到成功連接到內(nèi)部的W55MH32上了。UDP也是同樣進行操作,這里不再演示。
接著我們輸入Q退出回環(huán)測試程序,然后輸入8,將之前添加的TCP協(xié)議的12345外部端口刪除。在UPnP Wizard上點擊刷新,可以看到已經(jīng)成功刪除,再次執(zhí)行回環(huán)測試程序,已經(jīng)無法連接上內(nèi)部的W55MH32上。
8總結(jié)
本文講解了如何在 W55MH32芯片上實現(xiàn) UPnP協(xié)議的端口轉(zhuǎn)發(fā)功能,通過實戰(zhàn)例程詳細展示了從設備搜索、獲取設備描述、訂閱事件到添加和刪除端口映射的完整流程,包括各步驟涉及的協(xié)議報文、函數(shù)實現(xiàn)和具體操作。文章還對 UPnP協(xié)議的簡介、特點、應用場景進行了分析,幫助讀者理解其在網(wǎng)絡設備互聯(lián)互通中的實際應用價值。
下一篇文章將聚焦 TFTP協(xié)議,解析其核心原理及在文件傳輸中的應用,同時講解如何在W55MH32上實現(xiàn) TFTP功能,敬請期待!
WIZnet是一家無晶圓廠半導體公司,成立于 1998年。產(chǎn)品包括互聯(lián)網(wǎng)處理器 iMCU?,它采用 TOE(TCP/IP卸載引擎)技術,基于獨特的專利全硬連線 TCP/IP。iMCU?面向各種應用中的嵌入式互聯(lián)網(wǎng)設備。
WIZnet在全球擁有 70多家分銷商,在香港、韓國、美國設有辦事處,提供技術支持和產(chǎn)品營銷。
香港辦事處管理的區(qū)域包括:澳大利亞、印度、土耳其、亞洲(韓國和日本除外)。
審核編輯 黃宇
-
以太網(wǎng)
+關注
關注
41文章
5684瀏覽量
176251 -
端口
+關注
關注
4文章
1048瀏覽量
32992 -
UPnP
+關注
關注
0文章
7瀏覽量
8506
發(fā)布評論請先 登錄
第二十六章 W55MH32?上位機搜索和配置示例

第二十三章 W55MH32 MQTT_OneNET示例

第十八章 W55MH32 FTP_Server示例

第十七章 W55MH32 ARP示例

第十六章 W55MH32 PING示例

第十五章 W55MH32 SNMP示例

第十四章 W55MH32 TFTP示例

第十二章 W55MH32 NetBIOS示例

第十一章 W55MH32 SMTP示例

第十章 W55MH32 SNTP示例

第九章 W55MH32 HTTP Server示例

第五章 W55MH32 UDP示例

第三章 W55MH32 TCP Client示例

第二章 W55MH32 DHCP示例

WIZnet W55MH32以太網(wǎng)單片機開發(fā)教程 第十一章 通用定時器(上篇)

評論