minidlna源码初探(三)—— ACE实现SSDP设备发现功能


前言:

       前一篇文章minidlna源码初探(二)—— SSDP设备发现的大致流程介绍了SSDP设备发现的大致流程。本文将根据这一流程使用ACE库大致实现该流程。在VLC中模拟出一个伪服务端(设备),为了方便,我们省略了一些验证的内容,对一些XML消息也采取写死的方式。


正文:

      首先,我们需要一个消息循环,如下:

//创建组播SOCKET
ACE_SOCK_Dgram_Mcast udp;
ACE_INET_Addr mcast_inet("239.255.255.250:1900");
udp.join(mcast_inet);
udp.set_option(IP_MULTICAST_TTL, MAX_MULTICAST_IP_TTL);

//创建单址SOCKET
ACE_HANDLE peer_handle = GetPeerListenSocket(8200);

thread_pool thrd_pool;    //新建线程池
thrd_pool.run();               //启动线程池

while(1)
 {
        if ((GetCurrentSecond() - time_record) > 20)
        {
            handle_alive(udp);
            time_record = GetCurrentSecond();           //定时组播ssdp:alive消息
        }
        
        active_handle_set.set_bit(udp_handle);
        active_handle_set.set_bit(peer_handle);

        width = (int)active_handle_set.max_set() + 1;
        if (ACE::select(width , &active_handle_set , 0 , 0 , &atv) < 1)  //监听组播和单址是否有请求
        {
            continue;
        }
        
        if (active_handle_set.num_set() <= 0)
        {
            continue;
        }
        

        ACE_Handle_Set_Iterator iterator(active_handle_set);
        ACE_HANDLE handle = iterator();
        for (; handle != ACE_INVALID_HANDLE; handle = iterator())
        {
            if (handle == udp_handle)     //处理组播请求
            {
                {
                    boost::mutex::scoped_lock lock(transform_mutex);
                    res = udp.recv(buf , 512 , remote_inet_addr);
                    if(res <= 0) continue;
                }
                
                std::map<ACE_HANDLE , std::string> m;
                m.insert(std::pair<ACE_HANDLE , std::string>(udp_handle , std::string(buf)));
                thrd_pool.push_task(m);     //向线程池push新的消息
            }
            
            if (handle == peer_handle)  //处理单地请求
            {
                std::map<ACE_HANDLE , std::string> m;
                m.insert(std::pair<ACE_HANDLE , std::string>(peer_handle , std::string("HANDLE_PEER_ACCEPTOR")));
                thrd_pool.push_task(m);  //向线程池push新的消息
            }
        }
}

消息循环定时组播ssdp:alive消息,并获取组播或者单址传递来的消息。然后将SOCKET handle连同消息存入一个std::map<ACE_HANDLE , std::string>容器,随后将这个map压入线程池中的队列。线程池代码如下:

class thread_pool : public boost::noncopyable
{
	private:
		
		boost::thread_group thrds;
		boost::mutex io_mutex;

		std::map<std::string , std::string>::iterator iter;
		
		static bool finish;
		int thread_num;
		
		
		typedef std::map<ACE_HANDLE , std::string> MAPHDLSTR;
		typedef std::queue<MAPHDLSTR> QUEMAPHDLSTR;
		QUEMAPHDLSTR vecque;
	public:
		thread_pool(){ thread_num = 8;} 		
		thread_pool(int num):thread_num(num){}
		
		~thread_pool()
		{
                       //......
                       //处理队列中剩余的任务
		}
		
		void proxy()
		{
			while(!finish)
			{
				MAPHDLSTR maphdlstr;
				{
					boost::mutex::scoped_lock lock(io_mutex);
					if (!vecque.empty())
					{
						maphdlstr = vecque.front();      //获取队列的map讯息
						vecque.pop();
					}
				}

				if (!maphdlstr.empty())
				{
					handle_data(maphdlstr);    //处理map
				}
			}
			
		}
		
		void push_task(MAPHDLSTR m)
		{
			vecque.push(m);     //push新的map讯息
		}

		void run()
		{
			for(size_t i = 0; i < thread_num ; ++i)
			{
				thrds.create_thread(boost::bind(&thread_pool::proxy,this));   //启动线程
			}
		}
		
		
		void join(){thrds.join_all();}
		int get_static(){return finish;}
		int handle_data(MAPHDLSTR maphdlstr);
};

map被压入线程池后交由handle_data处理,其代码如下:

int thread_pool::handle_data(MAPHDLSTR maphdlstr)
{
	ACE_HANDLE  sock = maphdlstr.begin()->first;
	std::string data   = maphdlstr.begin()->second;
	
	if (0 == data.find("M-SEARCH * HTTP/1.1"))  //处理ssdp:discover
	{
                //data中的格式如下
                /*
	  	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  #设置服务查询的目标  
 		*/

		 SendSSDPResponse(sock);
		return 0;
	}
	
	if (0 == data.find("NOTIFY * HTTP/1.1"))   //ssdp:alive
	{
                //data中的格式如下
                /*
		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 
		*/

		return 0;
	}
	
	if (0 == data.find("POST /ctl/ContentDir HTTP/1.1"))
	{
                //处理设备发现后的后续请求,在这里暂缺,data中的格式如下
                /*
		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> 
		*/

	}
	
	if (0 == data.find("HANDLE_PEER_ACCEPTOR"))
	{
		handle_peer(sock);       //处理单址请求
	}

	return 0;
}
当客户端(控制点)连入服务,发送ssdp:discover过来,我们需要回复其消息,这个在SendSSDPResponse中完成,其代码如下:

void
SendSSDPResponse(ACE_HANDLE handle)
{
        //回复客户端的消息格式
	/*
	HTTP/1.1 200 OK
	CACHE-CONTROL: max-age=1810
	DATE: Wed, 21 May 2014 03:54:53 GMT
	ST: urn:schemas-upnp-org:device:MediaServer:1
	USN: uuid:4d696e69-444c-164e-9d41-001ec92f0378::urn:schemas-upnp-org:device:MediaServer:1
	EXT:
	SERVER: 3.2.0-61-generic DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.2
	LOCATION: http://192.168.1.20:8200/rootDesc.xml
	Content-Length: 0
	 */
	 
	std::cout<<"SendSSDPResponse  handle = "<<handle<<std::endl;

	char buf[512];
	char tmstr[30];
	time_t tm = time(NULL);
	
	strftime(tmstr, sizeof(tmstr), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&tm));
	snprintf(buf, sizeof(buf), "HTTP/1.1 200 OK\r\n"
                 "CACHE-CONTROL: max-age=%u\r\n"
                 "DATE: %s\r\n"
                 "ST: %s%s\r\n"
                 "USN: %s%s%s%s\r\n"
                 "EXT:\r\n"
                 "SERVER: " MINIDLNA_SERVER_STRING "\r\n"
                 "LOCATION: http://%s:%u" ROOTDESC_PATH "\r\n"
                 "Content-Length: 0\r\n"
                 "\r\n",
                 (895<<1)+20,
                 tmstr,
                 "urn:schemas-upnp-org:device:MediaServer:",
                 (3>1?"1":""),
                 "uuid:4d696e69-444c-164e-9d41-001ec92f0378",
                 (3 > 0 ? "::" : ""),
                 (3 > 0 ? "urn:schemas-upnp-org:device:MediaServer:" : ""),
                 (3 > 1 ? "1" : ""),
                 "192.168.1.20", (unsigned int)8200);
	
	{
		boost::mutex::scoped_lock lock(transform_mutex);
		ACE_SOCK_Dgram_Mcast udp_t;
		udp_t.set_handle(handle);
		udp_t.send(buf , strlen(buf)); 
	}
	
}
以上操作完成后,客户端会在8200端口向设备(服务端)发送单址请求,在线程池中,我们交由handle_peer完成,其代码如下:

int handle_peer(ACE_HANDLE handle)
{
	socklen_t clientnamelen;
	struct sockaddr_in clientname;
	clientnamelen = sizeof(struct sockaddr_in);
	char buf[2048] = {0};

	int shttp = accept(handle, (struct sockaddr *)&clientname, &clientnamelen);  //获取远程客户端单址SOCKET
	if (shttp < 0)
	{
		std::cout<<GREEN<<"accept(http):  "<<strerror(errno)<<NONE<<std::endl;
		return -1;
	}

	recv(shttp, buf, 2048, 0);  
			
	if (std::string(buf).find("GET /rootDesc.xml HTTP/1.1") == 0)
	{
                //如果是请求信息,则会送客户端根目录信息,请求信息格式如下
                /*
                GET /rootDesc.xml HTTP/1.1
                HOST: 192.168.1.20:8200
                DATE: Fri, 30 May 2014 05:37:15 GMT
                CONNECTION: close
                USER-AGENT: 6.1.7600 2/, UPnP/1.0, Portable SDK for UPnP devices/1.6.14
                */
                SendRootContainer(shttp);  //会送给客户端根目录讯息
	}
	close(shttp);
	
	return 0;
}

SendRootContainer将会送给客户端根目录讯息,其代码如下:

void
SendRootContainer(ACE_HANDLE handle)
{		
	const char *friendly_name = "Jane";
	const char *resp  = "HTTP/1.1 200 OK\r\n"
						"Content-Type: text/xml; charset=\"utf-8\"\r\n"
						"Connection: close\r\n"
						"Content-Length: 2189\r\n"
						"Server: 3.2.0-61-generic DLNADOC/1.50 UPnP/1.0 MiniDLNA/1.1.2\r\n"
						"Date: Thu, 22 May 2014 05:29:30 GMT\r\n"
						"EXT:\r\n"
						"\r\n"
						"<?xml version=\"1.0\"?>\r\n"
						"<root xmlns=\"urn:schemas-upnp-org:device-1-0\"><specVersion><major>1</major><minor>0</minor></specVersion><device>"
						"<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType><friendlyName>Jane"
				        "</friendlyName><manufacturer>Justin Maggard</manufacturer><manufacturerURL>http://www.netgear.com/</manufacturerURL>"
				        "<modelDescription>MiniDLNA on Linux</modelDescription><modelName>Windows Media Connect compatible (MiniDLNA)</modelName>"
				        "<modelNumber>1</modelNumber><modelURL>http://www.netgear.com</modelURL><serialNumber>12345678</serialNumber>"
				        "<UDN>uuid:4d696e69-444c-164e-9d41-001ec92f0378</UDN><dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>"
				        "<presentationURL>/</presentationURL><iconList><icon><mimetype>image/png</mimetype><width>48</width><height>48</height>"
				        "<depth>24</depth><url>/icons/sm.png</url></icon><icon><mimetype>image/png</mimetype><width>120</width><height>120</height>"
				        "<depth>24</depth><url>/icons/lrg.png</url></icon><icon><mimetype>image/jpeg</mimetype><width>48</width><height>48</height>"
				        "<depth>24</depth><url>/icons/sm.jpg</url></icon><icon><mimetype>image/jpeg</mimetype><width>120</width><height>120</height>"
				        "<depth>24</depth><url>/icons/lrg.jpg</url></icon></iconList><serviceList><service><serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>"
				        "<serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId><controlURL>/ctl/ContentDir</controlURL><eventSubURL>/evt/ContentDir</eventSubURL>"
				        "<SCPDURL>/ContentDir.xml</SCPDURL></service><service><serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>"
				        "<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId><controlURL>/ctl/ConnectionMgr</controlURL><eventSubURL>/evt/ConnectionMgr</eventSubURL>"
				        "<SCPDURL>/ConnectionMgr.xml</SCPDURL></service><service><serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType>"
				        "<serviceId>urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar</serviceId><controlURL>/ctl/X_MS_MediaReceiverRegistrar</controlURL>"
				        "<eventSubURL>/evt/X_MS_MediaReceiverRegistrar</eventSubURL><SCPDURL>/X_MS_MediaReceiverRegistrar.xml</SCPDURL></service></serviceList></device></root>";

	ACE_SOCK_Stream stream;
	stream.set_handle(handle);
	
	int res = 0;
	int len = strlen(resp);
	
	{
		boost::mutex::scoped_lock lock(peer_mutex);
		if( (res = stream.send_n(resp, len)) <= 0 )
		{
			std::cout<<RED<<"send(http): "<< strerror(errno)<<std::endl;
		}
	}
	
	stream.close();
}

源码大致讲述到此,看看效果,在VlC的UPNP中:

minidlna源码初探(三)—— ACE实现SSDP设备发现功能_第1张图片



完整代码下载地址https://github.com/iwojima/ACE_SSDP_DEMO.git





你可能感兴趣的:(minidlna源码初探(三)—— ACE实现SSDP设备发现功能)