根据索尼的开发文档, 在使用wifi控制索尼相机的最开始, 需要发送ssdp设备查询消息,然后相机返回的相应的响应消息, 从响应消息中获得相机的设备描述文件(.xml格式)的url.
SSDP简介
SSDP:Simple Sever Discovery Protocol,简单服务发现协议,此协议为网络客户提供一种无需任何配置、管理和维护网络设备服务的机制。此协议采用基于通知和发现路由的多播发现方式实现。协议客户端在保留的多播地址:239.255.255.250:1900(IPV4)发现服务,(IPv6 是:FF0x::C)同时每个设备服务也在此地址上上监听服务发现请求。如果服务监听到的发现请求与此服务相匹配,此服务会使用单播方式响应。SSDP包的格式
设备查询消息格式:
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: seconds to delay response
ST: search target
参数说明:
M-SEARCH是http协议的扩展方法, 设备查询消息通过该方法发送出去.
HOST:设置为协议保留多播地址和端口, 必须是:239.255.255.250:1900(IPv4)或FF0x::C(IPv6)
MAN:设置协议查询的类型, 必须是:ssdp:discover
MX:设置设备响应最长等待时间, 设备响应在0和这个值之间随机选择响应延迟的值。这样可以为控制点响应平衡网络负载。
ST: 查询的目标, 它必须是下面的类型:
ssdp:all 搜索所有设备和服务
upnp:rootdevice 仅搜索网络中的根设备
uuid:device-UUID 查询UUID标识的设备
urn:schemas-upnp-org:device:device-Type:version 查询device-Type字段指定的设备类型,设备类型和版本由UPNP组织定义。
urn:schemas-upnp-org:service:service-Type:version 查询service-Type字段指定的服务类型,服务类型和版本由UPNP组织定义。
在索尼的开发文档中, 采用的就是最后一个service的定义.
在设备接收到查询请求并且查询类型(ST字段值)与此设备匹配时,设备必须向多播地址239.255.255.250:1900回应响应消息:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age = seconds until advertisement expires
DATE: when reponse was generated
EXT:
LOCATION: URL for UPnP description for root device
SERVER: OS/Version UPNP/1.0 product/version
ST: search target
USN: advertisement UUID
各HTTP协议头的含义简介:
CACHE-CONTROL max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
DATE 指定响应生成的时间
EXT 向控制点确认MAN头域已经被设备理解
LOCATION 包含根设备描述得URL地址
SERVER 饱含操作系统名,版本,产品名和产品版本信息
ST 内容和意义与查询请求的相应字段相同
USN 表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。
索尼的相机设备描述文件的url会放在LOCATION中, 再通过http GET就可以获取到设备描述符文件.
然而在实际操作中, 在我发送了M-SEARCH的请求后, 索尼相机并没有向239.255.255.250:1900的多播地址发送响应消息, 通过wireshark抓包后发现, 相机通过udp直接向主机发送了这个响应消息, 可是经过测试, 每次返回的udp消息端口应该都是随机的, 没有想到办法解决, 不过通过抓包发现, 相机会不断向多播地址发送设备通知消息, 主机通过去获取多播地址中的相机设备通知消息, 来得到了相机设备描述文件的url.
设备通知消息格式:
NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900CACHE-CONTROL: max-age = seconds until advertisement expires
LOCATION: URL for UPnP description for root device
NT: search target
NTS: ssdp:alive
USN: advertisement UUID
参数说明:
HOST 设置为协议保留多播地址和端口,必须是239.255.255.250:1900。
CACHE-CONTROL max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
LOCATION 包含根设备描述得URL地址
NT 在此消息中,NT头必须为服务的服务类型。
NTS 表示通知消息的子类型,必须为ssdp:alive
USN 表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。
从LOCATION中, 就获得了相机的设备描述文件的url.
遇到的问题及解决方法
首先是在使用从github上下载下来的用来发送和获取ssdp包的.c和.h文件的时候, 直接放到qt工程中是无法编译的, 因为使用的编译方法是不一样的, 必须加上形如:
extern "C" { #include "lssdp.h" }
才能够通过编译, 小技巧, 记下来.
其次一个问题是双网卡的问题, github下载下来的代码, 原先是通过调用ubuntu系统的socket.h的函数,将socket绑定好了以后, 让操作系统自动分配网卡地址, 并加入该地址的多播组, 如果只是单网卡的话, 这个做法是没有任何问题的, 但如果是多网卡的话, 就往往不能加入指定的网卡地址, 这个情况下, 修改加入多播组的参数, 来加入到指定的网卡多播组中去. 具体修改参数如下:
if(hasMultiNetworkCard){ struct in_addr aaaddr = {0}; aaaddr.s_addr=inet_addr(ip); struct ip_mreq imr = { .imr_multiaddr.s_addr = inet_addr(Global.ADDR_MULTICAST), .imr_interface.s_addr = aaaddr.s_addr }; if (setsockopt(lssdp->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) != 0) { lssdp_error("setsockopt IP_ADD_MEMBERSHIP failed: %s (%d)\n", strerror(errno), errno); goto end; } // set IP_ADD_MEMBERSHIP }else{ struct ip_mreq imr = { .imr_multiaddr.s_addr = inet_addr(Global.ADDR_MULTICAST), .imr_interface.s_addr = htonl(INADDR_ANY) }; if (setsockopt(lssdp->sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) != 0) { lssdp_error("setsockopt IP_ADD_MEMBERSHIP failed: %s (%d)\n", strerror(errno), errno); goto end; } }
注意看imr中的interface的ip地址的写入, 如果是写入INADDR_ANY的话, 就会由操作系统默认绑定一个网卡地址, 即不一定是你想要绑定的网卡地址, 因此,在多网卡的情况虾, 由外部手动写入想要绑定的ip地址, 加入该ip的多播组, 从而就能够正确的获取到多播消息. 否则的话, 由于绑定的是另一张网卡的ip地址, 本网卡的ip地址多播组的消息, 会被操作系统认为ip非法而丢弃掉, 从而应用程序不能正确的获取到.
至此, 就完成了索尼相机控制的第一步, 相机的发现, 获取到了相机的设备描述文件.
参考博客:
ssdp:
https://blog.csdn.net/swanabin/article/details/52024800
多网卡收不到udp组播的问题:
https://blog.csdn.net/little_water/article/details/52550514