1. 程式人生 > >JavaScript和Chrome中的SSDP發現

JavaScript和Chrome中的SSDP發現

目錄

介紹

什麼是SSDP?

在Chrome中使用UDP的要求

使用UDP執行SSDP的步驟

發現裝置!現在怎麼辦?


Chrome中的HTMLJavaScript內發現使用SSDP的本地裝置

介紹

在實施一些專案時,我決定用HTML實現它們,因為它可以在我感興趣的最廣泛的裝置上工作。我感興趣的專案需要發現連線到我的家庭網路的其他裝置。我用SSDP進行發現。

https://www.codeproject.com/KB/HTML/1273691/SSDPDiscovery.png

什麼是SSDP

SSDP簡單服務發現協議)是基於UDP的協議,是UPnP的一部分,用於在網路上查詢其他裝置和服務。它由許多裝置實現,包括網路附加儲存裝置,智慧電視和家庭自動化系統。有很多這些裝置通過

JSON呼叫公開功能。您可以輕鬆地建立介面來控制這些裝置。但是,由於HTMLJavaScript的標準不包含UDP介面,因此如何執行發現並不是很明顯。SSDP的替代方案包括讓使用者手動輸入感興趣的裝置的IP地址或掃描網路。這些選項中的後者可以在某些公司網路上執行時引發一些安全標記,並且可能需要很長時間。在大多數情況下,解決方案都是依賴於平臺。有各種基於HTML的解決方案,允許您通過UDP進行通訊。例如,BrightSign HTML5播放器通過使用roDatagramSocket支援UDPChrome通過chrome.udp.sockets提供UDP通訊。

Chrome中使用UDP的要求

網頁沒有訪問此介面的許可權(這是有充分理由的,因為在其他方面可能會被濫用)。雖然網路應用無法訪問,但Chrome擴充套件程式可以訪問。Chrome擴充套件程式無法在其他瀏覽器中使用。但在撰寫本文時,Chrome佔瀏覽器市場份額的67%,微軟已宣佈將使用Chromium作為其Edge瀏覽器的基礎。雖然這種UDP套接字實現在各種瀏覽器中都不可用,但它在很大程度上可供大多數使用者使用,因為這是大多數桌面使用者的首選瀏覽器。要將HTML程式碼作為擴充套件執行,需要另外兩個元素:清單和後臺指令碼。後臺指令碼將建立一個視窗並將開始的HTML載入到其中。

chrome.app.runtime.onLaunched.addListener(function() {
    chrome.app.window.create('index.html', {
        'outerBounds': {
        'width': 600,
        'height': 800
        }
    });
});

我不會詳細介紹清單中的內容,但我會強調其最重要的元素。清單採用JSON格式。定義要執行的初始指令碼app.background.scripts。其他重要元素是permission元素和manifest_version元素,沒有permission元素,通過UDP或加入多播組進行通訊的嘗試將失敗並且。其他元素是直觀的。

{
    "name": "SSDP Browser",
    "version": "0.1",
    "manifest_version": 2,
    "minimum_chrome_version": "27",
    "description": "Discovers SSDP devices on the network",
    "app": {
      "background": {
        "scripts": [
          "./scripts/background.js"
        ]
      }
    },

    "icons": {
        "128": "./images/j2i-128.jpeg",
        "64": "./images/j2i-64.jpeg",
        "32": "./images/j2i-32.jpeg"
    },

    "permissions": [
      "http://*/",
      "storage",
      {
        "socket": ["udp-send-to", "udp-bind", "udp-multicast-membership"]
      }
    ]
  }

使用UDP執行SSDP的步驟

Google已經發布了一個包裝器,作為chrome.udp.sockets在網路上使用多播發布的程式碼示例。Google程式碼示例以未經更改的形式假定文字採用Unicode16位字元編碼進行編碼。SSDP使用8ASCII編碼。我已經使用了Google的類,並對其進行了一些小改動使其可以使用ASCII而不是Unicode。要執行SSDP搜尋,請執行以下步驟。

  1. 建立UDP埠並將其連線到組播組239.255.255.250
  2. 在埠1900上傳送M-SEARCH查詢
  3. 等待來自其他裝置上的從埠1900傳入的響應
  4. 解析響應
  5. 一段時間後停止監聽

第一步主要由Google Multicast類處理。我們只需要將埠和地址傳遞給它。M-SEARCH查詢是一個string。至於最後一步,當響應停止進入時,它不是決定性的。即使沒有請求,某些裝置似乎偶爾也會在網路上廣播訊息。從理論上講,你可以繼續得到迴應。在某個時候,我建議不再監聽。五到十秒的時間通常是足夠的。M-SEARCH引數有變化,但以下內容可用於詢找所有裝置。還有其他查詢可用於篩選具有特定功能的裝置。以下是我使用的string不立即可見的是,在最後一行文字之後,有兩個空行。

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 3
ST: ssdp:all
USER-AGENT: Joel's SSDP Implementation

當響應出現時,我們分配給MulticastScoket.onDiagram的函式將使用包含響應的位元組陣列、響應來自的IP地址和傳送響應的埠號(當前應用程式的埠號為1900)應用)來呼叫。在下面的程式碼示例中,我啟動搜尋並將響應列印到JavaScript控制檯。

const SSDP_ADDRESS = '239.255.255.250';
const SSDP_PORT = 1900;
const SSDP_REQUEST_PAYLOAD =    "M-SEARCH * HTTP/1.1\r\n"+
                                "HOST: 239.255.255.250:1900\r\n"+
                                "MAN: \"ssdp:discover\"\r\n"+
                                "MX: 3\r\n"+
                                "ST: ssdp:all\r\n"+
                                "USER-AGENT: Joel's SSDP Implementation\r\n\r\n";

var searchSocket = null;

function beginSSDPDiscovery() { 
    if (searchSocket)
        return;
    $('.responseList').empty();
    searchSocket = new MulticastSocket({address:SSDP_ADDRESS, port:SSDP_PORT});
    searchSocket.onDiagram = function(arrayBuffer, remote_address, remote_port) {
        console.log('response from ', remote_address, " ", remote_port);
        var msg = searchSocket.arrayBufferToString8(arrayBuffer);
        console.log(msg);        
    }
    searchSocket.connect({call:function(c) {
        console.log('connect result',c);
        searchSocket.sendDiagram(SSDP_REQUEST_PAYLOAD,{call:()=>{console.log('success')}});
        setTimeout(endSSDPDiscovery, 5000);
    }});    
}

並不是解析響應string很困難,無論如何,如果響應是JSON物件會更方便。我已經建立了一個函式,可以快速轉換響應,因此我可以像任何其他JSON物件一樣使用它。

function discoveryStringToDiscoveryDictionary(str) {
    var lines = str.split('\r');
    var retVal = {}
    lines.forEach((l) => {
        var del = l.indexOf(':');
        if(del>1) {
            var key = l.substring(0,del).trim().toLowerCase();
            var value = l.substring(del+1).trim();
            retVal[key]=value;
        }
    });
    return retVal;
}   

完成此轉換後,我網路上的Roku流媒體播放器返回了以下響應。(我改變了序列號。)

{
    cache-control: "max-age=3600",
    device-group.roku.com: "D1E000C778BFF26AD000",
    ext: "",
    location: "http://192.168.1.163:8060/",
    server: "Roku UPnP/1.0 Roku/9.0.0",
    st: "roku:ecp",
    usn: "uuid:roku:ecp:1XX000000000",
    wakeup: "MAC=08:05:81:17:9d:6d;Timeout=10"    ,
}

已經為要使用的示例分享了足夠的程式碼,但是我將更改示例以在UI中顯示響應,而不是依賴於開發JavaScript控制檯。為了簡單起見,我定義了HTML結構,將每個結果用作palettediv元素的子元素。這個元素是隱藏的,但對於每個響應,我將克隆ssdpDevice類的div元素將改變一些子成員並將其附加到頁面的可見部分。

  

 <html>
    <head>
        <link rel="stylesheet" href="styles/style.css" />
        <script src="./scripts/jquery-3.3.1.min.js"></script>
        <script src="./scripts/MulticastSocket.js"></script>
        <script src="./scripts/app.js"></script>
    </head>
    <body>
        <div class="visualRoot">
            <div>
                <button id="scanNetworkButton">Scan Network</button>
            </div>
            <div class="responseList">

            </div>
        </div>
        <div class="palette">

            <div class="ssdpDevice">
                <div>address: <span class="ipAddress"></span></div>
                <div >location: <span class="location"></span></div>
                <div >server: <span class="server"></span></div>
                <div> search target:<span class="searchTarget"></span></div>
            </div>

        </div>
    </body>
</html> 

改變後的函式現在將在HTML中顯示SSDP響應,如下所示:

function beginSSDPDiscovery() {
    if (searchSocket)
        return;
    $('.responseList').empty();
    searchSocket = new MulticastSocket({address:SSDP_ADDRESS, port:SSDP_PORT});
    searchSocket.onDiagram = function(arrayBuffer, remote_address, remote_port) {
        console.log('response from ', remote_address, " ", remote_port);
        var msg = searchSocket.arrayBufferToString8(arrayBuffer);
        console.log(msg);
        discoveryData = discoveryStringToDiscoveryDictionary(msg);
        console.log(discoveryData);

        var template = $('.palette').find('.ssdpDevice').clone();
        $(template).find('.ipAddress').text(remote_address);
        $(template).find('.location').text(discoveryData.location);
        $(template).find('.server').text(discoveryData.server);
        $(template).find('.searchTarget').text(discoveryData.st)
        $('.responseList').append(template);
    }
    searchSocket.connect({call:function(c) {
        console.log('connect result',c);
        searchSocket.sendDiagram(SSDP_REQUEST_PAYLOAD,{call:()=>{console.log('success')}});
        setTimeout(endSSDPDiscovery, 5000);
    }});
}

發現裝置!現在怎麼辦?

從這裡出發取決於他們的意圖和裝置的能力。不同的裝置實現不同的API以訪問其功能。您可以找到有關在UPnP規範下實現的常規API的更多資訊,但許多允許以這種方式發現它們的裝置可以通過它們自己的API提供功能。

 

原文地址:https://www.codeproject.com/Articles/1273691/SSDP-Discovery-in-JavaScript-and-Chrome