转:minidlna源码(1)(怕找不到,转一下)

前言

minidlna是一种优秀的DLNA解决方案。本文将涉及minidlna的upnp以及目录管理的代码。minidlna的下载链接如下:

wget http://netcologne.dl.sourceforge.net/project/minidlna/minidlna/1.1.0/minidlna-1.1.0.tar.gz

控制点使用VLC Media Player,下载链接如下:

http://www.videolan.org/vlc/index.zh.html#download

关于minidlna的配置,网上已有很多介绍,在这里就不复述了。

本文中一些关于UPNP的理论问题参考了IBM的相关介绍:

UPnP协议编程实践(1)

UPnP协议编程实践(2)


正文

在minidlna,本文描述的主要内容分布在minidlna.c(主程序),inotify.c(目录管理),upnphttp.c(upnp通信),minissdp.c(ssdp设备发现相关),upnpsoap.c(soap设备控制相关)等。

照例从main函数进入,这个在~/minidlna.c下。程序首先执行了init,open_db等方法:


 
 
  1. ret = init(argc, argv); //这里主要分析配置文件以及命令中的选项
  2. //......
  3. LIST_INIT(&upnphttphead); //初始化upnphttphead
  4. ret = open_db( NULL); //新建sqlite3 db
  5. //......
  6. check_db(db, ret, &scanner_pid);

新建连接用socket:


 
 
  1. sudp = OpenAndConfSSDPReceiveSocket(); //新建一个socket,执行setsockopt并且bind之, sudp就是返回的socket , 端口号SSDP_PORT(1900), 用于接受控制点信息
  2. if (sudp < 0)
  3. {
  4. DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n");
  5. if (SubmitServicesToMiniSSDPD(lan_addr[ 0].str, runtime_vars.port) < 0)
  6. DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING");
  7. }
  8. /* open socket for HTTP connections. Listen on the 1st LAN address */
  9. shttpl = OpenAndConfHTTPSocket(runtime_vars.port); //新建一个socket,执行setsockopt并且bind之, shttpl就是返回的socket , 端口号runtime_vars.port = 8200 , 它来自minidlna.conf
  10. if (shttpl < 0)
  11. DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n");
  12. DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port);
  13. /* open socket for sending notifications */
  14. if (OpenAndConfSSDPNotifySockets(snotify) < 0) //初始化n_lan_addr个广播用socket
  15. DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending SSDP notify "
  16. "messages. EXITING\n");
进入一个标准的select模型:

 
 
  1. while (!quitting) //init quitting = 0
  2. {
  3. /* Check if we need to send SSDP NOTIFY messages and do it if
  4. * needed */
  5. if (gettimeofday(&timeofday, 0) < 0)
  6. {
  7. DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno));
  8. timeout.tv_sec = runtime_vars.notify_interval;
  9. timeout.tv_usec = 0;
  10. }
  11. else
  12. {
  13. /* the comparison is not very precise but who cares ? */
  14. if (timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) //如果超时
  15. {
  16. SendSSDPNotifies2(snotify,
  17. ( unsigned short)runtime_vars.port,
  18. (runtime_vars.notify_interval << 1)+ 10); //心跳广播ssdp:alive消息,通知其他接入点自己就绪
  19. memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval));
  20. timeout.tv_sec = runtime_vars.notify_interval;
  21. timeout.tv_usec = 0;
  22. }
  23. else
  24. {
  25. timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval
  26. - timeofday.tv_sec;
  27. if (timeofday.tv_usec > lastnotifytime.tv_usec)
  28. {
  29. timeout.tv_usec = 1000000 + lastnotifytime.tv_usec
  30. - timeofday.tv_usec;
  31. timeout.tv_sec--;
  32. }
  33. else
  34. timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec;
  35. //..............
  36. FD_ZERO(&readset);
  37. if (sudp >= 0)
  38. {
  39. FD_SET(sudp, &readset); //将sudp加入readset
  40. max_fd = MAX(max_fd, sudp);
  41. }
  42. if (shttpl >= 0)
  43. {
  44. FD_SET(shttpl, &readset); //将shttpl加入readset
  45. max_fd = MAX(max_fd, shttpl);
  46. }
  47. //......
  48. i = 0; /* active HTTP connections count */
  49. // struct upnphttp *e
  50. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  51. {
  52. if ((e->socket >= 0) && (e->state <= 2))
  53. {
  54. FD_SET(e->socket, &readset); //添加记录的socket进入readset
  55. max_fd = MAX(max_fd, e->socket);
  56. i++;
  57. }
  58. }
  59. //.......
  60. FD_ZERO(&writeset);
  61. upnpevents_selectfds(&readset, &writeset, &max_fd);
  62. ret = select(max_fd+ 1, &readset, &writeset, 0, &timeout);
  63. if (ret < 0)
  64. {
  65. if(quitting) goto shutdown;
  66. if(errno == EINTR) continue;
  67. DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno));
  68. DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n");
  69. }
  70. upnpevents_processfds(&readset, &writeset);
  71. /* process SSDP packets */
  72. if (sudp >= 0 && FD_ISSET(sudp, &readset))
  73. {
  74. /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/
  75. ProcessSSDPRequest(sudp, ( unsigned short)runtime_vars.port); //接受控制点传来的ssdp信息,并回传给控制点设备描述信息
  76. }
  77. //......
  78. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  79. {
  80. if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset)))
  81. {
  82. Process_upnphttp(e); //这里会回送消息给控制点( 设备信息xml或远程目录信息等), port:8200
  83. }
  84. }
  85. /* process incoming HTTP connections */
  86. if (shttpl >= 0 && FD_ISSET(shttpl, &readset))
  87. {
  88. int shttp;
  89. socklen_t clientnamelen;
  90. struct sockaddr_in clientname;
  91. clientnamelen = sizeof(struct sockaddr_in);
  92. shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); //获取远程socket shttp
  93. if (shttp< 0)
  94. {
  95. DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno));
  96. }
  97. else
  98. {
  99. struct upnphttp * tmp = 0;
  100. DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n",
  101. inet_ntoa(clientname.sin_addr),
  102. ntohs(clientname.sin_port) );
  103. /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) {
  104. DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n");
  105. }*/
  106. /* Create a new upnphttp object and add it to
  107. * the active upnphttp object list */
  108. tmp = New_upnphttp(shttp); //初始化 struct upnphttp ,并且将shttp赋予其socket字段
  109. if (tmp)
  110. {
  111. tmp->clientaddr = clientname.sin_addr;
  112. LIST_INSERT_HEAD(&upnphttphead, tmp, entries); //将tmp插入链表upnphttphead中
  113. }
  114. else
  115. {
  116. DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n");
  117. close(shttp);
  118. }
  119. }
  120. }
  121. //......
  122. }
设备发现是UPnP网络实现的第一步。在这里,minidlna启动后,本机作为一个设备加入到网络中,设备发现过程允许设备向网络上的控制点告知它提供的服务(ssdp:alive)。当一个控制点加入到网络中时,设备发现过程允许控制点寻找网络上感兴趣的设备(ssdp:discover)。在这两种情况下,基本的交换信息就是发现消息。发现消息包括设备的一些特定信息或者某项服务的信息,例如它的类型、标识符、和指向XML设备描述文档的指针。简单发现协议(SSDP)定义了在网络中发现网络服务,控制点定位网络上相关资源和设备在网络上声明其可用性的方法。

在上面的select模型中,程序通过定时执行SendSSDPNotifies2方法,广播设备就绪消息(心跳包),它的实现如下:


 
 
  1. void
  2. SendSSDPNotifies2 (int *sockets,
  3. unsigned short port,
  4. unsigned int lifetime)
  5. {
  6. int i;
  7. DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n");
  8. for (i = 0; i < n_lan_addr; i++) //向本地的网络接口循环发送ssdp:alive消息
  9. {
  10. SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime); //发送ssdp:alive
  11. }
  12. }
发送的ssdp:alive消息格式如下:
NOTIFY * HTTP/1.1
HOST:239.255.255.250:1900    #协议保留多播地址和端口,必须是239.255.255.250:1900
CACHE-CONTROL:max-age=1810   #max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
LOCATION:http://192.168.1.20:8200/rootDesc.xml     #包含根设备描述得URL地址
SERVER: 3.4.72-rt89 DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.0
NT:upnp:rootdevice   #在此消息中,NT头必须为服务的服务类型
USN:uuid:4d696e69-444c-164e-9d41-001ec92f0378::upnp:rootdevice   #表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力
NTS:ssdp:alive   #表示通知消息的子类型,必须为ssdp:alive

UPnP网络结构的第二步是设备描述。在控制点发现了一个设备之后,控制点仍然对设备知之甚少,控制点可能仅仅知道设备或服务的UPnP类型,设备的UUID和设备描述的URL地址。为了让控制点更多的了解设备和它的功能或者与设备交互,控制点必须从发现消息中得到设备描述的URL,通过URL取回设备描述。
在程序中,我们发送完ssdp:alive广播后,网络上的控制点就会发送相应的消息到程序,在上边的select模型中,我们会通过以下程序接收控制点传来的ssdp消息:


 
 
  1. if (sudp >= 0 && FD_ISSET(sudp, &readset))
  2. {
  3. /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/
  4. ProcessSSDPRequest(sudp, ( unsigned short)runtime_vars.port); //接受控制点传来的ssdp信息,并回传给控制点设备描述信息
  5. }
在ProcessSSDPRequest中实现了接收控制点传来的消息,以及回传给控制点的信息(设备描述URL),接收的控制点消息格式如下(ssdp:discover):
M-SEARCH * HTTP/1.1
Host: 239.255.255.250:1900  #设置为协议保留多播地址和端口,必须是239.255.255.250:1900。
Man: "ssdp:discover"   #设置协议查询的类型,必须是"ssdp:discover"
MX: 5   #设置设备响应最长等待时间,设备响应在0和这个值之间随机选择响应延迟的值。这样可以为控制点响应平衡网络负载。
ST: upnp:rootdevice  #设置服务查询的目标
回传给控制点的消息格式如下:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1810  #max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
DATE: Tue, 11 Feb 2014 08:16:14 GMT  #指定响应生成的时间
ST: upnp:rootdevice  #内容和意义与查询请求的相应字段相同
USN: uuid:4d696e69-444c-164e-9d41-001ec92f0378::upnp:rootdevice  #表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。
EXT:     #向控制点确认MAN头域已经被设备理解
SERVER: 3.4.72-rt89 DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.0
LOCATION: http://192.168.1.20:8200/rootDesc.xml    #包含根设备描述得URL地址
Content-Length: 0

设备控制是UPnP网络的第三步。在接收设备和服务描述之后,控制点可以向这些服务发出动作,同时控制点也可以轮询服务的状态变量值。发出动作实质上是一种远程过程调用,控制点将动作送到设备服务,在动作完成之后,服务返回相应的结果。在这里,我们利用minidlna的基本功能——远程目录浏览,来说明。当我们在控制点VLC Media Player中点击“通用即插即播”,它会自动完成前面描述的设备发现设备描述,显示可用的设备信息列表(在这里,可用设备就是minidlna服务)


点击这里的Jane,就会显示minidlna设备指定的目录下的目录信息。当我们做这些操作的时候,控制点正在向minidlna设备发送请求消息。这个请求的格式如下:

POST /ctl/ContentDir HTTP/1.1
HOST: 192.168.1.20:8200
CONTENT-LENGTH: 488
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
USER-AGENT: 6.1.7600 2/, UPnP/1.0, Portable SDK for UPnP devices/1.6.18

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/” s:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
<s:Body><u:Browse xmlns:u=“urn:schemas-upnp-org:service:ContentDirectory:1”>
<ObjectID>64$4</ObjectID>
<BrowseFlag>BrowseDirectChildren</BrowseFlag>
<Filter>id,dc:title,res,sec:CaptionInfo,sec:CaptionInfoEx</Filter>
<StartingIndex>0</StartingIndex>
<RequestedCount>0</RequestedCount>
<SortCriteria></SortCriteria>
</u:Browse>
</s:Body>
</s:Envelope>注意这里SOAPACTION: “urn:schemas-upnp-org:service:ContentDirectory:1#Browse”,Browse将决定我们远程执行何种方法(有点类似信令)。在上边的select模型中,我们收到该请求:


 
 
  1. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  2. {
  3. if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset)))
  4. {
  5. Process_upnphttp(e); //这里会回送消息给控制点( 设备信息xml或远程目录信息等), port:8200
  6. }
  7. }
Process_upnphttp会在底层调用upnpsoap.c中的ExecuteSoapAction方法,在upnpsoap.c定义了相关信令和它们对应的方法,如下:

 
 
  1. static const struct
  2. {
  3. const char * methodName;
  4. void (*methodImpl)(struct upnphttp *, const char *);
  5. }
  6. soapMethods[] =
  7. {
  8. { “QueryStateVariable”, QueryStateVariable},
  9. { “Browse”, BrowseContentDirectory},
  10. { “Search”, SearchContentDirectory},
  11. { “GetSearchCapabilities”, GetSearchCapabilities},
  12. { “GetSortCapabilities”, GetSortCapabilities},
  13. { “GetSystemUpdateID”, GetSystemUpdateID},
  14. { “GetProtocolInfo”, GetProtocolInfo},
  15. { “GetCurrentConnectionIDs”, GetCurrentConnectionIDs},
  16. { “GetCurrentConnectionInfo”, GetCurrentConnectionInfo},
  17. { “IsAuthorized”, IsAuthorizedValidated},
  18. { “IsValidated”, IsAuthorizedValidated},
  19. { “X_GetFeatureList”, SamsungGetFeatureList},
  20. { “X_SetBookmark”, SamsungSetBookmark},
  21. { 0, 0 }
  22. };

更具对应关系,ExecuteSoapAction会再调用BrowseContentDirectory方法。BrowseContentDirectory中会搜索sqlite中的目录信息,将信息拼接出xml字符串,代码如下:


 
 
  1. static void
  2. BrowseContentDirectory (struct upnphttp * h, const char * action)
  3. {
  4. static const char resp0[] =
  5. "<u:BrowseResponse "
  6. "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
  7. "<Result>"
  8. "<DIDL-Lite"
  9. //......
  10. sql = sqlite3_mprintf( SELECT_COLUMNS
  11. "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
  12. " where PARENT_ID = '%q' %s limit %d, %d;",
  13. ObjectID, orderBy, StartingIndex, RequestedCount);
  14. DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
  15. /*
  16. * SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS, d.SIZE, d.TITLE, d.DURATION,
  17. * d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE,
  18. * d.RESOLUTION, d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC from OBJECTS o
  19. * left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '0' limit 0, -1;
  20. */
  21. ret = sqlite3_exec(db, sql, callback, ( void *) &args, &zErrMsg); //查询目录信息
  22. // ......
  23. ret = strcatf(&str, "</DIDL-Lite></Result>\n"
  24. "<NumberReturned>%u</NumberReturned>\n"
  25. "<TotalMatches>%u</TotalMatches>\n"
  26. "<UpdateID>%u</UpdateID>"
  27. "</u:BrowseResponse>",
  28. args.returned, totalMatches, updateID); //拼接xml字符串
  29. BuildSendAndCloseSoapResp(h, str.data, str.off); //回送给控制点xml字符串消息
  30. //......
  31. }
通过BuildSendAndCloseSoapResp回传给控制点,这个xml字符串格式如下:

<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result><DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" 
					  xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" 
					  xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><
					  container id="64$0" parentID="64" restricted="1" ><dc:title>android-14</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$1" parentID="64" restricted="1" ><dc:title>armeabi-v7a</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$2" parentID="64" restricted="1" ><dc:title>libwnck-2.22.0</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$3" parentID="64" restricted="1" ><dc:title>voice-client-example</dc:title><upnp:class>object.container.storageFolder</upnp:class></container></DIDL-Lite>
</Result>
<NumberReturned>6</NumberReturned>
<TotalMatches>6</TotalMatches>
<UpdateID>10</UpdateID></u:BrowseResponse>             
这个xml字符串,说明minidlna指定的目录下有android-14,armeabi-v7a,libwnck-2.22.0和voice-client-example等4个目录。控制点通过这一信息获取minidlna服务。







0 个人打赏
文章最后发布于: 2014-02-11 17:45:48
原创

minidlna源码初探(一)

前言

minidlna是一种优秀的DLNA解决方案。本文将涉及minidlna的upnp以及目录管理的代码。minidlna的下载链接如下:

wget http://netcologne.dl.sourceforge.net/project/minidlna/minidlna/1.1.0/minidlna-1.1.0.tar.gz

控制点使用VLC Media Player,下载链接如下:

http://www.videolan.org/vlc/index.zh.html#download

关于minidlna的配置,网上已有很多介绍,在这里就不复述了。

本文中一些关于UPNP的理论问题参考了IBM的相关介绍:

UPnP协议编程实践(1)

UPnP协议编程实践(2)


正文

在minidlna,本文描述的主要内容分布在minidlna.c(主程序),inotify.c(目录管理),upnphttp.c(upnp通信),minissdp.c(ssdp设备发现相关),upnpsoap.c(soap设备控制相关)等。

照例从main函数进入,这个在~/minidlna.c下。程序首先执行了init,open_db等方法:


 
 
  1. ret = init(argc, argv); //这里主要分析配置文件以及命令中的选项
  2. //......
  3. LIST_INIT(&upnphttphead); //初始化upnphttphead
  4. ret = open_db( NULL); //新建sqlite3 db
  5. //......
  6. check_db(db, ret, &scanner_pid);

新建连接用socket:


 
 
  1. sudp = OpenAndConfSSDPReceiveSocket(); //新建一个socket,执行setsockopt并且bind之, sudp就是返回的socket , 端口号SSDP_PORT(1900), 用于接受控制点信息
  2. if (sudp < 0)
  3. {
  4. DPRINTF(E_INFO, L_GENERAL, "Failed to open socket for receiving SSDP. Trying to use MiniSSDPd\n");
  5. if (SubmitServicesToMiniSSDPD(lan_addr[ 0].str, runtime_vars.port) < 0)
  6. DPRINTF(E_FATAL, L_GENERAL, "Failed to connect to MiniSSDPd. EXITING");
  7. }
  8. /* open socket for HTTP connections. Listen on the 1st LAN address */
  9. shttpl = OpenAndConfHTTPSocket(runtime_vars.port); //新建一个socket,执行setsockopt并且bind之, shttpl就是返回的socket , 端口号runtime_vars.port = 8200 , 它来自minidlna.conf
  10. if (shttpl < 0)
  11. DPRINTF(E_FATAL, L_GENERAL, "Failed to open socket for HTTP. EXITING\n");
  12. DPRINTF(E_WARN, L_GENERAL, "HTTP listening on port %d\n", runtime_vars.port);
  13. /* open socket for sending notifications */
  14. if (OpenAndConfSSDPNotifySockets(snotify) < 0) //初始化n_lan_addr个广播用socket
  15. DPRINTF(E_FATAL, L_GENERAL, "Failed to open sockets for sending SSDP notify "
  16. "messages. EXITING\n");
进入一个标准的select模型:

 
 
  1. while (!quitting) //init quitting = 0
  2. {
  3. /* Check if we need to send SSDP NOTIFY messages and do it if
  4. * needed */
  5. if (gettimeofday(&timeofday, 0) < 0)
  6. {
  7. DPRINTF(E_ERROR, L_GENERAL, "gettimeofday(): %s\n", strerror(errno));
  8. timeout.tv_sec = runtime_vars.notify_interval;
  9. timeout.tv_usec = 0;
  10. }
  11. else
  12. {
  13. /* the comparison is not very precise but who cares ? */
  14. if (timeofday.tv_sec >= (lastnotifytime.tv_sec + runtime_vars.notify_interval)) //如果超时
  15. {
  16. SendSSDPNotifies2(snotify,
  17. ( unsigned short)runtime_vars.port,
  18. (runtime_vars.notify_interval << 1)+ 10); //心跳广播ssdp:alive消息,通知其他接入点自己就绪
  19. memcpy(&lastnotifytime, &timeofday, sizeof(struct timeval));
  20. timeout.tv_sec = runtime_vars.notify_interval;
  21. timeout.tv_usec = 0;
  22. }
  23. else
  24. {
  25. timeout.tv_sec = lastnotifytime.tv_sec + runtime_vars.notify_interval
  26. - timeofday.tv_sec;
  27. if (timeofday.tv_usec > lastnotifytime.tv_usec)
  28. {
  29. timeout.tv_usec = 1000000 + lastnotifytime.tv_usec
  30. - timeofday.tv_usec;
  31. timeout.tv_sec--;
  32. }
  33. else
  34. timeout.tv_usec = lastnotifytime.tv_usec - timeofday.tv_usec;
  35. //..............
  36. FD_ZERO(&readset);
  37. if (sudp >= 0)
  38. {
  39. FD_SET(sudp, &readset); //将sudp加入readset
  40. max_fd = MAX(max_fd, sudp);
  41. }
  42. if (shttpl >= 0)
  43. {
  44. FD_SET(shttpl, &readset); //将shttpl加入readset
  45. max_fd = MAX(max_fd, shttpl);
  46. }
  47. //......
  48. i = 0; /* active HTTP connections count */
  49. // struct upnphttp *e
  50. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  51. {
  52. if ((e->socket >= 0) && (e->state <= 2))
  53. {
  54. FD_SET(e->socket, &readset); //添加记录的socket进入readset
  55. max_fd = MAX(max_fd, e->socket);
  56. i++;
  57. }
  58. }
  59. //.......
  60. FD_ZERO(&writeset);
  61. upnpevents_selectfds(&readset, &writeset, &max_fd);
  62. ret = select(max_fd+ 1, &readset, &writeset, 0, &timeout);
  63. if (ret < 0)
  64. {
  65. if(quitting) goto shutdown;
  66. if(errno == EINTR) continue;
  67. DPRINTF(E_ERROR, L_GENERAL, "select(all): %s\n", strerror(errno));
  68. DPRINTF(E_FATAL, L_GENERAL, "Failed to select open sockets. EXITING\n");
  69. }
  70. upnpevents_processfds(&readset, &writeset);
  71. /* process SSDP packets */
  72. if (sudp >= 0 && FD_ISSET(sudp, &readset))
  73. {
  74. /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/
  75. ProcessSSDPRequest(sudp, ( unsigned short)runtime_vars.port); //接受控制点传来的ssdp信息,并回传给控制点设备描述信息
  76. }
  77. //......
  78. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  79. {
  80. if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset)))
  81. {
  82. Process_upnphttp(e); //这里会回送消息给控制点( 设备信息xml或远程目录信息等), port:8200
  83. }
  84. }
  85. /* process incoming HTTP connections */
  86. if (shttpl >= 0 && FD_ISSET(shttpl, &readset))
  87. {
  88. int shttp;
  89. socklen_t clientnamelen;
  90. struct sockaddr_in clientname;
  91. clientnamelen = sizeof(struct sockaddr_in);
  92. shttp = accept(shttpl, (struct sockaddr *)&clientname, &clientnamelen); //获取远程socket shttp
  93. if (shttp< 0)
  94. {
  95. DPRINTF(E_ERROR, L_GENERAL, "accept(http): %s\n", strerror(errno));
  96. }
  97. else
  98. {
  99. struct upnphttp * tmp = 0;
  100. DPRINTF(E_DEBUG, L_GENERAL, "HTTP connection from %s:%d\n",
  101. inet_ntoa(clientname.sin_addr),
  102. ntohs(clientname.sin_port) );
  103. /*if (fcntl(shttp, F_SETFL, O_NONBLOCK) < 0) {
  104. DPRINTF(E_ERROR, L_GENERAL, "fcntl F_SETFL, O_NONBLOCK\n");
  105. }*/
  106. /* Create a new upnphttp object and add it to
  107. * the active upnphttp object list */
  108. tmp = New_upnphttp(shttp); //初始化 struct upnphttp ,并且将shttp赋予其socket字段
  109. if (tmp)
  110. {
  111. tmp->clientaddr = clientname.sin_addr;
  112. LIST_INSERT_HEAD(&upnphttphead, tmp, entries); //将tmp插入链表upnphttphead中
  113. }
  114. else
  115. {
  116. DPRINTF(E_ERROR, L_GENERAL, "New_upnphttp() failed\n");
  117. close(shttp);
  118. }
  119. }
  120. }
  121. //......
  122. }
设备发现是UPnP网络实现的第一步。在这里,minidlna启动后,本机作为一个设备加入到网络中,设备发现过程允许设备向网络上的控制点告知它提供的服务(ssdp:alive)。当一个控制点加入到网络中时,设备发现过程允许控制点寻找网络上感兴趣的设备(ssdp:discover)。在这两种情况下,基本的交换信息就是发现消息。发现消息包括设备的一些特定信息或者某项服务的信息,例如它的类型、标识符、和指向XML设备描述文档的指针。简单发现协议(SSDP)定义了在网络中发现网络服务,控制点定位网络上相关资源和设备在网络上声明其可用性的方法。

在上面的select模型中,程序通过定时执行SendSSDPNotifies2方法,广播设备就绪消息(心跳包),它的实现如下:


 
 
  1. void
  2. SendSSDPNotifies2 (int *sockets,
  3. unsigned short port,
  4. unsigned int lifetime)
  5. {
  6. int i;
  7. DPRINTF(E_DEBUG, L_SSDP, "Sending SSDP notifies\n");
  8. for (i = 0; i < n_lan_addr; i++) //向本地的网络接口循环发送ssdp:alive消息
  9. {
  10. SendSSDPNotifies(sockets[i], lan_addr[i].str, port, lifetime); //发送ssdp:alive
  11. }
  12. }
发送的ssdp:alive消息格式如下:
NOTIFY * HTTP/1.1
HOST:239.255.255.250:1900    #协议保留多播地址和端口,必须是239.255.255.250:1900
CACHE-CONTROL:max-age=1810   #max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
LOCATION:http://192.168.1.20:8200/rootDesc.xml     #包含根设备描述得URL地址
SERVER: 3.4.72-rt89 DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.0
NT:upnp:rootdevice   #在此消息中,NT头必须为服务的服务类型
USN:uuid:4d696e69-444c-164e-9d41-001ec92f0378::upnp:rootdevice   #表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力
NTS:ssdp:alive   #表示通知消息的子类型,必须为ssdp:alive

UPnP网络结构的第二步是设备描述。在控制点发现了一个设备之后,控制点仍然对设备知之甚少,控制点可能仅仅知道设备或服务的UPnP类型,设备的UUID和设备描述的URL地址。为了让控制点更多的了解设备和它的功能或者与设备交互,控制点必须从发现消息中得到设备描述的URL,通过URL取回设备描述。
在程序中,我们发送完ssdp:alive广播后,网络上的控制点就会发送相应的消息到程序,在上边的select模型中,我们会通过以下程序接收控制点传来的ssdp消息:


 
 
  1. if (sudp >= 0 && FD_ISSET(sudp, &readset))
  2. {
  3. /*DPRINTF(E_DEBUG, L_GENERAL, "Received UDP Packet\n");*/
  4. ProcessSSDPRequest(sudp, ( unsigned short)runtime_vars.port); //接受控制点传来的ssdp信息,并回传给控制点设备描述信息
  5. }
在ProcessSSDPRequest中实现了接收控制点传来的消息,以及回传给控制点的信息(设备描述URL),接收的控制点消息格式如下(ssdp:discover):
M-SEARCH * HTTP/1.1
Host: 239.255.255.250:1900  #设置为协议保留多播地址和端口,必须是239.255.255.250:1900。
Man: "ssdp:discover"   #设置协议查询的类型,必须是"ssdp:discover"
MX: 5   #设置设备响应最长等待时间,设备响应在0和这个值之间随机选择响应延迟的值。这样可以为控制点响应平衡网络负载。
ST: upnp:rootdevice  #设置服务查询的目标
回传给控制点的消息格式如下:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1810  #max-age指定通知消息存活时间,如果超过此时间间隔,控制点可以认为设备不存在
DATE: Tue, 11 Feb 2014 08:16:14 GMT  #指定响应生成的时间
ST: upnp:rootdevice  #内容和意义与查询请求的相应字段相同
USN: uuid:4d696e69-444c-164e-9d41-001ec92f0378::upnp:rootdevice  #表示不同服务的统一服务名,它提供了一种标识出相同类型服务的能力。
EXT:     #向控制点确认MAN头域已经被设备理解
SERVER: 3.4.72-rt89 DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.0
LOCATION: http://192.168.1.20:8200/rootDesc.xml    #包含根设备描述得URL地址
Content-Length: 0

设备控制是UPnP网络的第三步。在接收设备和服务描述之后,控制点可以向这些服务发出动作,同时控制点也可以轮询服务的状态变量值。发出动作实质上是一种远程过程调用,控制点将动作送到设备服务,在动作完成之后,服务返回相应的结果。在这里,我们利用minidlna的基本功能——远程目录浏览,来说明。当我们在控制点VLC Media Player中点击“通用即插即播”,它会自动完成前面描述的设备发现设备描述,显示可用的设备信息列表(在这里,可用设备就是minidlna服务)


点击这里的Jane,就会显示minidlna设备指定的目录下的目录信息。当我们做这些操作的时候,控制点正在向minidlna设备发送请求消息。这个请求的格式如下:

POST /ctl/ContentDir HTTP/1.1
HOST: 192.168.1.20:8200
CONTENT-LENGTH: 488
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
USER-AGENT: 6.1.7600 2/, UPnP/1.0, Portable SDK for UPnP devices/1.6.18

<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/” s:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
<s:Body><u:Browse xmlns:u=“urn:schemas-upnp-org:service:ContentDirectory:1”>
<ObjectID>64$4</ObjectID>
<BrowseFlag>BrowseDirectChildren</BrowseFlag>
<Filter>id,dc:title,res,sec:CaptionInfo,sec:CaptionInfoEx</Filter>
<StartingIndex>0</StartingIndex>
<RequestedCount>0</RequestedCount>
<SortCriteria></SortCriteria>
</u:Browse>
</s:Body>
</s:Envelope>注意这里SOAPACTION: “urn:schemas-upnp-org:service:ContentDirectory:1#Browse”,Browse将决定我们远程执行何种方法(有点类似信令)。在上边的select模型中,我们收到该请求:


 
 
  1. for (e = upnphttphead.lh_first; e != NULL; e = e->entries.le_next)
  2. {
  3. if ((e->socket >= 0) && (e->state <= 2) && (FD_ISSET(e->socket, &readset)))
  4. {
  5. Process_upnphttp(e); //这里会回送消息给控制点( 设备信息xml或远程目录信息等), port:8200
  6. }
  7. }
Process_upnphttp会在底层调用upnpsoap.c中的ExecuteSoapAction方法,在upnpsoap.c定义了相关信令和它们对应的方法,如下:

 
 
  1. static const struct
  2. {
  3. const char * methodName;
  4. void (*methodImpl)(struct upnphttp *, const char *);
  5. }
  6. soapMethods[] =
  7. {
  8. { “QueryStateVariable”, QueryStateVariable},
  9. { “Browse”, BrowseContentDirectory},
  10. { “Search”, SearchContentDirectory},
  11. { “GetSearchCapabilities”, GetSearchCapabilities},
  12. { “GetSortCapabilities”, GetSortCapabilities},
  13. { “GetSystemUpdateID”, GetSystemUpdateID},
  14. { “GetProtocolInfo”, GetProtocolInfo},
  15. { “GetCurrentConnectionIDs”, GetCurrentConnectionIDs},
  16. { “GetCurrentConnectionInfo”, GetCurrentConnectionInfo},
  17. { “IsAuthorized”, IsAuthorizedValidated},
  18. { “IsValidated”, IsAuthorizedValidated},
  19. { “X_GetFeatureList”, SamsungGetFeatureList},
  20. { “X_SetBookmark”, SamsungSetBookmark},
  21. { 0, 0 }
  22. };

更具对应关系,ExecuteSoapAction会再调用BrowseContentDirectory方法。BrowseContentDirectory中会搜索sqlite中的目录信息,将信息拼接出xml字符串,代码如下:


 
 
  1. static void
  2. BrowseContentDirectory (struct upnphttp * h, const char * action)
  3. {
  4. static const char resp0[] =
  5. "<u:BrowseResponse "
  6. "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
  7. "<Result>"
  8. "<DIDL-Lite"
  9. //......
  10. sql = sqlite3_mprintf( SELECT_COLUMNS
  11. "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
  12. " where PARENT_ID = '%q' %s limit %d, %d;",
  13. ObjectID, orderBy, StartingIndex, RequestedCount);
  14. DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
  15. /*
  16. * SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS, d.SIZE, d.TITLE, d.DURATION,
  17. * d.BITRATE, d.SAMPLERATE, d.ARTIST, d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE,
  18. * d.RESOLUTION, d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC from OBJECTS o
  19. * left join DETAILS d on (d.ID = o.DETAIL_ID) where PARENT_ID = '0' limit 0, -1;
  20. */
  21. ret = sqlite3_exec(db, sql, callback, ( void *) &args, &zErrMsg); //查询目录信息
  22. // ......
  23. ret = strcatf(&str, "</DIDL-Lite></Result>\n"
  24. "<NumberReturned>%u</NumberReturned>\n"
  25. "<TotalMatches>%u</TotalMatches>\n"
  26. "<UpdateID>%u</UpdateID>"
  27. "</u:BrowseResponse>",
  28. args.returned, totalMatches, updateID); //拼接xml字符串
  29. BuildSendAndCloseSoapResp(h, str.data, str.off); //回送给控制点xml字符串消息
  30. //......
  31. }
通过BuildSendAndCloseSoapResp回传给控制点,这个xml字符串格式如下:

<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result><DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" 
					  xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" 
					  xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><
					  container id="64$0" parentID="64" restricted="1" ><dc:title>android-14</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$1" parentID="64" restricted="1" ><dc:title>armeabi-v7a</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$2" parentID="64" restricted="1" ><dc:title>libwnck-2.22.0</dc:title><upnp:class>object.container.storageFolder</upnp:class></container><
					  container id="64$3" parentID="64" restricted="1" ><dc:title>voice-client-example</dc:title><upnp:class>object.container.storageFolder</upnp:class></container></DIDL-Lite>
</Result>
<NumberReturned>6</NumberReturned>
<TotalMatches>6</TotalMatches>
<UpdateID>10</UpdateID></u:BrowseResponse>             
这个xml字符串,说明minidlna指定的目录下有android-14,armeabi-v7a,libwnck-2.22.0和voice-client-example等4个目录。控制点通过这一信息获取minidlna服务。







0 个人打赏
文章最后发布于: 2014-02-11 17:45:48
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值