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的运行截图。
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个字节来储存,这点请读者注意。
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/。
-----------------------------------------------------
转载请注明来源此处
原地址:#
发表