實(shí)現(xiàn)功能:將socket.io服務(wù)器的二進(jìn)制圖片流利用img標(biāo)簽顯示在瀏覽器端弹灭,形成視頻铐尚。
所用到的知識(shí):WebSocket協(xié)議裳食,Node.js的Socket.io類(lèi)庫(kù)玫恳,瀏覽器的對(duì)象URL機(jī)制辨赐。
WebSocket協(xié)議
WebSocket是HTML5的一種新協(xié)議, 它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信京办。以前很多網(wǎng)站為了實(shí)現(xiàn)實(shí)時(shí)通信掀序,所用到的技術(shù)都是輪詢(xún),輪詢(xún)是在特定的時(shí)間間隔惭婿,由瀏覽器對(duì)服務(wù)器發(fā)出http請(qǐng)求不恭,然后由服務(wù)器返回最新的數(shù)據(jù)給瀏覽器。很顯然财饥,這種傳統(tǒng)模式的http請(qǐng)求需要瀏覽器端不斷的發(fā)出請(qǐng)求换吧,http請(qǐng)求的header是非常長(zhǎng)的,如果只是要發(fā)一個(gè)很小的數(shù)據(jù)佑力,會(huì)非常浪費(fèi)帶寬式散。在WebSocket API,瀏覽器和服務(wù)器只需要做一個(gè)握手的動(dòng)作就能實(shí)現(xiàn)全雙工通訊打颤。
Socket.io類(lèi)庫(kù)
瀏覽器的兼容問(wèn)題以及網(wǎng)絡(luò)中間物(代理服務(wù)暴拄、防火墻)問(wèn)題不支持WebSocket,這時(shí)Socket.io的出現(xiàn)就是為了完善這些問(wèn)題编饺。Socket.io是一個(gè)簡(jiǎn)單的小類(lèi)庫(kù)乖篷,該類(lèi)庫(kù)實(shí)現(xiàn)的功能類(lèi)似于Node.js中的net模塊實(shí)現(xiàn)的功能,這些功能包括WebSocket通信透且、XHR輪詢(xún)撕蔼、JSONP輪詢(xún)等豁鲤。該類(lèi)庫(kù)的一個(gè)顯著特征是在服務(wù)器端與瀏覽器之間提供一共享接口,也就是說(shuō)鲸沮,當(dāng)客戶(hù)端與服務(wù)端建立連接后琳骡,在處理消息時(shí),開(kāi)發(fā)者可以在客戶(hù)端使用服務(wù)器端JavaScript代碼讼溺。
下面是個(gè)小案例:
// socket.js
var http = require('http');
var sio = require('socket.io');
var fs = require('fs');
var server = http.createServer(function(req,res){
res.writeHead(200,{'Content-type':'text/html'});
res.end(fs.readFileSync('./index.html'));
});
server.listen(1337);
var socket = sio.listen(server);
socket.on('connectiion',function(socket){
console.log('客戶(hù)端建立連接');
socket.send('你好');
socket.on('message',function(msg){
console.log('接收到一個(gè)消息:',msg);
});
socket.on('disconnect',function(){
console.log('客戶(hù)端斷開(kāi)連接');
});
});
// index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://cdn.bootcss.com/socket.io/1.7.3/socket.io.js"></script>
<script>
var socket = io.connect();
socket.on('message',function(data){
console.log(data);
socket.send('消息已接收到');
});
socket.on('disconnect',function(){
console.log('服務(wù)器斷開(kāi)連接');
});
</script>
</head>
<body>
</body>
</html>
瀏覽器的對(duì)象URL機(jī)制
對(duì)象URL機(jī)制--后臺(tái)服務(wù)器直接發(fā)送JPEG圖像數(shù)據(jù)的原始二進(jìn)制數(shù)據(jù)楣号,瀏覽器收到數(shù)據(jù)后把圖像數(shù)據(jù)解析成img元素能過(guò)引用的文件,能夠有效降低圖像數(shù)據(jù)傳輸?shù)恼加脦挕?/p>
objectURL=window.URL.createObjectURL(blob);
blob參數(shù)是一個(gè)文件對(duì)象或者Blob對(duì)象怒坯,Blob對(duì)象代表文件的二進(jìn)制數(shù)據(jù)炫狱,objectURL是生成的對(duì)象URL,這個(gè)URL的格式為"blob"開(kāi)頭的鏈接對(duì)象剔猿,一個(gè)對(duì)象的URL長(zhǎng)這個(gè)樣子:
![](blob:http%3A//localhost%3A4000/ffd23149-960f-4555-92a5-c3e1d6e9d1d9)
具體用法參考http://www.cnblogs.com/liulangmao/p/4262565.html
還有一種是對(duì)象URI機(jī)制视译,一般而言,img標(biāo)簽無(wú)法把圖像的二進(jìn)制數(shù)據(jù)解析成圖片文件归敬,這是由瀏覽器防止用戶(hù)直接引用用戶(hù)計(jì)算機(jī)本地文件的安全機(jī)制決定的酷含,但是瀏覽器有種解析data URI數(shù)據(jù)的方式,我們可以利用這種特性來(lái)解析圖像汪茧。data URI是一種直接把文件嵌入HTML文檔的方案第美,其格式如下:
data:[<mediatype>][;base64],<data>
上述<data>部分就是文件原始二進(jìn)制數(shù)據(jù)的base64編碼數(shù)據(jù),但是這種編碼得到的數(shù)據(jù)比原始數(shù)據(jù)大了1/3陆爽,會(huì)導(dǎo)致數(shù)據(jù)傳輸占用帶寬加大,因此扳缕,我們采用對(duì)象URL機(jī)制慌闭。
下面是將socket.io服務(wù)器的二進(jìn)制數(shù)據(jù)傳輸給瀏覽器的代碼(‘圖片的二進(jìn)制數(shù)據(jù)可以自己將本地圖片的二進(jìn)制數(shù)據(jù)輸進(jìn)去’):
var http = require('http');
var sio = require('socket.io');
var fs = require('fs');
var server = http.createServer(function(req,res){
res.writeHead(200,
{
'Content-type':'text/html'
}
);
res.end(fs.readFileSync('./img.html'));
});
server.listen(4000);
var io = sio.listen(server);
socket.on('connection',function(socket){
socket.emit('news',{shuju:'圖片的二進(jìn)制數(shù)據(jù)'});
})
瀏覽器端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="http://cdn.bootcss.com/socket.io/1.7.3/socket.io.js"></script>
<script>
var socket = io.connect();
socket.on('news',function(data){
var blob = new Blob([data.shuju],{"type":"image\/jpeg"});
var src = window.URL.createObjectURL(blob);
var img = document.createElement('img');
img.src = src;
img.onload = function(){
window.URL.revokeObjectURL(src);
};
document.body.appendChild(img);
});
</script>
</head>
<body>
<div id="data"></div>
</body>
</html>
由于是用本地圖片測(cè)試的,路徑會(huì)有問(wèn)題躯舔,會(huì)導(dǎo)致瀏覽器端顯示不出來(lái)圖片可以在控制臺(tái)查看是否正確:
要想從http服務(wù)器傳輸圖片數(shù)據(jù)給socket.io服務(wù)器驴剔,可以用node.js的進(jìn)程和子進(jìn)程解決,將socket.io代碼設(shè)為http部分的子進(jìn)程粥庄,即可實(shí)現(xiàn)傳輸丧失。
// http.js
var http = require('http');
var cp = require('child_process');
var n= cp.fork(__dirname+'/socket.js');
var server = http.createServer(function(req,res){
}).listen(3000,"localhost",function(){
n.send({shuju:'圖片二進(jìn)制數(shù)據(jù)'})});
//監(jiān)聽(tīng)事件
server.on('listening',function(){
console.log('服務(wù)器開(kāi)始監(jiān)聽(tīng)');
//server.close();
});
//建立連接
server.on('connection',function(socket){
console.log('客戶(hù)端已連接');
});
//服務(wù)器關(guān)閉時(shí)響應(yīng)
server.on('close',function(){
console.log('服務(wù)器已關(guān)閉');
});
//觸發(fā)錯(cuò)誤事件
server.on('error',function(){
if(e.code=='EADDRINUSE'){ //端口被占用的錯(cuò)誤代碼為EADDRINUSE
console.log('服務(wù)器端口已被占用');
}
});
由于JPEG編碼得到的圖像數(shù)據(jù)大小均不同,且TCP協(xié)議只是保證接收端接受的的比特是按序的惜互,但不能保證每次接收的數(shù)據(jù)的就是發(fā)送端發(fā)送的完整數(shù)據(jù)布讹,可能只是其中的一部分。因此训堆,node.js服務(wù)器接收時(shí)不能用長(zhǎng)度來(lái)指定接收一幀JPEG圖像數(shù)據(jù)的Buffer描验。此處使用了JPEG數(shù)據(jù)的最后兩個(gè)ASCII碼的217和255來(lái)區(qū)分兩幀圖像數(shù)據(jù),并使用Buffer把之前接收到的二進(jìn)制數(shù)據(jù)緩存起來(lái)最后拼接完成完整的JPEG幀數(shù)據(jù)坑鱼。
socket.js
var http = require('http');
var sio = require('socket.io');
var fs = require('fs');
var server = http.createServer(function(req,res){
res.writeHead(200,
{
'Content-type':'text/html'
}
);
res.end(fs.readFileSync('./img.html'));
});
server.listen(3000);
var io = sio.listen(server);
process.on('message',function(m){
var buffers=[];
io.sockets.on('connection',function(socket){
buffers.push[m.shuju];
var n_shu= m.shuju;
if(n_shu[n_shu.length-1]==217 && n_shu[n_shu.length-2]==255){
var n_buffer = Buffer.concat(buffers);
io.sockets.emit('news',m);
buffers=[];
}
});
});
瀏覽器端代碼不變膘流,以上便完成圖像的傳輸。