ssdp使用多播套接字发现upnp设备

时间:2016-06-07 05:27:00

标签: c# upnp multicastsocket ssdp

我正在尝试使用多播套接字在网络中发现UPnP设备,但是,我似乎多次获得相同的设备。这里的发现代码有什么问题。

我得到的结果如下

HTTP / 1.1 200好的 CACHE-CONTROL:最大年龄= 60 EXT: 地点:http://10.2.1.89:5200/Printer.xml 服务器:网络打印机服务器UPnP / 1.0 V4.00.01.31 DEC-23-2014 ST:uuid:16a65700-007c-1000-bb49-30cda79cac19 USN:uuid:16a65700-007c-1000-bb49-30cda79cac19

HTTP / 1.1 200好的 CACHE-CONTROL:最大年龄= 60 EXT: 地点:http://10.2.1.87:5200/Printer.xml 服务器:网络打印机服务器UPnP / 1.0 V4.00.01.31 DEC-23-2014 ST:uuid:16a65700-007c-1000-bb49-30cda79b5419 USN:uuid:16a65700-007c-1000-bb49-30cda79b5419

使用的代码如下

namespace DevManager
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    public class UPnPDevDiscovery 
    {
        /// <summary>
        /// Device search request
        /// </summary>
        private const string searchRequest = "M-SEARCH * HTTP/1.1\r\nHOST: {0}:{1}\r\nMAN: \"ssdp:discover\"\r\nMX: {2}\r\nST: {3}\r\n";

        /// <summary>
        /// Advertisement multicast address
        /// </summary>
        private const string multicastIP = "239.255.255.250";

        /// <summary>
        /// Advertisement multicast port
        /// </summary>
        private const int multicastPort = 1900;

        /// <summary>
        ///  Time to Live (TTL) for multicast messages
        /// </summary>
        private const int multicastTTL = 4;

        private const int unicastPort = 1901;

        private const int MaxResultSize = 8096;

        private const string DefaultDeviceType = "ssdp:all";

        private string deviceType;

        private Action<Device> onDeviceFound;

        private int searchTimeOut;

        private Socket socket;

        private Timer timer;

        private int sendCount;

        private SocketAsyncEventArgs sendEvent;

        private bool socketClosed;

        private List<Task> taskList = new List<Task>();

        public void Initialize(string deviceType, int searchTimeOut, Action<Device> onDeviceFound)
        {
            if (searchTimeOut < 1 || searchTimeOut > 4)
            {
                this.searchTimeOut = multicastTTL;
            }
            else
            {
                this.searchTimeOut = searchTimeOut;
            }

            if (string.IsNullOrWhiteSpace(deviceType))
            {
                this.deviceType = DefaultDeviceType;
            }
            else
            {
                this.deviceType = deviceType;
            }

            this.onDeviceFound = onDeviceFound;
        }

        public void FindDevices()
        {
            string request = string.Format(searchRequest, multicastIP, multicastPort, this.searchTimeOut, this.deviceType);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            byte[] multiCastData = Encoding.UTF8.GetBytes(request);
            socket.SendBufferSize = multiCastData.Length;
            sendEvent = new SocketAsyncEventArgs();
            sendEvent.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(multicastIP), multicastPort);
            sendEvent.SetBuffer(multiCastData, 0, multiCastData.Length);
            sendEvent.Completed += OnSocketSendEventCompleted;

            // Set a one-shot timer for the Search time plus a second
            TimerCallback cb = new TimerCallback((state) =>
            {
                this.socketClosed = true;
                socket.Close();
            });

            timer = new Timer(cb, null, TimeSpan.FromSeconds(this.searchTimeOut + 1), new TimeSpan(-1));

            // Kick off the initial Send
            this.sendCount = 3;
            this.socketClosed = false;
            socket.SendToAsync(sendEvent);
            //while (!this.socketClosed)
            //{
            //    Thread.Sleep(200);
            //}

            //Task.WaitAll(this.taskList.ToArray());
            //this.taskList.Clear();
        }

        private void OnSocketSendEventCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                this.AddDevice(null);
            }
            else
            {
                if (e.LastOperation == SocketAsyncOperation.SendTo)
                {
                    if (--this.sendCount != 0)
                    {
                        if (!this.socketClosed)
                        {
                            socket.SendToAsync(sendEvent);
                        }
                    }
                    else
                    {
                        // When the initial multicast is done, get ready to receive responses
                        e.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                        socket.ReceiveBufferSize = MaxResultSize;
                        byte[] receiveBuffer = new byte[MaxResultSize];
                        e.SetBuffer(receiveBuffer, 0, MaxResultSize);
                        socket.ReceiveFromAsync(e);
                    }
                }
                else if (e.LastOperation == SocketAsyncOperation.ReceiveFrom)
                {
                    // Got a response, so decode it
                    string result = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
                    if (result.StartsWith("HTTP/1.1 200 OK", StringComparison.InvariantCultureIgnoreCase))
                    {
                        //parse device and invoke callback
                        AddDevice(result);
                    }
                    else
                    {
                        //Debug.WriteLine("INVALID SEARCH RESPONSE");
                    }

                    if (!this.socketClosed)
                    {
                        // and kick off another read
                        socket.ReceiveFromAsync(e);
                    }
                    else
                    {
                        // unless socket was closed, when declare the scan is complete
                        //AddDevice(result);
                    }
                }
            }
        }

        private void AddDevice(string response)
        {
            Console.WriteLine(response);
            //Task addDeviceTask = Task.Run(() =>
            //{
            //    // parse the result and download the device description
            //    if (this.onDeviceFound != null && response != null)
            //    {
            //        Dictionary<string, string> ssdpResponse = ParseSSDPResponse(response);
            //        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(ssdpResponse["location"]);
            //        WebResponse webResponse = webRequest.GetResponse();
            //        using (DeviceXml deviceXml = new DeviceXml(webResponse.GetResponseStream()))
            //        {
            //            this.onDeviceFound(deviceXml.GetObject());
            //        }
            //    }
            //});

            //this.taskList.Add(addDeviceTask);
        }

        // Probably not exactly compliant with RFC 2616 but good enough for now
        private Dictionary<string, string> ParseSSDPResponse(string response)
        {
            StringReader reader = new StringReader(response);

            string line = reader.ReadLine();
            if (line != "HTTP/1.1 200 OK")
                return null;

            Dictionary<string, string> result = new Dictionary<string, string>();

            for (;;)
            {
                line = reader.ReadLine();
                if (line == null)
                    break;
                if (line != "")
                {
                    int colon = line.IndexOf(':');
                    if (colon < 1)
                    {
                        return null;
                    }
                    string name = line.Substring(0, colon).Trim();
                    string value = line.Substring(colon + 1).Trim();
                    if (string.IsNullOrEmpty(name))
                    {
                        return null;
                    }
                    result[name.ToLowerInvariant()] = value;
                }
            }
            return result;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我已将上面的内容移到了最低限度的实现中:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Timers;
namespace SomeApp
{
    public class UPnPDevDiscovery
    {
        /// <summary>
        /// Device search request
        /// </summary>
    private const string searchRequest = @"M-SEARCH * HTTP/1.1
HOST: {0}:{1}
MAN: ""ssdp:discover""
MX: {2}
ST: {3}

";
        /// <summary>
        /// Advertisement multicast address
        /// </summary>
        private const string MulticastIP = "239.255.255.250";

        /// <summary>
        /// Advertisement multicast port
        /// </summary>
        private const int multicastPort = 1900;

        /// <summary>
        ///  Time to Live (TTL) for multicast messages
        /// </summary>
        private const int multicastTTL = 4;

        private const int MaxResultSize = 8096;

        private const string DefaultDeviceType = "ssdp:all";

        private int searchTimeOut = 5; //Seconds

        private Socket socket;

        private SocketAsyncEventArgs sendEvent;

        public void FindDevices()
        {
            string request = string.Format(searchRequest, MulticastIP, multicastPort, this.searchTimeOut, DefaultDeviceType);
            Console.WriteLine("Sending: \n" + request);

            byte[] multiCastData = Encoding.UTF8.GetBytes(request);

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket.SendBufferSize = multiCastData.Length;
            sendEvent = new SocketAsyncEventArgs();
            sendEvent.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(MulticastIP), multicastPort);
            sendEvent.SetBuffer(multiCastData, 0, multiCastData.Length);
            sendEvent.Completed += OnSocketSendEventCompleted;

            Timer t = new Timer(TimeSpan.FromSeconds(this.searchTimeOut + 1).TotalMilliseconds);
            t.Elapsed += (e, s) => {
                socket.Dispose();
                socket = null;
            };

            // Kick off the initial Send
            socket.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.MulticastInterface, IPAddress.Parse(MulticastIP).GetAddressBytes());
            socket.SendToAsync(sendEvent);
            t.Start();
        }

        private void OnSocketSendEventCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                Console.WriteLine("SocketError: " + e.SocketError);
                return;
            }

            switch (e.LastOperation)
            {
                case SocketAsyncOperation.SendTo:
                    Console.WriteLine("Send complete");

                    // When the initial multicast is done, get ready to receive responses
                    e.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                    byte[] receiveBuffer = new byte[MaxResultSize];
                    socket.ReceiveBufferSize = receiveBuffer.Length;
                    e.SetBuffer(receiveBuffer, 0, MaxResultSize);

                    Console.WriteLine("Waiting for response");
                    socket.ReceiveFromAsync(e);
                    break;

                case SocketAsyncOperation.ReceiveFrom:
                    Console.WriteLine("Received:");
                    // Got a response, so decode it
                    string result = Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred);
                    if (result.StartsWith("HTTP/1.1 200 OK", StringComparison.InvariantCultureIgnoreCase))
                        Console.WriteLine(result);
                    else
                        Console.WriteLine("INVALID SEARCH RESPONSE\n" + result);

                    if (socket != null)// and kick off another read
                        socket.ReceiveFromAsync(e);                    
                    break;
                default:
                    Console.WriteLine("***"+e.LastOperation);
                    break;
            }
        }
    }
}