陈建华的博客
专注web开发
【chrome扩展】网络通信(第九章)
2014-11-30 11:16:57   阅读4948次

Chrome应用通过sockets接口支持TCP和UDP协议,使网络通信成为可能。使用sockets接口时,声明权限比较特殊,并不在permissions中声明,而是直接在Manifest的sockets中声明:

"sockets": {
    "udp": {
        "send": ["host-pattern1", ...],
        "bind": ["host-pattern2", ...],
        ...
    },
    "tcp" : {
        "connect": ["host-pattern1", ...],
        ...
    },
    "tcpServer" : {
        "listen": ["host-pattern1", ...],
        ...
    }
}

但在早期的Chrome版本中socket权限依然在permissions中声明。

sockets接口传输的数据类型为ArrayBuffer,有关ArrayBuffer的内容可以参阅7.6.1节的内容。

最后本章还会介绍有关WebSocket的内容,这是HTML5原生支持的方法。


9.1UDP协议

UDP协议是一个简单的面向数据报的传输层协议,它是一种不可靠数据报协议。由于缺乏可靠性且属于非连接导向协定,UDP应用一般必须允许一定量的丢包和出错。

Chrome提供sockets.udp接口使Chrome应用可以进行UDP通信。要使用sockets.udp接口需要在sockets域中声明udp权限:

"sockets": {
    "udp": {
        "send": ["192.168.1.106:8000", ":8001"],
        "bind": ":8000"
    }
}

上面的代码表示应用可以通过UDP与192.168.1.106的8000端口通信,也可以与任意主机的8001端口通信。应用可以绑定本地的8000端口用来接收UDP消息。

如果想连接任意主机的任意端口可以声明为"send": "*"。主机和端口可以是一个特定的字符串,也可以是一个数组表示多个规则,如"bind": [":8000", ":8001"]。如果想连接192.168.1.106的任意端口可以声明为"send": "192.168.1.106"。


9.1.1建立与关闭连接

创建socket

var socketOption = {
    persistent: true,
    name: 'udpSocket',
    bufferSize: 4096
};
chrome.sockets.udp.create(socketOption, function(socketInfo){
    //We'll do the next step later
});

socketOption是一个可选参数,其中persistent代表Chrome应用的生命周期结束后(参见6.4节)这个socket是否还依然处于开启状态。bufferSize定义socket接收数据的缓存区大小,如果这个值过小,会导致数据丢失,默认值是4096。

当socket创建完毕就返回一个对象,这个对象包含代表这个socket的唯一id,之后对socket的操作都将根据这个id。

更新socket属性

此处说的属性是指创建socket时提到的socketOptions。通过update方法可以更新socket的属性:

chrome.sockets.udp.update(socketId, newSocketOption, function(){
    //do something when update complete
});

阻止和解除阻止socket接收数据

当一个socket被阻止后,将不会触发消息接收事件,解除阻止后将恢复正常。

//Blocking socket receiving data
var isPaused = true;
chrome.sockets.udp.setPaused(socketId, isPaused, function(){
    //do something after pause a socket
});

如果想解除阻止,将上述代码中的isPaused设为false即可。

绑定端口

绑定固定端口用以接收UDP消息:

var localAddress = '10.60.37.105';
var localPort = 6259;
chrome.sockets.udp.bind(socketId, localAddress, localPort, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after bind a port
});

一台计算机往往有多个IP,如本地回路IP、局域网IP和公网IP等,那么指定localAddress为"0.0.0.0"系统会自动绑定一个合适的IP地址,而不必让开发者获取所有IP地址后再进行选择。同样可以指定localPort为0,这样系统会自动分配一个空闲的端口给应用使用。

关闭socket

当一个socket不再被使用了我们应该关闭它。

chrome.socket.udp.close(socketId, function(){
    //do something after close a socket
});

下面我们来试着封装一个udp类,并在之后的内容逐步扩充它:

function udp(){
    var _udp = chrome.sockets.udp;
    this.option = {},
    this.socketId = 0,
    this.localAddress = '0.0.0.0',
    this.localPort = 0,
    this.create = function(callback){
        _udp.create(this.option, function(socketInfo){
            this.socketId = socketInfo.socketId;
            callback();
        }.bind(this));
    }.bind(this),
    this.update = function(){
        _udp.update(this.socketId, newSocketOption, callback);
    }.bind(this),
    this.pause = function(isPaused, callback){
        _udp.setPaused(this.socketId, isPaused, callback);
    }.bind(this),
    this.bind = function(callback){
        _udp.bind(this.socketId, this.localAddress, this.localPort, callback);
    }.bind(this),
    this.close = function(callback){
        _udp.close(this.socketId, callback);
    }.bind(this),
    this.init = function(callback){
        this.create(function(){
            this.bind(callback);
        }.bind(this));
    }.bind(this)
}

调用时指定socket属性、绑定IP和端口就可以进行初始化了:

var udpSocket = new udp();
udpSocket.option = {
    persistent: true
};
udpSocket.localPort = 8000;
udpSocket.init(function(code){
    if(code<0){
        console.log('UDP Socket bind failed, error code: '+code);
        return false;
    }
    else{
        //We'll do something after udp socket init later
    }
});

9.1.2发送与接收数据

发送数据

Socket发送的数据类型为ArrayBuffer,对ArrayBuffer不熟悉的读者请参阅7.6.1节的内容。

chrome.sockets.udp.send(socketId, data, address, port, function(){
    //do something after send some data
});

其中data为ArrayBuffer类型的数据,address为接收方的ip,port为接收方的端口。

接收数据

当socket接收到数据时,就会触发onReceive事件:

chrome.socket.udp.onReceive.addListener(function(info){
    //We'll do something with info later
});

返回的info是一个对象,包括4个属性:socketId、data、remoteAddress和remotePort。其中data为ArrayBuffer类型数据,remoteAddress和remotePort分别为发送方ip地址和端口。

处理异常

当网络出现问题时,会触发onReceiveError事件,同时socket会被阻断:

chrome.sockets.udp.onReceiveError.addListener(function(info){
    //We'll do something with info later
});

与接收数据相似,info也是一个对象,但只包含socketId和resultCode两个属性。

现在我们来把8.1.1中的udp类完善一下,由于篇幅限制,将只写出添加或改动的部分:

function udp(){
    this.send = function(address, port, data, callback){
        _udp.send(this.socketId, data, address, port, callback);
    }.bind(this),
    this.receive = function(info){
        console.log('Received data from '+info.removeAddress+':'+info.removePort);
    },
    this.error = function(code){
        console.log('An error occurred with code '+code);
    },
    this.init = function(callback){
        this.create(function(){
            this.bind(function(code){
                if(code<0){
                    this.error(code);
                    return false;
                }
                else{
                    callback();
                }
            }.bind(this));
            _udp.onReceive.addListener(function(info){
                if(info.socketId==this.socketId){
                    this.receive(info);
                }
            }.bind(this));
            _udp.onReceiveError.addListener(function(info){
                if(info.socketId==this.socketId){
                    this.error(info.resultCode);
                }
            });
        }.bind(this));
    }.bind(this)
}

9.1.3多播

多播用于一个有限的局部网络中的UDP一对多通信。之所以说是一个有限的局部网络是因为这个范围是无法确定的,一个是因为一个数据能传多远由TTL决定,多播中TTL一般被设为15,最多不会超过30,也有设为0的(数据不会流出本地)。再一个就是有的路由是不会转发多播数据的,即使TTL在此节点并没有减为0。

多播使用一段保留的ip地址,224.0.0.0到239.255.255.255。其中224.0.0.0到224.0.0.255为局部连接多播地址,224.0.1.0到238.255.255.255为预留多播地址,239.0.0.0到239.255.255.255为管理权限多播地址。局部连接多播地址和管理权限多播地址均为保留多播地址,可被自由使用的只有预留多播地址,即224.0.1.0到238.255.255.255。

多播地址是特殊的ip地址,它不对应任何物理设备。但UDP进行多播时,只将其看做普通的ip地址就可以。

要使用多播,需要在sockets的udp中声明multicastMembership权限:

"sockets": {
    "udp": {
        "multicastMembership": "*";
    }
}

如果提示Invalid host:port pattern,这是Chrome的Bug,解决方法是multicastMembership的值设定成空字符串:

"sockets": {
    "udp": {
        "multicastMembership": "";
    }
}

加入组

chrome.sockets.udp.joinGroup(socketId, address, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after join a group
});

离开组

chrome.sockets.udp.leaveGroup(socketId, address, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after leave a group
});

设置多播的TTL

chrome.sockets.udp.setMulticastTimeToLive(socketId, ttl, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after set multicast TTL
});

设置多播回环模式

这个模式定义了当主机本身处于多播的目标组中时,是否接收来自自身的数据。如果一台主机中多个程序加入了同一个多播组,但回环模式设置矛盾时,Windows会不接收来自本机自身的数据,而基于Unix的系统则不接收来自程序自身的数据。

chrome.sockets.udp.setMulticastLoopbackMode(sockedId, enabled, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after set multicast loopback mode
});

让我们把多播的功能加入到udp类中:

function udp(){
    this.joinGroup = function(address, callback){
        _udp.joinGroup(this.socketId, address, function(code){
            if(code<0){
                this.error(code);
                return false;
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.leaveGroup = function(address, callback){
        _udp.leaveGroup(this.socketId, address, function(code){
            if(code<0){
                this.error(code);
                return false;
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.setMilticastTTL = function(ttl, callback){
        _udp.setMulticastTimeToLive(this.socketId, ttl, function(code){
            if(code<0){
                this.error(code);
                return false;
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.setMilticastLoopback = function(enabled, callback){
        _udp.setMulticastLoopbackMode(this.sockedId, enabled, function(code){
            if(code<0){
                this.error(code);
                return false;
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this)
}

9.1.4获取socket和组

获取指定socket

chrome.sockets.udp.getInfo(socketId, function(socketInfo){
    //do something with socketInfo
});

socketInfo是一个描述socket信息的对象,包括socketId、persistent、name、bufferSize、paused、localAddress和localPort。

获取全部活动的socket

chrome.sockets.udp.getSockets(function(socketInfoArray){
    //do something with socketInfoArray
});

socketInfoArray是一个包含一个或多个socketInfo对象的数组。

获取指定socket加入的组

chrome.sockets.udp.getJoinedGroups(socketId, function(groupArray){
    //do something with groupArray
});

groupArray是一个包含多个组地址字符串的数组。

将以上方法加入到udp类中:

function udp(){
    this.getInfo = function(callback){
        _udp.getInfo(this.socketId, callback);
    }.bind(this),
    this.getSockets = function(callback){
        _udp.getSockets (callback);
    }.bind(this),
    this.getGroups = function(callback){
        _udp.getJoinedGroups(this.socketId, callback);
    }.bind(this)
}

9.1.5局域网聊天应用

经过前面的介绍,我们对UDP在Chrome应用中的使用有了一定的了解,本节来和大家一起编写一款局域网聊天的应用。

这个应用利用UDP的多播功能,进行一对多通信,来实现局域网聊天。首先需要在Manifest的sockets中声明udp的权限:

{
    "app": {
        "background": {
            "scripts": ["udp.js", "background.js"]
        }
    },
    "manifest_version": 2,
    "name": "Local Messager",
    "version": "1.0",
    "description": "A local network chating application.",
    "icons": {
        "128": "lm.png"
    },
    "sockets": {
        "udp": {
            "send": "224.0.1.100",
            "bind": ":*",
            "multicastMembership": "224.0.1.100"
        }
    }
}

注意,由于8.1.3节中提到的Bug,multicastMembership的值在实际操作中可能需要指定为空字符串"",即"multicastMembership": ""。

Event Page中指定的udp.js就是我们在之前写好的udp类。下面我们来编写background.js。

首先当应用运行时开始创建UDP连接并加入到多播组:

var udpSocket = new udp();
udpSocket.localPort = 8943;
udpSocket.receive = receiveMsg;
udpSocket.init(function(){
    udpSocket.joinGroup('224.0.1.100', function(){
        //Joined group 224.0.1.100
    });
});

下面需要background来监听来自前端页面发来的指令:

chrome.runtime.onMessage.addListener(function(message, sender, callback){
    if(message.action == 'send'){
        var buf = str2ab(message.msg);
        udpSocket.send('224.0.1.100', udpSocket.localPort, buf, function(){
            //message is sent
        });
    }
});

下面我们来编写接收消息的函数:

function receiveMsg(info){
    var msg = ab2str(info.data);
    chrome.runtime.sendMessage({action:'receive', msg:msg});
}

最后来编写ArrayBuffer和String类型数据互换的两个函数:

function str2ab(str){
    var buf = new ArrayBuffer(str.length*2);
    bufView = new Uint16Array(buf);
    for(var i=0; i<str.length; i++){
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}
function ab2str(buf){
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}

UDP通信相关的内容写好了,接下来创建前端窗口:

chrome.app.runtime.onLaunched.addListener(function(){
    chrome.app.window.create('main.html', {
        'id': 'main',
        'bounds': {
            'width': 400,
            'height': 600
        }
    });
});

前端窗口main.html的HTML代码:

<html>
<head>
<title>Local Messager</title>
<style>
* {
    margin: 0;
    padding: 0;
}
body {
    border: #EEE 1px solid;
}
#history {
    margin: 10px;
    border: gray 1px solid;
    height: 480px;
    overflow-y: auto;
    overflow-x: hidden;
}
#history div {
    padding: 10px;
    border-bottom: #EEE 1px solid;
}
#msg {
    margin: 10px;
    width: 480px;
    height: 80px;
    box-sizing: border-box;
    border: gray 1px solid;
    font-size: 24px;
}
</style>
</head>
<body>
<div id="history"></div>
<input type="text" id="msg" />
<script src="main.js"></script>
</body>
</html>

main.js的代码:

document.getElementById('msg').onkeyup = function(e){
    if(e.keyCode==13){
        chrome.runtime.sendMessage({
            action:'send',
            msg:encodeURIComponent(this.value)
        });
        this.value = '';
    }
}
chrome.runtime.onMessage.addListener(function(message, sender, callback){
    if(message.action == 'receive'){
        var el = document.createElement('div');
        el.innerText = decodeURIComponent(message.msg);
        document.getElementById('history').appendChild(el);
    }
});

下面是Local Messager的运行截图。

01finmqZFBCd.png

Local Messager

本节讲解的应用源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/local_messager下载得到。

9.2TCP协议

TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议。

Chrome提供sockets.tcp接口使Chrome应用可以进行TCP通信。要使用sockets.tcp接口需要在sockets域中声明tcp权限:

"sockets": {
    "tcp": {
        "connect": ["192.168.1.100:80", ":8080"]
    }
}

上面的代码表示应用可以通过TCP与192.168.1.100的80端口和任意主机的8080端口通信。


9.2.1建立与关闭连接

创建socket

var socketOption = {
    persistent: true,
    name: 'tcpSocket',
    bufferSize: 4096
};
chrome.sockets.tcp.create(socketOption, function(socketInfo){
    //We'll do the next step later
});

socketOption是一个可选参数,其中persistent代表Chrome应用的生命周期结束后(参见6.4节)这个socket是否还依然处于开启状态。bufferSize定义socket接收数据的缓存区大小,默认值是4096。

当socket创建完毕就返回一个对象,这个对象包含代表这个socket的唯一id,之后对socket的操作都将根据这个id。

更新socket属性

此处说的属性是指创建socket时提到的socketOptions。通过update方法可以更新socket的属性:

chrome.sockets.tcp.update(socketId, newSocketOption, function(){
    //do something when update complete
});

阻止和解除阻止socket接收数据

当一个socket被阻止后,将不会触发消息接收事件,解除阻止后将恢复正常。

//Blocking socket receiving data
var isPaused = true;
chrome.sockets.tcp.setPaused(socketId, isPaused, function(){
    //do something after pause a socket
});

如果想解除阻止,将上述代码中的isPaused设为false即可。

长连接

chrome.sockets.tcp.setKeepAlive(socketId, enable, delay, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after set keep-alive
});

当enable设为true时启用保持连接功能,delay定义了最后接收的数据包与第一次探测之间的时间,以秒为单位。

禁用和启用纳格算法

纳格算法以减少封包传送量来增进TCP/IP网络的效能。可以通过setNoDelay方法禁用或启用。

chrome.sockets.tcp.setNoDelay(socketId, noDelay, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after set no delay
});

当noDelay设为true时设置TCP连接的TCP_NODELAY标志,禁用纳格算法。

断开连接

chrome.sockets.tcp.disconnect(socketId, function(){
    //do something after disconnect a connection
});

关闭socket

当一个socket不再被使用了我们应该关闭它。

chrome.socket.tcp.close(socketId, function(){
    //do something after close a socket
});

下面我们来试着封装一个tcp类,并在之后的内容逐步扩充它:

function tcp(){
    var _tcp = chrome.sockets.tcp;
    this.option = {},
    this.socketId = 0,
    this.create = function(callback){
        _tcp.create(this.option, function(socketInfo){
            this.socketId = socketInfo.socketId;
            callback();
        }.bind(this));
    }.bind(this),
    this.update = function(){
        _tcp.update(this.socketId, newSocketOption, callback);
    }.bind(this),
    this.pause = function(isPaused, callback){
        _tcp.setPaused(this.socketId, isPaused, callback);
    }.bind(this),
    this.keepAlive = function(enable, delay, callback){
        _tcp.setKeepAlive(this.socketId, enable, delay, function(code){
            if(code<0){
                this.error(code);
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.noDelay = function(noDelay, callback){
        _tcp.setNoDelay(this.socketId, noDelay, function(code){
            if(code<0){
                this.error(code);
            }
            else{
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.disconnect = function(callback){
        _tcp.disconnect(this.socketId, callback);
    }.bind(this),
    this.close = function(callback){
        _tcp.close(this.socketId, callback);
    }.bind(this),
    this.error = function(code){
        console.log('An error occurred with code '+code);
    },
    this.init = function(callback){
        this.create(callback);
    }.bind(this)
}

调用时指定socket属性就可以进行初始化了:

var tcpSocket = new tcp();
tcpSocket.option = {
    persistent: true
};
tcpSocket.init(function(){
    //We'll do something after tcp socket init later
});

9.2.2发送与接收数据

连接

chrome.sockets.tcp.connect(socketId, peerAddress, peerPort, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after bind a port
});

发送数据

Socket发送的数据类型为ArrayBuffer,对ArrayBuffer不熟悉的读者请参阅7.6.1节的内容。

chrome.sockets.tcp.send(socketId, data, function(info){
    //if info.resultCode is a negative value, an error occurred
    //otherwise do something after send some data
});

其中data为ArrayBuffer类型的数据。

接收数据

当socket接收到数据时,就会触发onReceive事件:

chrome.socket.tcp.onReceive.addListener(function(info){
    //We'll do something with info later
});

返回的info是一个对象,包括两个属性:socketId和data。其中data为ArrayBuffer类型数据。

处理异常

当网络出现问题时,会触发onReceiveError事件,同时socket会被阻断:

chrome.sockets.tcp.onReceiveError.addListener(function(info){
    //We'll do something with info later
});

与接收数据相似,info也是一个对象,但包含socketId和resultCode两个属性。

现在我们来把8.2.1中的tcp类完善一下,由于篇幅限制,将只写出添加或改动的部分:

function tcp(){
    this.connect = function(address, port, callback){
        _tcp.connect(this.socketId, address, port, function(){
            _tcp.onReceive.addListener(function(info){
                if(info.socketId==this.socketId){
                    this.receive(info);
                }
            }.bind(this));
            _tcp.onReceiveError.addListener(function(info){
                if(info.socketId==this.socketId){
                    this.error(info.resultCode);
                }
            }.bind(this));
        }.bind(this));
    }.bind(this),
    this.send = function(data, callback){
        _tcp.send(this.socketId, data, callback);
    }.bind(this),
    this.receive = function(info){
        console.log('Received data.');
    }
}

9.2.3获取socket

获取指定socket

chrome.sockets.tcp.getInfo(socketId, function(socketInfo){
    //do something with socketInfo
});

socketInfo是一个描述socket信息的对象,包括socketId、name、bufferSize、paused、connected、localAddress、localPort、peerAddress和peerPort。

获取全部活动的socket

chrome.sockets.tcp.getSockets(function(socketInfoArray){
    //do something with socketInfoArray
});

socketInfoArray是一个包含一个或多个socketInfo对象的数组。

将以上方法加入到tcp类中:

function tcp(){
    this.getInfo = function(callback){
        _tcp.getInfo(this.socketId, callback);
    }.bind(this),
    this.getSockets = function(callback){
        _tcp.getSockets (callback);
    }.bind(this)
}

 

9.3TCP Server

TCP Server可以绑定指定端口并被动地接收信息。

Chrome提供sockets.tcpServer接口使Chrome应用可以作为TCP服务器。要使用sockets.tcpServer接口需要在sockets域中声明tcpServer权限:

"sockets": {
    "tcpServer": {
        "listen": ":80"
    }
}

上面的代码表示应用可以通过监听本地80端口接收TCP消息。


9.3.1建立与关闭连接

创建socket

var socketOption = {
    persistent: true,
    name: 'tcpSocket'
};
chrome.sockets.tcpServer.create(socketOption, function(socketInfo){
    //We'll do the next step later
});

socketOption是一个可选参数,其中persistent代表Chrome应用的生命周期结束后(参见6.4节)这个socket是否还依然处于开启状态。

当socket创建完毕就返回一个对象,这个对象包含代表这个socket的唯一id,之后对socket的操作都将根据这个id。

更新socket属性

此处说的属性是指创建socket时提到的socketOptions。通过update方法可以更新socket的属性:

chrome.sockets.tcpServer.update(socketId, newSocketOption, function(){
    //do something when update complete
});

阻止和解除阻止socket接收数据

当一个socket被阻止后,将不会触发消息接收事件,解除阻止后将恢复正常。

//Blocking socket receiving data
var isPaused = true;
chrome.sockets.tcpServer.setPaused(socketId, isPaused, function(){
    //do something after pause a socket
});

如果想解除阻止,将上述代码中的isPaused设为false即可。

断开连接

chrome.sockets.tcpServer.disconnect(socketId, function(){
    //do something after disconnect a connection
});

关闭socket

当一个socket不再被使用了我们应该关闭它。

chrome.socket.tcpServer.close(socketId, function(){
    //do something after close a socket
});

下面我们来试着封装一个tcpServer类,并在之后的内容逐步扩充它:

function tcpServer(){
    var _tcpServer = chrome.sockets.tcpServer;
    this.option = {},
    this.socketId = 0,
    this.create = function(callback){
        _tcpServer.create(this.option, function(socketInfo){
            this.socketId = socketInfo.socketId;
            callback();
        }.bind(this));
    }.bind(this),
    this.update = function(){
        _tcpServer.update(this.socketId, newSocketOption, callback);
    }.bind(this),
    this.pause = function(isPaused, callback){
        _tcpServer.setPaused(this.socketId, isPaused, callback);
    }.bind(this),
    this.disconnect = function(callback){
        _tcpServer.disconnect(this.socketId, callback);
    }.bind(this),
    this.close = function(callback){
        _tcpServer.close(this.socketId, callback);
    }.bind(this),
    this.init = function(callback){
        this.create(callback);
    }.bind(this)
}

调用时指定socket属性就可以进行初始化了:

var tcpSocket = new tcpServer();
tcpSocket.option = {
    persistent: true
};
tcpSocket.init(function(){
    //We'll do something after tcpSocket socket init later
});

9.3.2监听数据

监听端口

chrome.sockets.tcpServer.listen(socketId, address, port, backlog, function(code){
    //if a negative value is returned, an error occurred
    //otherwise do something after listen complete
});

address和port分别是监听本地的地址和端口,此处的端口不要使用0,因为这样并不知道具体监听的端口。

接受连接

chrome.sockets.tcpServer.onAccept.addListener(function(info){
    //do something when a connection has been made to the server socket
});

其中info包含两个属性,socketId和clientSocketId。clientSocketId是服务器主动与客户端建立的新socket连接,通过操作此socket可以向客户端发送数据。clientSocketId只能使用chrome.sockets.tcp接口操作,而不能使用chrome.sockets.tcpServer接口。另外clientSocketId指向的socket处于阻止状态,它并不能接收客户端发回的消息,除非手动执行setPaused方法解除阻止。

处理异常

当网络出现问题时,会触发onAcceptError事件,同时socket会被阻断:

chrome.sockets.tcpServer.onAcceptError.addListener(function(info){
    //do something with info
});

与监测连接相似,info也是一个对象,但包含socketId和resultCode两个属性。

现在我们来把8.3.1中的tcpServer类完善一下,由于篇幅限制,将只写出添加或改动的部分:

function tcpServer(){
    this.listen = function(address, port, callback){
        _tcpServer.listen(this.socketId, address, port, function(code){
            if(code<0){
                this.error(code);
                return false;
            }
            else{
                _tcpServer.onAccept.addListener(function(info){
                    if(info.socketId==this.socketId){
                        this.accept(info);
                    }
                }.bind(this));
                _tcpServer.onAcceptError.addListener(function(info){
                    if(info.socketId==this.socketId){
                        this.error(info.resultCode);
                    }
                }.bind(this));
                callback();
            }
        }.bind(this));
    }.bind(this),
    this.error = function(code){
        console.log('An error occurred with code '+code);
    },
    this.accept = function(info){
        console.log('New connection.');
    }
}

注意,上面的代码中_tcpServer.listen后的参数没有backlog,这不是笔误。因为backlog是可选参数,如果不指定会由系统自动管理。系统会设定一个保证大多数程序正常运行的值,所以不需要手动设定。

9.3.3获取socket

获取指定socket

chrome.sockets.tcpServer.getInfo(socketId, function(socketInfo){
    //do something with socketInfo
});

socketInfo是一个描述socket信息的对象,包括socketId、name、paused、persistent、localAddress和localPort。

获取全部活动的socket

chrome.sockets.tcpServer.getSockets(function(socketInfoArray){
    //do something with socketInfoArray
});

socketInfoArray是一个包含一个或多个socketInfo对象的数组。

将以上方法加入到tcpServer类中:

function tcpServer(){
    this.getInfo = function(callback){
        _tcpServer.getInfo(this.socketId, callback);
    }.bind(this),
    this.getSockets = function(callback){
        _tcpServer.getSockets (callback);
    }.bind(this)
}

 

9.3.4HTTP Server

通过8.3.2和8.3.3两小节的内容我们了解的TCP在Chrome应用中的使用,本小节将实战编写一个HTTP服务器。

HTTP服务器需要监听TCP连接同时使用TCP与客户端进行通信,所以需要tcp和tcpServer权限:

{
    "app": {
        "background": {
            "scripts": ["tcp.js", "tcpServer.js", "background.js"]
        }
    },
    "manifest_version": 2,
    "name": "HTTP Server",
    "version": "1.0",
    "description": "An HTTP server.",
    "icons": {
        "128": "http_server.png"
    },
    "sockets": {
        "tcp": {
            "connect": "*"
        },
        "tcpServer": {
            "listen": ":80"
        }
    }
}

其中tcp.js和tcpServer.js就是前两节中我们写的两个类。下面来写background.js。

首先需要创建tcpServerSocket:

var tcpServerSocket = new tcpServer();
tcpServerSocket.option = {
    persistent: true
};
tcpServerSocket.accept = handleAccept.bind(tcpServerSocket);
tcpServerSocket.init(function(){
    tcpServerSocket.listen('127.0.0.1', 80, function(){
        console.log('Listening 127.0.0.1:80...');
    });
});

创建完成后来编写监听连接的函数handleAccept:

function handleAccept(info){
    if(info.socketId==this.socketId){
        var _tcp = chrome.sockets.tcp;
        var tcpSocket = new tcp();
        tcpSocket.socketId = info.clientSocketId;
        tcpSocket.keepAlive(true, 5, function(){
            _tcp.onReceive.addListener(function(info){
                if(info.socketId==tcpSocket.socketId){
                    tcpSocket.receive(info);
                }
            });
            _tcp.onReceiveError.addListener(function(info){
                if(info.socketId==tcpSocket.socketId){
                    tcpSocket.error(info.resultCode);
                }
            });
            tcpSocket.receive = handleRequest.bind(tcpSocket);
            tcpSocket.pause(false, function(){
                console.log('Receiving data...');
            });
        });
    }
}

在handleAccept函数中,我们获取到clientSocketId后创建了一个新的tcp对象tcpSocket,并将clientSocketId的值赋给了tcpSocket的socketId,这样对tcpSocket的操作就是对clientSocketId指向的TCP socket了。

之后设定了keep-alive属性,并解除了此TCP socket的阻止状态开始接收数据。接收到的数据通过handleRequest函数来处理。下面来编写handleRequest函数:

function handleRequest(info){
    var header = ab2str(info.data);
    header = header.split("\r\n").join('<br />');
    var body = "<h1>It Works!</h1>"+
               "<hr />"+
               "Request Header:<br />"+header;
    var respondse = "HTTP/1.1 200 OK\r\n"+
                    "Connection: Keep-Alive\r\n"+
                    "Content-Length: "+body.length+"\r\n"+
                    "Content-Type: text/html\r\n"+
                    "Connection: close\r\n\r\n"+body;
    respondse = str2ab(respondse);
    this.send(respondse, function(){
        console.log('Sent.');
        this.close(function(){
            console.log('Closed.');
        })
    }.bind(this));
}

handleRequest函数将得到的用户请求的HTTP头展示给用户,同时在最前端显示“It Works!”。

最后来编写ArrayBuffer和字符串直接转换的函数,本例中使用到的转换函数与8.1.5节中所使用的略有不同:

function str2ab(str){
    var buf = new ArrayBuffer(str.length);
    bufView = new Uint8Array(buf);
    for(var i=0; i<str.length; i++){
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}
function ab2str(buf){
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

在8.1.5节中我们使用的是Uint16Array这个ArrayBufferView,因为在JavaScript中一个字符占16位,所以应用两个字节来储存,但在HTTP协议中一个字符占8位,所以要用1个字节来储存,这点请读者注意。

01fiqW8kHp5t.png

HTTP Server运行截图

本节讲到的应用源码可以通过https://github.com/sneezry/chrome_extensions_and_apps_programming/tree/master/http_server下载。

9.4WebSocket

WebSocket与本章前三节介绍的内容不同,它是HTML5原生支持的功能。使用WebSocket需要外部服务器的支持。

在使用WebSocket通信前需要先连接到外部的WebSocket服务器:

var connection = new WebSocket('ws://127.0.0.1');

当连接打开后会触发onopen事件:

connection.onopen = function(){
    //do something when the connection is open
}

向服务器发送数据使用send方法:

connection.send(data);

其中data可以是ArrayBuffer、Blob或字符串。

当接收到来自服务器的数据时会触发onmessage事件:

connection.onmessage = function(result){
    //do something with result
}

其中result是一个对象,可以通过result.data访问数据。如果传送的数据是二进制数据,还可以通过result.data.byteLength和result.data.binaryType获取二进制数据的长度和类型(ArrayBuffer或Blob)。

当WebSocket连接发生异常时会触发onerror事件:

connection.onerror = function(error){
    console.log(error);
};

更多有关WebSocket的内容可以参见http://www.html5rocks.com/zh/tutorials/websockets/basics/。












-----------------------------------------------------
转载请注明来源此处
原地址:#

-----网友评论----
暂无评论
-----发表评论----
微网聚博客乐园 ©2014 blog.mn886.net 鲁ICP备14012923号   网站导航