Firefox插件之间的消息

时间:2014-07-21 14:54:29

标签: javascript plugins firefox-addon firefox-addon-sdk xpcom

我试图在两个或多个插件之间实现客户端 - 服务器通信,其中每个插件同时是服务器和客户端。我使用nsIServerSocket作为客户端部分的服务器部分和websockets。这是代码:

function startServer(port) {

var listener = {
onSocketAccepted: function(serverSocket, transport) {
    console.log("Accepted connection on " + transport.host + ":" + transport.port);
    var input = transport.openInputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);//.QueryInterface(Ci.nsIAsyncInputStream);
    var output = transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING, 0, 0);
    var sin = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);

    try{
    sin.init(input);
    var readBytes = sin.available();
    var request = '';
    request = sin.read(readBytes);
    console.log('Received: ' + request);
    //getUrl(request);
    output.write("yes", "yes".length);
    output.flush();
    }
    finally{
    sin.close();
    input.close();
    output.close();
    }
}
}

try{
var serverSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
serverSocket.init(port, true, 5);
console.log("Opened socket on " + serverSocket.port);
serverSocket.asyncListen(listener);
}catch(e){
console.log(e);
}    
}

对于服务器部分,以及客户端部分的以下内容:

var address="ws://otherAddress:1234";// + port;
var window = Cc["@mozilla.org/appshell/appShellService;1"]
             .getService(Ci.nsIAppShellService)
             .hiddenDOMWindow;
ws = new window.WebSocket(address);

try{
ws.onmessage = function () {
};

ws.onopen = function(){
    console.log("connection opened");
    // Web Socket is connected. You can send data by send() method
    ws.send("lol ");
};

ws.onclose = function() {
    // websocket is closed. };
    console.log("websocket is closed");
}
}catch(evt){
console.log(evt.data);
}

客户端代码在用户点击按钮时启动....此代码部分正常工作,因为从控制台我看到,当用户点击按钮时,服务器收到连接打开,但我无法接收消息......任何人都可以帮助我?感谢

更新1

我在控制台中看到的消息是这样的:

"Received: GET / HTTP/1.1
Host: localhost:1234
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: resource://gre-resources
Sec-WebSocket-Key: zh/EpJRRsOAgLfPIbI1EDg==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

更新2 在nmaier和IvyLynx回答之后(非常感谢!!),我修改了我的代码,插入了一个完整的" ServerSocket"实现(主要是因为将来我也会传递二进制数据)。这是localhost案例的代码:

var {Cc, Ci, Cu, Cr, components} = require("chrome");

// the thread manager can be important when using asynchronous mode
var thread_manager = Cc["@mozilla.org/thread-manager;1"].getService();
var socket_service = Cc["@mozilla.org/network/socket-transportservice;1"].getService(Ci.nsISocketTransportService);

// make some constructors so we don't have to worry about this later
var socket = Cc["@mozilla.org/network/serversocket;1"].createInstance(Ci.nsIServerSocket);
// set the second argument to false if you want it to listen
// to connections beyond the computer the extension runs on
socket.init(-1, true, -1);
var output_stream_bin = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
var input_stream_bin = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);

// this is so we can easily instantiate nsIInputStreamPump, which allows us to read input streams properly
var input_stream_pump_c = Cc["@mozilla.org/network/input-stream-pump;1"];
var input_stream_base, input_stream_async_c, input_stream_async, recieved_bytes, recieved_total, input_stream_pump;
var output_stream_base, output_stream_async_c, output_stream_async, generalStream;
var client, client_input_stream, client_output_stream, client_input_stream_pump;
var data_to_send = "hi hi"; // this holds what we want to send
var socket_transport = socket_service.createTransport(null, 0, "localhost", socket.port, null);

var socket_listener = {
   onSocketAccepted: function(socket, transport){

      client = transport;

      client_input_stream = client.openInputStream(0, 0, 0);
      client_output_stream = client.openOutputStream(0, 0, 0);
      client_output_stream.QueryInterface(Ci.nsIAsyncOutputStream);
      generalStream = client_output_stream;
      client_input_stream_pump[this_transport] = input_stream_pump_c.createInstance(Ci.nsIInputStreamPump);
      client_input_stream_pump[this_transport].init(client_input_stream, -1, -1, 0, 0, false);
      client_input_stream_pump[this_transport].asyncRead(socket_reader, socket);

   },
   onStopListening: function(socket, status){
   }
};

socket.asyncListen(socket_listener);

// this guy will get called when we're ready to send data
var output_stream_callback = {
   onOutputStreamReady: function(stream){          
      output_stream_bin.setOutputStream(stream);
      output_stream_bin.writeBytes(data_to_send, data_to_send.length);
      data_to_send = "";
   }
};

var socket_reader = {
   onDataAvailable: function(request, context, stream, offset, count){

      input_stream_bin.setInputStream(stream);

      if(input_stream_bin.available() > 0){
        recieved_bytes = input_stream_bin.readByteArray(count);
        recieved_total = "";

        // this loop converts bytes to characters
        // if you don't need to pass binary data around
        // you can just use nsIScriptableInputStream instead of
        // nsIBinaryInputStream and skip this
        for (var i = 0; i < recieved_bytes.length; i++){
            recieved_total += String.fromCharCode(recieved_bytes[i]);
        }
      console.log("Received " + recieved_total)
      }else{
        stream.close();
      }
   },
   onStartRequest: function(request, context){
   },
   onStopRequest: function(request, context, status){
   }
};

require("sdk/widget").Widget({
   id: "mozilla-link",
   label: "Mozilla website",
   contentURL: data.url("icon.png"),
   onClick: listTabs

});

function listTabs() {
   //console.log(client_output_stream);

   generalStream.asyncWait(output_stream_callback,0,0,thread_manager.mainThread);
};

问题是generalStream变量。当用户点击扩展图标时我调用asyncWait方法,但我也用其他方法插入调用。每个generalStream.asyncWait都会引发以下问题(实际上,在执行扩展时,实际存在配置文件的路径):

console.error: client:
Message: TypeError: generalStream is undefined
Stack:
listTabs@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NP
aadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/lo
ader.js -> resource://jid1-exo2npaaditkqg-at-jetpack/client/lib/main.js:742
_emitOnObject@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2N PaadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/l
oader.js -> resource://gre/modules/commonjs/sdk/deprecated/events.js:153
_emit@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/deprecated/events.js:123 _onEvent@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadi
Tkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:278
WidgetView__onEvent@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:426
WC_addEventHandlers/listener/<@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/widget.js:884
notify@resource://gre/modules/XPIProvider.jsm -> jar:file:///.../extensions/jid1-exo2NPaadiTkqg@jetpack.xpi!/bootstrap.js -> resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/commonjs/sdk/timers.js:40

2 个答案:

答案 0 :(得分:4)

nmaier很可能是正确的,为什么你的代码不起作用,但我仍然会将这个答案作为补充信息发布

首先,您不需要同时使用WebSockets和XPCOM套接字来创建服务器和客户端的扩展。这些技术中的任何一种都足以满足此目的。就个人而言,自己做了这个,我建议使用XPCOM套接字,除非你真的想在浏览器之间创建可移植的代码(因为你没有提到类似的东西,我建议删除WebSockets实现并坚持使用XPCOM套接字 - 但WebSockets更容易处理,所以你可能更喜欢它们 - 没关系,只需选择一个)。我提到这个,因为你有WebSocket和XPCOM Socket都在监听连接,我认为这是因为你想要这个:

ServerSocket接收客户端连接 - &gt;仅接收来自客户端的连接

WebSocket连接到服务器连接 - &gt;仅发送到服务器连接

相反,您可以只有nsIServerSocket来读取和发送数据到另一个服务器套接字。此外,正如nmaier所说,阻塞流是一个坏主意,除非你真的需要它们,在这种情况下,你不需要。如果你使用异步模式,它可能也会帮助事情更好地工作。

以下是使用nsIServerSocket所需内容的示例实现,我将其包括在内,因为了解其工作原理会给我带来很多痛苦和时间。如果想要本机应用程序级别tcp套接字的强大功能(换句话说,您不需要读取二进制流或进行复杂处理或编写自己的通信协议),WebSockets就是可能对你而言更合适,因为他们会处理一个简单的消息传递系统。简单地说,如果以下是你的一杯茶,只需坚持使用WebSockets,直到它们无法满足您的需求为止。

注意:以下代码不会尝试命名空间并立即使全局命名空间崩溃 - 它不是生产代码,所以通常,您希望所有这些变量和对象都包含在您的扩展名的命名空间对象。

以下是nsIServerSocket二进制流的示例实现:

准备代码

// these aliases will shorten the code
var {
    utils: Cu,
    interfaces: Ci,
    classes: Cc,
    results: Cr,
    stack: Cs,
    manager: Cm,
    Exception: Ce,
    Constructor: CC,
} = Components;

// get related services
// the thread manager can be important when using asynchronous mode
var thread_manager = Cc["@mozilla.org/thread-manager;1"].getService();
var socket_service = Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService);

// make some constructors so we don't have to worry about this later
var socket_c = CC("@mozilla.org/network/server-socket;1", "nsIServerSocket", "init");
var output_stream_bin_c = CC("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream");
var input_stream_bin_c = CC("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream");

// this is so we can easily instantiate nsIInputStreamPump, which allows us to read
// input streams properly
var input_stream_pump_c = Cc["@mozilla.org/network/input-stream-pump;1"];

// normally all these would be placed in a global object. they're declared here
// so we can instantiate them later, but this is just a sample, not production code!
var input_stream_base, input_stream_async_c, input_stream_async, input_stream_bin, recieved_bytes, recieved_total, input_stream_pump;
var output_stream_base, output_stream_async_c, output_stream_async, output_stream_bin;
var client, client_input_stream, client_output_stream, client_input_stream_pump;
var data_to_send = ""; // this holds what we want to send

制作套接字

// this socket will only listen on localhost
// set the second argument to false if you want it to listen
// to connections beyond the computer the extension runs on
var socket = new socket_c(-1, true, -1);

var socket_transport = socket_service.createTransport(null, 0, "localhost", socket.port, null);

定义回调和监听器

// this guy will get called when we're ready to send data
var output_stream_callback = {
    onOutputStreamReady: function(stream){          
        output_stream_bin = new output_stream_bin_c(stream);
        output_stream_bin.writeBytes(data_to_send, data_to_send.length);
        data_to_send = "";
    }
};

var socket_reader = {

    onDataAvailable: function(request, context, stream, offset, count){

        input_stream_bin = new input_stream_bin_c(stream);

        if(input_stream_bin.available() > 0){
            recieved_bytes = input_stream_bin.readByteArray(count);

            recieved_total = ""; // this holds the stuff we get

            // this loop converts bytes to characters
            // if you don't need to pass binary data around
            // you can just use nsIScriptableInputStream instead of
            // nsIBinaryInputStream and skip this
            for (var i = 0; i < recieved_bytes.length; i++){
                recieved_total += String.fromCharCode(recieved_bytes[i]);
             }

        }else{
            stream.close();
            // Nothing there, closing stream.
        }
    },
    onStartRequest: function(request, context){

    },
    onStopRequest: function(request, context, status){

    }
};

var socket_listener = {
    onSocketAccepted: function(socket, transport){

        client = transport;

        client_input_stream = client.openInputStream(0, 0, 0);
        client_output_stream = client.openOutputStream(0, 0, 0);
        client_output_stream.QueryInterface(Ci.nsIAsyncOutputStream);

        client_input_stream_pump[this_transport] = input_stream_pump_c.createInstance(Ci.nsIInputStreamPump);
        client_input_stream_pump[this_transport].init(client_input_stream, -1, -1, 0, 0, false);
        client_input_stream_pump[this_transport].asyncRead(socket_reader, socket);

    },
    onStopListening: function(socket, status){

    }
};

开始侦听套接字

socket.asyncListen(socket_listener);

当您想发送数据时

(编辑 - 此部分应放在一个函数中,只有在建立连接后才会被调用,并且仅在您想要发送数据时调用)

var stream = client_output_stream; // what stream you want to send the data to

// this is the only place where the thread_manager is required
stream.asyncWait(output_stream_callback,0,0,thread_manager.mainThread);

这个实现是完全异步,所以它应该永远不会对性能产生影响或导致问题,除非有太多的数据(我猜,我真的不认为以前性能会有问题)您的连接出现问题)或出现问题(例如从输出流回调中调用.asyncWait)。

您的输入位于recieved_total中,在您在连接的客户端输出流上调用data_to_send之前,您要发送的内容将放在.asyncWait中。请记住,这是一个示例实现,因此如果您想使用它,您需要更改它以便它使用命名空间,并且您需要为要获取或发送的任何数据添加处理函数。此外,如果您希望有多个客户端连接,则还需要单独处理(通过对象数组或其他内容)。

如果您需要有关所用组件的信息,那么MDN显然是可以实现的目标。但是,nsIInputStreamPump由于某种原因没有页面。为此,您需要转到源idl中的.js实现。 mozilla lxr也是查看javascript中套接字测试实现的好地方,因为其中有一些var {Cc, Ci, Cu, Cr, components} = require("chrome");文件用于测试实现。

编辑 -

对于addon-sdk,这可能效果更好

尝试用{替换var Components = require("chrome"); var { utils: Cu, interfaces: Ci, classes: Cc, results: Cr, stack: Cs, manager: Cm, Exception: Ce, Constructor: CC, } = Components; 然后添加

console.log(Components);

与原始代码中一样。另外,在require行之后添加components,以便了解您是否真正获得{{1}}对象。

答案 1 :(得分:2)

nsISocketServer实现了一个简单的TCP /绑定服务器,但没有实现websocket协议。

  • 您需要在服务器套接字中自己实现the websocket protocol(包括HTTP / 1.1升级)
  • 或使用原始TCP套接字(nsISocketTransport通过nsISocketTransportService)。

鉴于原始TCP套接字处理通常是一团糟,无论如何你都需要实现一些简单的交换协议,我猜第一个在服务器套接字中实现websocket协议的选项会更容易(至少,你可以免费获得客户端实现)。

PS:阻止模式是一个坏主意,因为它会在很长一段时间内阻止UI线程。

PS:显然,某人implemented the websocket protocol already in coffee script和其他人(来自附加SDK团队)也实现了它in (what appears to be some form of :p) Javascript(尽管后者几乎不是自包含且难以阅读/喘气)。

编辑我好奇并写了a stand-alone JS code module WebSocket server,这似乎主要起作用。 :P