【p2p、分布式,区块链笔记 UPNP】: 简单服务发现协议 SSDP

server/2024/10/23 17:13:39/
  • 在设备加入网络,UPnP发现协议允许设备向控制点广告它的服务。它使用向一个标准地址和端口多址传送发现消息来实现。控制点在此端口上侦听是否有新服务加入系统。为了通知所有设备,一个设备为每个其上的嵌入设备和服务发送一系列相应的发现消息。每个消息也包含它表征设备或服务的特定信息。

SSDP的数据内容格式

  • SSDP (Simple Service Discovery Protocol) 的数据内容格式与HTTP协议格式十分类似。SSDP是一种基于 HTTPU (HTTP over UDP) 的协议,通常用于 UPnP 设备发现。

  • 注:HTTP(HyperText Transfer Protocol)属于计算机网络中的应用层协议。而TCP/UDP是传输层协议。HTTP只是定义了一种信息的描述格式,并不一定要使用TCP进行传输。

  • SSDP是一种基于 UDP 的协议,所以它并不涉及到TCP协议中的三次握手(three-way handshake)来建立连接或者四次挥手(four-segment handshake)来关闭连接。

  • SSDP的操作主要包括发送发现请求(M-SEARCH)、响应发现请求(NOTIFY)以及注册服务(NOTIFY + LOCATION)等操作。它的工作机制主要是基于广播和多播消息,用于通告服务的存在以及查询网络上的服务。

特性广播 (Broadcast)多播 (Multicast)
目标主机网络中的所有主机特定的一组主机
地址范围255.255.255.255 (IPv4)224.0.0.0239.255.255.255 (IPv4)
传输范围局域网 (LAN)局域网 (LAN) 和广域网 (WAN)
网络负载高,因为所有设备都会接收低,因为只有需要的设备接收
使用场景ARP、DHCP 等简单网络服务视频流、股票行情、在线游戏等
跨网络传输不能跨路由器传播可以跨路由器传播(需配置)
带宽效率低,所有主机都接收高,只有订阅的主机接收
  • 在IPv4环境,当需要使用多播方式传送相关消息的时候,SSDP一般使用多播地址239.255.255.250和UDP端口号1900。

设备和控制点在SSDP中的角色

  • 设备(Device)
    • 被发现的设备,通常是嵌入式设备或网络设备。设备会周期性地发送NOTIFY消息来广播自己的存在。
    • 当收到M-SEARCH请求时,也会响应NOTIFY消息。
  • 控制点(Control Point)
    • 发起服务发现的客户端,通常是PC、手机等。
    • 控制点通过发送M-SEARCH请求来寻找特定的服务。
    • 接收到NOTIFY消息后,控制点就可以对设备进行控制或获取信息。

NOTIFY报文

  • 当设备通过DHCP或其他方式获取IP并加入网络后,会同构239.255.255.250:1900进行多播通告,控制点监听此端口以发现新的服务。
/*!* \brief Sends SSDP advertisements, replies and shutdown messages.** \return UPNP_E_SUCCESS if successful else appropriate error.*/
int AdvertiseAndReply(/* [in] -1 = Send shutdown, 0 = send reply, 1 = Send Advertisement. */int AdFlag,/* [in] Device handle. int  value  return by UpnpRegisterRootDevice、 UpnpRegisterRootDevice2、UpnpRegisterRootDevice3 or  UpnpRegisterRootDevice4*/UpnpDevice_Handle Hnd, /* [in] Search type for sending replies. */enum SsdpSearchType SearchType,/* [in] Destination address. */struct sockaddr *DestAddr,/* [in] Device type. */char *DeviceType,/* [in] Device UDN. <UDN>uuid:UUID</UDN>  */char *DeviceUDN,/* [in] Service type. */char *ServiceType,/* [in] Advertisement age. */int Exp);

ssdp:alive

2024-10-05 19:39:29 UPNP-SSDP-2: Thread:0x7F5331C176C0 [.upnp/src/ssdp/ssdp_server.c:878]: Start of received response ----------------------------------------------------
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
LOCATION: http://192.168.19.70:49152/tvdevicedesc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 3f8c3c84-830e-11ef-b9c9-a0de7ba280b6
NT: urn:schemas-upnp-org:service:tvpicture:1 # 服务类型    
NTS: ssdp:alive  #表示通知消息的子类型
SERVER: Linux/5.15.153.1-microsoft-standard-WSL2, UPnP/1.0, Portable SDK for UPnP devices/17.2.0
X-User-Agent: redsonic
USN: uuid:Upnp-TVEmulator-1_0-1234567890001::urn:schemas-upnp-org:service:tvpicture:1End of received response ------------------------------------------------------
From host 192.168.19.70
  • 各字段描述见UPnP-arch-DeviceArchitecture-v1.0.pdf的16页。

ssdp:byebye

NOTIFY * HTTP/1.1Host: 239.255.255.250:reservedSSDPportNT: someunique:idscheme3NTS: ssdp:byebyeUSN: someunique:idscheme3

SEARCH报文

  • 控制点向网络广播一个M-SEARCH请求(ssdp:discover)。请求中包含了目标的服务类型。

ssdp:discover

M-SEARCH * HTTP/1.1S: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6Host: 239.255.255.250:reservedSSDPportMan: "ssdp:discover"ST: ssdp:all  # 查询的目标MX: 3HTTP/1.1 200 OKS: uuid:ijklmnop-7dec-11d0-a765-00a0c91e6bf6Ext:Cache-Control: no-cache="Ext", max-age = 5000ST: ge:fridgeUSN: uuid:abcdefgh-7dec-11d0-a765-00a0c91e6bf6AL: <blender:ixl><http://foo/bar>

响应消息 (Response)

  • 当设备接收到 M-SEARCH 消息时,如果匹配,会发送一个响应消息来通知请求者设备或服务的存在。控制点接收到NOTIFY消息后,就可以根据设备提供的详细信息,与设备建立连接。
HTTP/1.1 200 OK # 表示成功响应
CACHE-CONTROL: max-age=1800 # 指定响应的有效时间,以秒为单位
DATE: Tue, 20 Oct 2024 10:24:00 GMT # 响应生成的时间戳
EXT:  # 扩展字段,保持空白
LOCATION: http://192.168.1.100:80/upnp/device.xml # 设备的 UPnP 描述文件的 URL 
SERVER: Linux/2.6 UPnP/1.0 MediaTomb/0.12 # 包含设备的操作系统、UPnP 版本以及产品信息
ST: urn:schemas-upnp-org:device:MediaServer:1 # 搜索目标,响应请求中指定的设备或服务类型。
USN: uuid:abcd1234-5678-90ab-cdef-1234567890ab::urn:schemas-upnp-org:device:MediaServer:1 #  唯一服务名称,通常由设备的 UUID 和服务类型组成

代码实现

ST SsdpSearchType

// upnp\src\inc\ssdplib.h#L66-L75
/*! Enumeration to define all different types of ssdp searches */
typedef enum SsdpSearchType
{/*! Unknown search command. */SSDP_SERROR = -1,SSDP_ALL,SSDP_ROOTDEVICE,SSDP_DEVICEUDN,SSDP_DEVICETYPE,SSDP_SERVICE
} SType;
// upnp\src\inc\ssdplib.h#L77-L84
#define BUFSIZE (size_t)2500
#define SSDP_IP "239.255.255.250"
#define SSDP_IPV6_LINKLOCAL "FF02::C"
#define SSDP_IPV6_SITELOCAL "FF05::C"
#define SSDP_PORT 1900
#define NUM_TRY 3
#define THREAD_LIMIT 50
#define COMMAND_LEN 300

AdvertiseAndReply函数

// pupnp-branch-1.14.x\upnp\src\ssdp\ssdp_server.c
/*!* \brief 发送 SSDP 广告、回复和关闭消息。** \return 如果成功返回 UPNP_E_SUCCESS,否则返回适当的错误码。*/
int AdvertiseAndReply(int AdFlag,           /* [in] -1 = 发送关闭消息,0 = 发送回复,1 = 发送广告。 */UpnpDevice_Handle Hnd,/* [in] 设备句柄。 */enum SsdpSearchType SearchType, /* [in] 发送回复的搜索类型。 */struct sockaddr *DestAddr, /* [in] 目标地址。 */char *DeviceType,    /* [in] 设备类型。 */char *DeviceUDN,     /* [in] 设备 UDN。 */char *ServiceType,   /* [in] 服务类型。 */int Exp              /* [in] 广告有效期。 */
)
{// 初始化了一些变量int retVal = UPNP_E_SUCCESS; long unsigned int i; long unsigned int j;	int defaultExp = DEFAULT_MAXAGE;	struct Handle_Info *SInfo = NULL;	char UDNstr[100];	char devType[100];	char servType[100];	IXML_NodeList *nodeList = NULL;	IXML_NodeList *tmpNodeList = NULL;	IXML_Node *tmpNode = NULL;	IXML_Node *tmpNode2 = NULL;	IXML_Node *textNode = NULL;	const DOMString tmpStr;	const DOMString dbgStr;	int NumCopy = 0;memset(UDNstr, 0, sizeof(UDNstr));	memset(devType, 0, sizeof(devType));	memset(servType, 0, sizeof(servType));UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Inside AdvertiseAndReply with AdFlag = %d\n",AdFlag);/* Use a read lock */HandleReadLock(); // 检查设备句柄的有效性。如果句柄无效,则返回错误。if (GetHandleInfo(Hnd, &SInfo) != HND_DEVICE) {retVal = UPNP_E_INVALID_HANDLE;		goto end_function;	}defaultExp = SInfo->MaxAge;/* parse the device list and send advertisements/replies */while (NumCopy == 0 || (AdFlag && NumCopy < NUM_SSDP_COPY)) {// 资源解析,一般如果发生错误,continue跳过后续步骤,进入下一次循环过程if (NumCopy != 0)imillisleep(SSDP_PAUSE);NumCopy++;for (i = 0lu;; i++) {UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Entering new device list with i = %lu\n\n",	i);tmpNode = ixmlNodeList_item(SInfo->DeviceList, i);if (!tmpNode) {UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Exiting new device list with i = "       "%lu\n\n",i);break;}dbgStr = ixmlNode_getNodeName(tmpNode);UpnpPrintf(UPNP_INFO,API,__FILE__,	__LINE__,"Extracting device type once for %s\n",dbgStr);ixmlNodeList_free(nodeList);nodeList = ixmlElement_getElementsByTagName((IXML_Element *)tmpNode, "deviceType");if (!nodeList) continue;UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Extracting UDN for %s\n",dbgStr);UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Extracting device type\n");tmpNode2 = ixmlNodeList_item(nodeList, 0lu);if (!tmpNode2) continue;textNode = ixmlNode_getFirstChild(tmpNode2);if (!textNode) continue;UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Extracting device type \n");tmpStr = ixmlNode_getNodeValue(textNode);if (!tmpStr) continue;strncpy(devType, tmpStr, sizeof(devType) - 1);UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Extracting device type = %s\n",				devType);if (!tmpNode) { UpnpPrintf(UPNP_ALL,	API,__FILE__,	__LINE__,"TempNode is NULL\n");	}dbgStr = ixmlNode_getNodeName(tmpNode);UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Extracting UDN for %s\n",dbgStr);ixmlNodeList_free(nodeList);nodeList = ixmlElement_getElementsByTagName((IXML_Element *)tmpNode, "UDN");if (!nodeList) {UpnpPrintf(UPNP_CRITICAL,API,__FILE__,	__LINE__,"UDN not found!\n");continue;}tmpNode2 = ixmlNodeList_item(nodeList, 0lu);if (!tmpNode2) {UpnpPrintf(UPNP_CRITICAL,API,__FILE__,	__LINE__,"UDN not found!\n");continue;}textNode = ixmlNode_getFirstChild(tmpNode2);if (!textNode) {UpnpPrintf(UPNP_CRITICAL,API,__FILE__,__LINE__,"UDN not found!\n");continue;}tmpStr = ixmlNode_getNodeValue(textNode);if (!tmpStr) {UpnpPrintf(UPNP_CRITICAL,API,__FILE__,__LINE__,"UDN not found!\n");	continue;}strncpy(UDNstr, tmpStr, sizeof(UDNstr) - 1);UpnpPrintf(UPNP_INFO,API,__FILE__,__LINE__,"Sending UDNStr = %s \n",UDNstr);if (AdFlag) {/* send the device advertisement */if (AdFlag == 1) {DeviceAdvertisement(devType,i == 0lu,UDNstr,SInfo->DescURL,	Exp,SInfo->DeviceAf,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else {/* AdFlag == -1 */DeviceShutdown(devType,	i == 0lu,UDNstr,	SInfo->DescURL,	Exp,SInfo->DeviceAf,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);}} else {switch (SearchType) {case SSDP_ALL:DeviceReply(DestAddr,devType,i == 0lu,UDNstr,SInfo->DescURL,defaultExp,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);break;case SSDP_ROOTDEVICE:if (i == 0lu) {SendReply(DestAddr,devType,1,UDNstr,SInfo->DescURL,defaultExp,0,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);}break;case SSDP_DEVICEUDN: {/* clang-format off */if (DeviceUDN && strlen(DeviceUDN) != (size_t)0) {if (strcasecmp(DeviceUDN, UDNstr)) {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceUDN=%s and search UDN=%s DID NOT match\n",UDNstr, DeviceUDN);} else {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceUDN=%s and search UDN=%s MATCH\n",UDNstr, DeviceUDN);SendReply(DestAddr, devType, 0, UDNstr, SInfo->DescURL, defaultExp, 0,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);}   }/* clang-format on */ break;}case SSDP_DEVICETYPE: {/* clang-format off */if (!strncasecmp(DeviceType, devType, strlen(DeviceType) - (size_t)2)) {if (atoi(strrchr(DeviceType, ':') + 1) < atoi(&devType[strlen(devType) - (size_t)1])) {// /*请求的版本低于设备版本。设备版本必须使用较低的版本号和较低的描述URL进行回复*//*UpnpPrintf+SendReply*/UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceType=%s and search devType=%s MATCH\n",devType, DeviceType);SendReply(DestAddr, DeviceType, 0, UDNstr, SInfo->LowerDescURL,defaultExp, 1,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else if (atoi(strrchr(DeviceType, ':') + 1)== atoi(&devType[strlen(devType) - (size_t)1])) {/*UpnpPrintf+SendReply*/UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceType=%s and search devType=%s MATCH\n",devType, DeviceType);SendReply(DestAddr, DeviceType, 0, UDNstr, SInfo->DescURL,defaultExp, 1,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceType=%s and search devType=%s DID NOT MATCH\n",devType, DeviceType);}} else { UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"DeviceType=%s and search devType=%s DID NOT MATCH\n",devType, DeviceType);}/* clang-format on */break;}default:break;}}/* send service advertisements for services* corresponding to the same device */UpnpPrintf(UPNP_INFO,API,__FILE__,__LINE__,"Sending service Advertisement\n");/* Correct service traversal such that each device's* serviceList is directly traversed as a child of its* parent device. This ensures that the service's alive* message uses the UDN of the parent device. */tmpNode = ixmlNode_getFirstChild(tmpNode);while (tmpNode) {dbgStr = ixmlNode_getNodeName(tmpNode);if (!strncmp(dbgStr,SERVICELIST_STR,sizeof SERVICELIST_STR)) {break;}tmpNode = ixmlNode_getNextSibling(tmpNode);}ixmlNodeList_free(nodeList);if (!tmpNode) {nodeList = NULL;continue;}nodeList = ixmlElement_getElementsByTagName((IXML_Element *)tmpNode, "service");if (!nodeList) {UpnpPrintf(UPNP_INFO,API,__FILE__,__LINE__,"Service not found 3\n");continue;}for (j = 0lu;; j++) {tmpNode = ixmlNodeList_item(nodeList, j);if (!tmpNode) {break;}ixmlNodeList_free(tmpNodeList);tmpNodeList = ixmlElement_getElementsByTagName((IXML_Element *)tmpNode, "serviceType");if (!tmpNodeList) {UpnpPrintf(UPNP_CRITICAL,API,__FILE__,__LINE__,"ServiceType not found \n");continue;}tmpNode2 = ixmlNodeList_item(tmpNodeList, 0lu);if (!tmpNode2)continue;textNode = ixmlNode_getFirstChild(tmpNode2);if (!textNode)continue;/* servType is of format* Servicetype:ServiceVersion */tmpStr = ixmlNode_getNodeValue(textNode);if (!tmpStr)continue;strncpy(servType, tmpStr, sizeof(servType) - 1);UpnpPrintf(UPNP_INFO,API,__FILE__,__LINE__,"ServiceType = %s\n",servType);if (AdFlag) {if (AdFlag == 1) {ServiceAdvertisement(UDNstr,servType,SInfo->DescURL,Exp,SInfo->DeviceAf,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else {/* AdFlag == -1 */ServiceShutdown(UDNstr,servType,SInfo->DescURL,Exp,SInfo->DeviceAf,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);}} else {switch (SearchType) {case SSDP_ALL:ServiceReply(DestAddr,servType,UDNstr,SInfo->DescURL,defaultExp,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);break;case SSDP_SERVICE:/* clang-format off */if (ServiceType) {if (!strncasecmp(ServiceType, servType, strlen(ServiceType) - (size_t)2)) {if (atoi(strrchr(ServiceType, ':') + 1) <atoi(&servType[strlen(servType) - (size_t)1])) {/* the requested version is lower than the service version* must reply with the lower version number and the lower* description URL */UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"ServiceType=%s and search servType=%s MATCH\n",ServiceType, servType);SendReply(DestAddr, ServiceType, 0, UDNstr, SInfo->LowerDescURL,defaultExp, 1,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else if (atoi(strrchr (ServiceType, ':') + 1) ==atoi(&servType[strlen(servType) - (size_t)1])) {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"ServiceType=%s and search servType=%s MATCH\n",ServiceType, servType);SendReply(DestAddr, ServiceType, 0, UDNstr, SInfo->DescURL,defaultExp, 1,SInfo->PowerState,SInfo->SleepPeriod,SInfo->RegistrationState);} else {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"ServiceType=%s and search servType=%s DID NOT MATCH\n",ServiceType, servType);}} else {UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__,"ServiceType=%s and search servType=%s DID NOT MATCH\n",ServiceType, servType);}}/* clang-format on */break;default:break;}}}ixmlNodeList_free(tmpNodeList);tmpNodeList = NULL;ixmlNodeList_free(nodeList);nodeList = NULL;}}end_function:ixmlNodeList_free(tmpNodeList);ixmlNodeList_free(nodeList);UpnpPrintf(UPNP_ALL,API,__FILE__,__LINE__,"Exiting AdvertiseAndReply.\n");HandleUnlock();return retVal;
}

DeviceAdvertisement 根据输入参数创建设备广告请求

/*!* \brief 根据输入参数创建设备广告请求,并将其发送到多播通道。* \return 如果成功返回 UPNP_E_SUCCESS,否则返回适当的错误码。*/
int DeviceAdvertisement(char *DevType,/* [in] 设备的类型。 */int RootDev,/* [in] 标志,指示设备是否为根设备。 */char *Udn, /* [in] UDN(唯一设备名)。 */char *Location, /* [in] 位置 URL。 */int Duration,/* [in] 服务持续时间(秒)。 */int AddressFamily,/* [in] 设备地址族。 */int PowerState,/* [in] 根据 UPnP 低功耗标准定义的电源状态。 */int SleepPeriod,/* [in] 根据 UPnP 低功耗标准定义的睡眠周期。 */int RegistrationState /* [in] 根据 UPnP 低功耗标准定义的注册状态。 */);
{struct sockaddr_storage __ss;struct sockaddr_in *DestAddr4 = (struct sockaddr_in *)&__ss;struct sockaddr_in6 *DestAddr6 = (struct sockaddr_in6 *)&__ss;/* char Mil_Nt[LINE_SIZE] */char Mil_Usn[LINE_SIZE];char *msgs[3]; int ret_code = UPNP_E_OUTOF_MEMORY;	int rc = 0;UpnpPrintf(UPNP_INFO,SSDP,__FILE__,__LINE__,"In function DeviceAdvertisement\n");memset(&__ss, 0, sizeof(__ss));switch (AddressFamily) {case AF_INET:DestAddr4->sin_family = (sa_family_t)AF_INET;inet_pton(AF_INET, SSDP_IP, &DestAddr4->sin_addr);DestAddr4->sin_port = htons(SSDP_PORT);break;case AF_INET6:DestAddr6->sin6_family = (sa_family_t)AF_INET6;	inet_pton(AF_INET6,	 (isUrlV6UlaGua(Location)) ? SSDP_IPV6_SITELOCAL : SSDP_IPV6_LINKLOCAL, 	&DestAddr6->sin6_addr);DestAddr6->sin6_port = htons(SSDP_PORT); DestAddr6->sin6_scope_id = gIF_INDEX;break;default:UpnpPrintf(UPNP_CRITICAL,SSDP,__FILE__,__LINE__,"Invalid device address family.\n");}msgs[0] = NULL;	msgs[1] = NULL;	msgs[2] = NULL;/* If deviceis a root device , here we need to send 3 advertisement or reply */if (RootDev) {// 构建 USN 字符串:如果是根设备 (RootDev 为真),则构建一个特殊的 USN(Unique Service Name),格式为 uuid::upnp:rootdevice。rc = snprintf(Mil_Usn, sizeof(Mil_Usn), "%s::upnp:rootdevice", Udn);if (rc < 0 || (unsigned int)rc >= sizeof(Mil_Usn))goto error_handler; //如果是根设备,那么会发送三条消息。CreateServicePacket(MSGTYPE_ADVERTISEMENT,"upnp:rootdevice",Mil_Usn,Location,Duration,&msgs[0],AddressFamily,PowerState,SleepPeriod,RegistrationState);}/* 创建消息  : both root and sub-devices need to send these two messages 如果是子设备,那么会发送两条消息。*/CreateServicePacket(MSGTYPE_ADVERTISEMENT,Udn,Udn,Location,Duration,&msgs[1],AddressFamily,PowerState,SleepPeriod,RegistrationState);rc = snprintf(Mil_Usn, sizeof(Mil_Usn), "%s::%s", Udn, DevType);if (rc < 0 || (unsigned int)rc >= sizeof(Mil_Usn))goto error_handler;CreateServicePacket(MSGTYPE_ADVERTISEMENT,DevType,Mil_Usn,Location,Duration,&msgs[2],AddressFamily,PowerState,SleepPeriod,RegistrationState);/* check error */if ((RootDev && msgs[0] == NULL) || msgs[1] == NULL ||msgs[2] == NULL) {goto error_handler;}/* send packets */if (RootDev) { /* send 3 msg types */ret_code = NewRequestHandler((struct sockaddr *)&__ss, 3, &msgs[0]);} else { /* sub-device *//* send 2 msg types */ret_code = NewRequestHandler((struct sockaddr *)&__ss, 2, &msgs[1]);}
error_handler:/* free msgs */free(msgs[0]);free(msgs[1]);free(msgs[2]);return ret_code;
}

CreateServicePacket

/*!* \brief 创建一个 HTTP 请求包。根据输入参数,创建服务广告请求或服务关闭请求等。  静态函数,只能在此源文件内被调用。*/
static void CreateServicePacket(int msg_type,          /*!< [in] 消息类型 (回复、广告或关闭) */const char *nt,        /*!< [in] SSDP 类型 */char *usn,             /*!< [in] 唯一服务名称 (进入 HTTP 头部) */char *location,        /*!< [in] 位置 URL */int duration,          /*!< [in] 服务持续时间 (秒) */char **packet,         /*!< [out] 输出缓冲区填充 HTTP 语句 */int AddressFamily,     /*!< [in] HTTP 请求的地址族 */int PowerState,        /*!< [in] 根据 UPnP 低功耗标准定义的电源状态 */int SleepPeriod,       /*!< [in] 根据 UPnP 低功耗标准定义的睡眠周期 */int RegistrationState  /*!< [in] 根据 UPnP 低功耗标准定义的注册状态 */
) {// 主要调用 http_MakeMessage 函数创建
}

NewRequestHandler:HTTP 请求发送到一多播通道

  • 主要调用socket创建套接字,setsockopt配置套接字, sendto函数发送数据包。
/*!* \brief Works as a request handler which passes the HTTP request string* to multicast channel.** \return UPNP_E_SUCCESS if successful else appropriate error.*/
static int NewRequestHandler(/*! [in] Ip address, to send the reply. */struct sockaddr *DestAddr,/*! [in] Number of packet to be sent. */int NumPacket,/*! [in] . */char **RqPacket)
{char errorBuffer[ERROR_BUFFER_LEN];SOCKET ReplySock;socklen_t socklen = sizeof(struct sockaddr_storage);int Index;struct in_addr replyAddr;/* a/c to UPNP Spec */int ttl = 4;#ifdef UPNP_ENABLE_IPV6int hops = 1;#endifchar buf_ntop[INET6_ADDRSTRLEN];int ret = UPNP_E_SUCCESS;if (strlen(gIF_IPV4) > (size_t)0 &&!inet_pton(AF_INET, gIF_IPV4, &replyAddr)) {return UPNP_E_INVALID_PARAM;}ReplySock = socket((int)DestAddr->sa_family, SOCK_DGRAM, 0);if (ReplySock == INVALID_SOCKET) {strerror_r(errno, errorBuffer, ERROR_BUFFER_LEN);UpnpPrintf(UPNP_INFO,SSDP,__FILE__,__LINE__,"SSDP_LIB: New Request Handler:" "Error in socket(): %s\n",errorBuffer);return UPNP_E_OUTOF_SOCKET;}switch (DestAddr->sa_family) {case AF_INET:inet_ntop(AF_INET,&((struct sockaddr_in *)DestAddr)->sin_addr,buf_ntop,sizeof(buf_ntop));setsockopt(ReplySock,IPPROTO_IP,	IP_MULTICAST_IF,(char *)&replyAddr,	sizeof(replyAddr));setsockopt(ReplySock,IPPROTO_IP,IP_MULTICAST_TTL,(char *)&ttl,sizeof(int));socklen = sizeof(struct sockaddr_in);break;#ifdef UPNP_ENABLE_IPV6case AF_INET6:inet_ntop(AF_INET6,	&((struct sockaddr_in6 *)DestAddr)->sin6_addr,buf_ntop,sizeof(buf_ntop));setsockopt(ReplySock,IPPROTO_IPV6,IPV6_MULTICAST_IF,(char *)&gIF_INDEX,	sizeof(gIF_INDEX));setsockopt(ReplySock,IPPROTO_IPV6,IPV6_MULTICAST_HOPS,(char *)&hops,sizeof(hops));break;#endifdefault:UpnpPrintf(UPNP_CRITICAL,SSDP,__FILE__,__LINE__,"Invalid destination address specified.");ret = UPNP_E_NETWORK_ERROR;goto end_NewRequestHandler;}//  发送数据包for (Index = 0; Index < NumPacket; Index++) {ssize_t rc;UpnpPrintf(UPNP_INFO,SSDP,__FILE__,__LINE__,">>> SSDP SEND to %s >>>\n%s\n",buf_ntop,*(RqPacket + Index));rc = sendto(ReplySock,*(RqPacket + Index),strlen(*(RqPacket + Index)),0,DestAddr,socklen);if (rc == -1) {strerror_r(errno, errorBuffer, ERROR_BUFFER_LEN);UpnpPrintf(UPNP_INFO,SSDP,__FILE__,__LINE__,"SSDP_LIB: New Request Handler:""Error in socket(): %s\n",errorBuffer);ret = UPNP_E_SOCKET_WRITE;goto end_NewRequestHandler;}}end_NewRequestHandler:UpnpCloseSocket(ReplySock);return ret;
}

在这里插入图片描述

CG

  • https://datatracker.ietf.org/doc/html/draft-cai-ssdp-v1-03
  • Windows 10 : How to Start or Stop SSDP Discovery Service
  • https://github.com/zlargon/lssdp

http://www.ppmy.cn/server/134213.html

相关文章

【Hive】4-HiveSQL 数据操控语言(DML)

HiveSQL 数据操控语言&#xff08;DML&#xff09; load加载数据 语法 -- 语法规则 LOAD DATA [LOCAL] INPATH filepath [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1val1, partcol2val2...)] -- 语法规则&#xff08;Hive 3.0及以后&#xff09; LOAD DATA [LOC…

中级职称和副高级职称,退休工资一年相差多少?

2014年&#xff0c;社会养老保险政策进行了统一并轨&#xff0c;但设立了10年的过渡期。过渡期结束后&#xff0c;每名事业单位员工都必须缴纳养老保险&#xff0c;原则上是“多缴多得&#xff0c;长缴长待&#xff0c;不缴不得”。 那么&#xff0c;事业单位副高和中级职称退…

后台管理员登录实现--系统篇

我的小系统后台原来就有一个上传图片的功能还夹带个删除图片的功能&#xff0c;还嵌到了一个菜单里面。之前效果如下 那么现在为了加大安全力度&#xff0c;想增加一个登录页面。通过登录再到这个页面。看着貌似很简单&#xff0c;但是听我细细说来&#xff0c;要新增些什么东西…

Idea 2023.2.7构建SpringCloud多模块项目

Idea 2023.2.7构建SpringCloud多模块项目 本文介绍如何使用idea 2023.2.7构建基于SpringCloud alibaba微服务项目&#xff0c;基于Nacos注册中心。 环境准备&#xff1a; JDK版本&#xff1a;jdk17 SpringBoot:3.3.4 SpringCloud:2023.0.3 Nacos服务端:2.4.3 1、创建父工程&a…

1971. 寻找图中是否存在路径

有一个具有 n 个顶点的 双向 图&#xff0c;其中每个顶点标记从 0 到 n - 1&#xff08;包含 0 和 n - 1&#xff09;。图中的边用一个二维整数数组 edges 表示&#xff0c;其中 edges[i] [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接&#x…

Unity/C#使用EPPlus读取和写入Excel

简介&#xff1a;本篇使用EPPlus来将数据写入Excel&#xff0c;如果需要使用NPOI那可以阅读我之前文档使用NPOI创建及写入数据_npoi 模板 写数据-CSDN博客 一、安装EPPlus 这里使用 .unitypackage 文件形式安装 1.1下载NuGetForUnity.unitypackage github进行搜索下载 下载…

windows 导出 oracle DMP文件

1.dba登录oracle sqlplus /orcl as sysdba 2.创建目录 授权目录 create directory bluesys1016 as C:\bluesys\DemoData; grant read,write on directory bluesys1016 to bluesys; 3.退出sqlplus exit 4.执行expdp expdp bluesys/bluesysorcl directorybluesys1016 dumpfil…

HarmonyOS Next应用开发——图像PixelMap压缩保存

【高心星出品】 图片编码保存 图片编码指将PixelMap编码成不同格式的存档图片&#xff0c;当前支持打包为JPEG、WebP、png和 HEIF(不同硬件设备支持情况不同) 格式&#xff0c;用于后续处理&#xff0c;如保存、传输等。图片编码是图片解码-图片处理-图片保存的最后环节&…