websocket探索其与语音,图片的能力

websocket探索其与语音、图片的力量

2015/12/26 · JavaScript
· 3 评论 ·
websocket

初稿出处:
AlloyTeam   

说到websocket想比大家不会陌生,假如陌生的话也没涉及,一句话概括

“WebSocket protocol
是HTML5一种新的商谈。它完成了浏览器与服务器全双工通讯”

WebSocket相比较传统那几个服务器推技术简直好了太多,大家得以挥手向comet和长轮询那一个技术说拜拜啦,庆幸我们生活在所有HTML5的一代~

那篇小说大家将分三局地探索websocket

第一是websocket的广大使用,其次是全然自己创设服务器端websocket,最后是最紧要介绍利用websocket制作的两个demo,传输图片和在线语音聊天室,let’s
go

一、websocket常见用法

此间介绍两种自我以为大规模的websocket达成……(小心:本文建立在node上下文环境

1、socket.io

先给demo

JavaScript

var http = require(‘http’); var io = require(‘socket.io’); var server =
http.createServer(function(req, res) { res.writeHeader(200,
{‘content-type’: ‘text/html;charset=”utf-8″‘}); res.end();
}).listen(8888); var socket =.io.listen(server);
socket.sockets.on(‘connection’, function(socket) { socket.emit(‘xxx’,
{options}); socket.on(‘xxx’, function(data) { // do someting }); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require(‘http’);
var io = require(‘socket.io’);
 
var server = http.createServer(function(req, res) {
    res.writeHeader(200, {‘content-type’: ‘text/html;charset="utf-8"’});
    res.end();
}).listen(8888);
 
var socket =.io.listen(server);
 
socket.sockets.on(‘connection’, function(socket) {
    socket.emit(‘xxx’, {options});
 
    socket.on(‘xxx’, function(data) {
        // do someting
    });
});

信任通晓websocket的同室无法不驾驭socket.io,因为socket.io太有名了,也很棒,它本身对逾期、握手等都做了处理。我测度那也是促成websocket使用最多的格局。socket.io最最最非凡的一点就是优雅降级,当浏览器不匡助websocket时,它会在里边优雅降级为长轮询等,用户和开发者是不须求关爱具体落到实处的,很便利。

可是事情是有两面性的,socket.io因为它的应有尽有也带来了坑的地点,最珍贵的就是臃肿,它的包裹也给多少牵动了较多的电视揭橥冗余,而且优雅降级这一亮点,也陪同浏览器标准化的开展逐步失去了了不起

Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+

在这边不是指责说socket.io不佳,已经被淘汰了,而是有时候我们也足以考虑部分任何的落到实处~

 

2、http模块

正要说了socket.io臃肿,那现在就来说说便捷的,首先demo

JavaScript

var http = require(‘http’); var server = http.createServer();
server.on(‘upgrade’, function(req) { console.log(req.headers); });
server.listen(8888);

1
2
3
4
5
6
var http = require(‘http’);
var server = http.createServer();
server.on(‘upgrade’, function(req) {
console.log(req.headers);
});
server.listen(8888);

很粗略的兑现,其实socket.io内部对websocket也是如此落成的,但是前面帮大家封装了有些handle处理,那里我们也足以协调去丰硕,给出两张socket.io中的源码图

图片 1

图片 2

 

3、ws模块

末尾有个例证会用到,那里就提一下,前边具体看~

 

二、自己已毕一套server端websocket

刚巧说了三种常见的websocket落成格局,现在大家思想,对于开发者来说

websocket相对于传统http数据交互形式以来,增加了服务器推送的风云,客户端接收到事件再拓展对应处理,开发起来差别并不是太大啊

那是因为那一个模块已经帮大家将数量帧解析此处的坑都填好了,第二片段我们将尝试自己制作一套简便的服务器端websocket模块

谢谢次碳酸钴的钻研帮忙,自身在那里那部分只是简短说下,即使对此有趣味好奇的请百度【web技术研讨所】

团结达成服务器端websocket首要有两点,一个是应用net模块接受数据流,还有一个是对待官方的帧结构图解析数据,完毕那两局地就早已成功了百分之百的底层工作

首先给一个客户端发送websocket握手报文的抓包内容

客户端代码很粗略

JavaScript

ws = new WebSocket(“ws://127.0.0.1:8888”);

1
ws = new WebSocket("ws://127.0.0.1:8888");

图片 3

服务器端要本着这一个key验证,就是讲key加上一个特定的字符串后做两次sha1运算,将其结果转换为base64送回到

JavaScript

var crypto = require(‘crypto’); var WS =
‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’;
require(‘net’).createServer(function(o) { var key;
o.on(‘data’,function(e) { if(!key) { // 获取发送过来的KEY key =
e.toString().match(/Sec-WebSocket-Key: (.+)/)[1]; //
连接上WS这一个字符串,并做几回sha1运算,最后转换成Base64 key =
crypto.createHash(‘sha1’).update(key+WS).digest(‘base64’); //
输出再次来到给客户端的多少,那么些字段都是必须的 o.write(‘HTTP/1.1 101
Switching Protocols\r\n’); o.write(‘Upgrade: websocket\r\n’);
o.write(‘Connection: Upgrade\r\websocket探索其与语音,图片的能力。n’); // 这么些字段带上服务器处理后的KEY
o.write(‘Sec-WebSocket-Accept: ‘+key+’\r\n’); //
输出空行,使HTTP头截至 o.write(‘\r\n’); } }); }).listen(8888);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var crypto = require(‘crypto’);
var WS = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’;
 
require(‘net’).createServer(function(o) {
var key;
o.on(‘data’,function(e) {
if(!key) {
// 获取发送过来的KEY
key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
// 连接上WS这个字符串,并做一次sha1运算,最后转换成Base64
key = crypto.createHash(‘sha1’).update(key+WS).digest(‘base64’);
// 输出返回给客户端的数据,这些字段都是必须的
o.write(‘HTTP/1.1 101 Switching Protocols\r\n’);
o.write(‘Upgrade: websocket\r\n’);
o.write(‘Connection: Upgrade\r\n’);
// 这个字段带上服务器处理后的KEY
o.write(‘Sec-WebSocket-Accept: ‘+key+’\r\n’);
// 输出空行,使HTTP头结束
o.write(‘\r\n’);
}
});
}).listen(8888);

如此那般握手部分就已经到位了,前面就是多少帧解析与转变的活了

先看下官方提供的帧结构示意图

图片 4

概括介绍下

FIN为是还是不是甘休的标志

RSV为预留空间,0

opcode标识数据类型,是或不是分片,是或不是二进制解析,心跳包等等

付出一张opcode对应图

图片 5

MASK是还是不是利用掩码

Payload len和后边extend payload length表示数据长度,这一个是最劳顿的

PayloadLen唯有7位,换成无符号整型的话唯有0到127的取值,这么小的数值当然不能描述较大的数额,因而确定当数码长度小于或等于125时候它才作为数据长度的叙述,如若这些值为126,则时候背后的四个字节来囤积数据长度,假若为127则用后边多个字节来储存数据长度

Masking-key掩码

上面贴出解析数据帧的代码

JavaScript

function decodeDataFrame(e) { var i = 0, j,s, frame = { FIN: e[i]
>> 7, Opcode: e[i++] & 15, Mask: e[i] >> 7,
PayloadLength: e[i++] & 0x7F }; if(frame.PayloadLength === 126) {
frame.PayloadLength = (e[i++] << 8) + e[i++]; }
if(frame.PayloadLength === 127) { i += 4; frame.PayloadLength =
(e[i++] << 24) + (e[i++] << 16) + (e[i++] << 8)

  • e[i++]; } if(frame.Mask) { frame.MaskingKey = [e[i++], e[i++],
    e[i++], e[i++]]; for(j = 0, s = []; j < frame.PayloadLength;
    j++) { s.push(e[i+j] ^ frame.MaskingKey[j%4]); } } else { s =
    e.slice(i, i+frame.PayloadLength); } s = new Buffer(s); if(frame.Opcode
    === 1) { s = s.toString(); } frame.PayloadData = s; return frame; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function decodeDataFrame(e) {
var i = 0,
j,s,
frame = {
FIN: e[i] >> 7,
Opcode: e[i++] & 15,
Mask: e[i] >> 7,
PayloadLength: e[i++] & 0x7F
};
 
if(frame.PayloadLength === 126) {
frame.PayloadLength = (e[i++] << 8) + e[i++];
}
 
if(frame.PayloadLength === 127) {
i += 4;
frame.PayloadLength = (e[i++] << 24) + (e[i++] << 16) + (e[i++] << 8) + e[i++];
}
 
if(frame.Mask) {
frame.MaskingKey = [e[i++], e[i++], e[i++], e[i++]];
 
for(j = 0, s = []; j < frame.PayloadLength; j++) {
s.push(e[i+j] ^ frame.MaskingKey[j%4]);
}
} else {
s = e.slice(i, i+frame.PayloadLength);
}
 
s = new Buffer(s);
 
if(frame.Opcode === 1) {
s = s.toString();
}
 
frame.PayloadData = s;
return frame;
}

接下来是变化数据帧的

JavaScript

function encodeDataFrame(e) { var s = [], o = new
Buffer(e.PayloadData), l = o.length; s.push((e.FIN << 7) +
e.Opcode); if(l < 126) { s.push(l); } else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF); } else { s.push(127, 0, 0,
0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00)
>> 8, l&0xFF); } return Buffer.concat([new Buffer(s), o]); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function encodeDataFrame(e) {
var s = [],
o = new Buffer(e.PayloadData),
l = o.length;
 
s.push((e.FIN << 7) + e.Opcode);
 
if(l < 126) {
s.push(l);
} else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF);
} else {
s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00) >> 8, l&0xFF);
}
 
return Buffer.concat([new Buffer(s), o]);
}

都是按照帧结构示意图上的去处理,在那边不细讲,小说主要在下一些,假设对那块感兴趣的话可以活动web技术探究所~

 

三、websocket传输图片和websocket语音聊天室

正片环节到了,那篇作品最关键的要么展现一下websocket的一些应用境况

1、传输图片

咱俩先思考传输图片的步骤是怎么,首先服务器收到到客户端请求,然后读取图片文件,将二进制数据转载给客户端,客户端怎么样处理?当然是接纳FileReader对象了

先给客户端代码

JavaScript

var ws = new WebSocket(“ws://xxx.xxx.xxx.xxx:8888”); ws.onopen =
function(){ console.log(“握手成功”); }; ws.onmessage = function(e) { var
reader = new FileReader(); reader.onload = function(event) { var
contents = event.target.result; var a = new Image(); a.src = contents;
document.body.appendChild(a); } reader.readAsDataURL(e.data); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var ws = new WebSocket("ws://xxx.xxx.xxx.xxx:8888");
 
ws.onopen = function(){
    console.log("握手成功");
};
 
ws.onmessage = function(e) {
    var reader = new FileReader();
    reader.onload = function(event) {
        var contents = event.target.result;
        var a = new Image();
        a.src = contents;
        document.body.appendChild(a);
    }
    reader.readAsDataURL(e.data);
};

收起到信息,然后readAsDataURL,直接将图纸base64添加到页面中

转到服务器端代码

JavaScript

fs.readdir(“skyland”, function(err, files) { if(err) { throw err; }
for(var i = 0; i < files.length; i++) { fs.readFile(‘skyland/’ +
files[i], function(err, data) { if(err) { throw err; }
o.write(encodeImgFrame(data)); }); } }); function encodeImgFrame(buf) {
var s = [], l = buf.length, ret = []; s.push((1 << 7) + 2);
if(l < 126) { s.push(l); } else if(l < 0x10000) { s.push(126,
(l&0xFF00) >> 8, l&0xFF); } else { s.push(127, 0, 0, 0, 0,
(l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00)
>> 8, l&0xFF); } return Buffer.concat([new Buffer(s), buf]); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
fs.readdir("skyland", function(err, files) {
if(err) {
throw err;
}
for(var i = 0; i < files.length; i++) {
fs.readFile(‘skyland/’ + files[i], function(err, data) {
if(err) {
throw err;
}
 
o.write(encodeImgFrame(data));
});
}
});
 
function encodeImgFrame(buf) {
var s = [],
l = buf.length,
ret = [];
 
s.push((1 << 7) + 2);
 
if(l < 126) {
s.push(l);
} else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF);
} else {
s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00) >> 8, l&0xFF);
}
 
return Buffer.concat([new Buffer(s), buf]);
}

注意s.push((1 << 7) +
2)
这一句,那里非凡直接把opcode写死了为2,对于Binary
Frame,那样客户端接收到多少是不会尝试举行toString的,否则会报错~

代码很容易,在此处向大家分享一下websocket传输图片的快慢怎样

测试很多张图片,总共8.24M

普通静态资源服务器须要20s左右(服务器较远)

cdn需要2.8s左右

那我们的websocket方式呢??!

答案是相同要求20s左右,是否很失望……速度就是慢在传输上,并不是服务器读取图片,本机上同样的图纸资源,1s左右可以落成……那样看来数据流也不知所可冲破距离的限量升高传输速度

下边咱们来探望websocket的另一个用法~

 

用websocket搭建语音聊天室

先来整理一下口音聊天室的效益

用户进入频道随后从Mike风输入音频,然后发送给后台转载给频道里面的其余人,其余人接收到音信举办播报

看起来困难在三个地点,首个是节奏的输入,第二是吸收到数量流进行广播

先说音频的输入,那里运用了HTML5的getUserMedia方法,不过注意了,本条点子上线是有大坑的,最终说,先贴代码

JavaScript

if (navigator.getUserMedia) { navigator.getUserMedia( { audio: true },
function (stream) { var rec = new SRecorder(stream); recorder = rec; })
}

1
2
3
4
5
6
7
8
if (navigator.getUserMedia) {
    navigator.getUserMedia(
        { audio: true },
        function (stream) {
            var rec = new SRecorder(stream);
            recorder = rec;
        })
}

先是个参数是{audio:
true},只启用音频,然后成立了一个SRecorder对象,后续的操作基本上都在那几个目的上进行。此时只要代码运行在地面的话浏览器应该指示您是或不是启用迈克风输入,确定今后就开动了

接下去大家看下SRecorder构造函数是吗,给出首要的有的

JavaScript

var SRecorder = function(stream) { …… var context = new AudioContext();
var audioInput = context.createMediaStreamSource(stream); var recorder =
context.createScriptProcessor(4096, 1, 1); …… }

1
2
3
4
5
6
7
var SRecorder = function(stream) {
    ……
   var context = new AudioContext();
    var audioInput = context.createMediaStreamSource(stream);
    var recorder = context.createScriptProcessor(4096, 1, 1);
    ……
}

奥迪(Audi)oContext是一个旋律上下文对象,有做过声音过滤处理的校友应该精通“一段音频到达扬声器进行播报以前,半路对其开展拦阻,于是大家就拿走了旋律数据了,这几个拦截工作是由window.奥迪(Audi)oContext来做的,我们具备对旋律的操作都基于那么些目的”,大家得以透过奥迪(Audi)oContext创设分化的奥迪oNode节点,然后添加滤镜播放特其余鸣响

录音原理一样,大家也亟需走奥迪oContext,然则多了一步对Mike风音频输入的收受上,而不是像往常处理音频一下用ajax请求音频的ArrayBuffer对象再decode,迈克风的收受须要用到createMediaStreamSource方法,注意那一个参数就是getUserMedia方法第一个参数的参数

再说createScriptProcessor方法,它官方的讲演是:

Creates a ScriptProcessorNode, which can be used for direct audio
processing via JavaScript.

——————

蕴含下就是这么些法子是使用JavaScript去处理音频采集操作

毕竟到点子采集了!胜利就在前方!

接下去让大家把迈克风的输入和拍子采集相连起来

JavaScript

audioInput.connect(recorder); recorder.connect(context.destination);

1
2
audioInput.connect(recorder);
recorder.connect(context.destination);

context.destination官方表达如下

The destination property of
the AudioContext interface
returns
an AudioDestinationNoderepresenting
the final destination of all audio in the context.

——————

context.destination重返代表在条件中的音频的尾声目标地。

好,到了那儿,大家还亟需一个监听音频采集的事件

JavaScript

recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0)); }

1
2
3
recorder.onaudioprocess = function (e) {
    audioData.input(e.inputBuffer.getChannelData(0));
}

audioData是一个对象,那个是在网上找的,我就加了一个clear方法因为后边会用到,主要有极度encodeWAV方法很赞,外人举办了往往的旋律压缩和优化,这些最后会陪伴完整的代码一起贴出来

这时一切用户进入频道随后从Mike风输入音频环节就早已成功啦,上面就该是向服务器端发送音频流,稍微有点蛋疼的来了,刚才大家说了,websocket通过opcode分化可以代表回去的数据是文本仍旧二进制数据,而我们onaudioprocess中input进去的是数组,最终播放音响要求的是Blob,{type:
‘audio/wav’}的对象,那样我们就非得要在殡葬以前将数组转换成WAV的Blob,此时就用到了地点说的encodeWAV方法

服务器就像是很粗略,只要转载就行了

地面测试确实可以,唯独天坑来了!将先后跑在服务器上时候调用getUserMedia方法提示我不可以不在一个安然无恙的环境,也就是须求https,那表示ws也务必换成wss……为此服务器代码就从不使用我们协调包装的握手、解析和编码了,代码如下

JavaScript

var https = require(‘https’); var fs = require(‘fs’); var ws =
require(‘ws’); var userMap = Object.create(null); var options = { key:
fs.readFileSync(‘./privatekey.pem’), cert:
fs.readFileSync(‘./certificate.pem’) }; var server =
https.createServer(options, function(req, res) { res.writeHead({
‘Content-Type’ : ‘text/html’ }); fs.readFile(‘./testaudio.html’,
function(err, data) { if(err) { return ; } res.end(data); }); }); var
wss = new ws.Server({server: server}); wss.on(‘connection’, function(o)
{ o.on(‘message’, function(message) { if(message.indexOf(‘user’) === 0)
{ var user = message.split(‘:’)[1]; userMap[user] = o; } else {
for(var u in userMap) { userMap[u].send(message); } } }); });
server.listen(8888);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var https = require(‘https’);
var fs = require(‘fs’);
var ws = require(‘ws’);
var userMap = Object.create(null);
var options = {
    key: fs.readFileSync(‘./privatekey.pem’),
    cert: fs.readFileSync(‘./certificate.pem’)
};
var server = https.createServer(options, function(req, res) {
    res.writeHead({
        ‘Content-Type’ : ‘text/html’
    });
 
    fs.readFile(‘./testaudio.html’, function(err, data) {
        if(err) {
            return ;
        }
 
        res.end(data);
    });
});
 
var wss = new ws.Server({server: server});
 
wss.on(‘connection’, function(o) {
    o.on(‘message’, function(message) {
if(message.indexOf(‘user’) === 0) {
    var user = message.split(‘:’)[1];
    userMap[user] = o;
} else {
    for(var u in userMap) {
userMap[u].send(message);
    }
}
    });
});
 
server.listen(8888);

代码仍旧很简短的,使用https模块,然后用了启幕说的ws模块,userMap是效仿的频段,只兑现转载的中坚作用

选择ws模块是因为它万分https完毕wss实在是太便宜了,和逻辑代码0争持

https的搭建在那里就不提了,重如若索要私钥、CSR证书签名和证件文件,感兴趣的校友能够明白下(不过不了然的话在现网环境也用持续getUserMedia……)

下边是完整的前端代码

JavaScript

var a = document.getElementById(‘a’); var b =
document.getElementById(‘b’); var c = document.getElementById(‘c’);
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia; var gRecorder = null; var audio =
document.querySelector(‘audio’); var door = false; var ws = null;
b.onclick = function() { if(a.value === ”) { alert(‘请输入用户名’);
return false; } if(!navigator.getUserMedia) {
alert(‘抱歉您的装置无马耳他语音聊天’); return false; }
SRecorder.get(function (rec) { gRecorder = rec; }); ws = new
WebSocket(“wss://x.x.x.x:8888”); ws.onopen = function() {
console.log(‘握手成功’); ws.send(‘user:’ + a.value); }; ws.onmessage =
function(e) { receive(e.data); }; document.onkeydown = function(e) {
if(e.keyCode === 65) { if(!door) { gRecorder.start(); door = true; } }
}; document.onkeyup = function(e) { if(e.keyCode === 65) { if(door) {
ws.send(gRecorder.getBlob()); gRecorder.clear(); gRecorder.stop(); door
= false; } } } } c.onclick = function() { if(ws) { ws.close(); } } var
SRecorder = function(stream) { config = {}; config.sampleBits =
config.smapleBits || 8; config.sampleRate = config.sampleRate || (44100
/ 6); var context = new 奥迪oContext(); var audioInput =
context.createMediaStreamSource(stream); var recorder =
context.createScriptProcessor(4096, 1, 1); var audioData = { size: 0
//录音文件长度 , buffer: [] //录音缓存 , inputSampleRate:
context.sampleRate //输入采样率 , input萨姆pleBits: 16 //输入采样数位 8,
16 , outputSampleRate: config.sampleRate //输出采样率 , outut萨姆pleBits:
config.sampleBits //输出采样数位 8, 16 , clear: function() { this.buffer
= []; this.size = 0; } , input: function (data) { this.buffer.push(new
Float32Array(data)); this.size += data.length; } , compress: function ()
{ //合并压缩 //合并 var data = new Float32Array(this.size); var offset =
0; for (var i = 0; i < this.buffer.length; i++) {
data.set(this.buffer[i], offset); offset += this.buffer[i].length; }
//压缩 var compression = parseInt(this.inputSampleRate /
this.outputSampleRate); var length = data.length / compression; var
result = new Float32Array(length); var index = 0, j = 0; while (index
< length) { result[index] = data[j]; j += compression; index++; }
return result; } , encodeWAV: function () { var sampleRate =
Math.min(this.inputSampleRate, this.outputSampleRate); var sampleBits =
Math.min(this.inputSampleBits, this.oututSampleBits); var bytes =
this.compress(); var dataLength = bytes.length * (sampleBits / 8); var
buffer = new ArrayBuffer(44 + dataLength); var data = new
DataView(buffer); var channelCount = 1;//单声道 var offset = 0; var
writeString = function (str) { for (var i = 0; i < str.length; i++) {
data.setUint8(offset + i, str.charCodeAt(i)); } }; // 资源调换文件标识符
writeString(‘RIFF’); offset += 4; //
下个地点开始到文件尾总字节数,即文件大小-8 data.setUint32(offset, 36 +
dataLength, true); offset += 4; // WAV文件申明 writeString(‘WAVE’);
offset += 4; // 波形格式标志 writeString(‘fmt ‘); offset += 4; //
过滤字节,一般为 0x10 = 16 data.setUint32(offset, 16, true); offset += 4;
// 格式种类 (PCM情势采样数据) data.setUint16(offset, 1, true); offset +=
2; // 通道数 data.setUint16(offset, channelCount, true); offset += 2; //
采样率,每秒样本数,表示每个通道的播放速度 data.setUint32(offset,
sampleRate, true); offset += 4; // 波形数据传输率 (每秒平均字节数)
单声道×每秒数据位数×每样本数据位/8 data.setUint32(offset, channelCount
* sampleRate * (sampleBits / 8), true); offset += 4; // 快数据调整数
采样两遍占用字节数 单声道×每样本的数据位数/8 data.setUint16(offset,
channelCount * (sampleBits / 8), true); offset += 2; // 每样本数量位数
data.setUint16(offset, sampleBits, true); offset += 2; // 数据标识符
writeString(‘data’); offset += 4; // 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength, true); offset += 4; // 写入采样数据
if (sampleBits === 8) { for (var i = 0; i < bytes.length; i++,
offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); var val = s
< 0 ? s * 0x8000 : s * 0x7FFF; val = parseInt(255 / (65535 / (val +
32768))); data.setInt8(offset, val, true); } } else { for (var i = 0; i
< bytes.length; i++, offset += 2) { var s = Math.max(-1, Math.min(1,
bytes[i])); data.setInt16(offset, s < 0 ? s * 0x8000 : s *
0x7FFF, true); } } return new Blob([data], { type: ‘audio/wav’ }); }
}; this.start = function () { audioInput.connect(recorder);
recorder.connect(context.destination); } this.stop = function () {
recorder.disconnect(); } this.getBlob = function () { return
audioData.encodeWAV(); } this.clear = function() { audioData.clear(); }
recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0)); } }; SRecorder.get =
function (callback) { if (callback) { if (navigator.getUserMedia) {
navigator.getUserMedia( { audio: true }, function (stream) { var rec =
new SRecorder(stream); callback(rec); }) } } } function receive(e) {
audio.src = window.URL.createObjectURL(e); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
var a = document.getElementById(‘a’);
var b = document.getElementById(‘b’);
var c = document.getElementById(‘c’);
 
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
 
var gRecorder = null;
var audio = document.querySelector(‘audio’);
var door = false;
var ws = null;
 
b.onclick = function() {
    if(a.value === ”) {
        alert(‘请输入用户名’);
        return false;
    }
    if(!navigator.getUserMedia) {
        alert(‘抱歉您的设备无法语音聊天’);
        return false;
    }
 
    SRecorder.get(function (rec) {
        gRecorder = rec;
    });
 
    ws = new WebSocket("wss://x.x.x.x:8888");
 
    ws.onopen = function() {
        console.log(‘握手成功’);
        ws.send(‘user:’ + a.value);
    };
 
    ws.onmessage = function(e) {
        receive(e.data);
    };
 
    document.onkeydown = function(e) {
        if(e.keyCode === 65) {
            if(!door) {
                gRecorder.start();
                door = true;
            }
        }
    };
 
    document.onkeyup = function(e) {
        if(e.keyCode === 65) {
            if(door) {
                ws.send(gRecorder.getBlob());
                gRecorder.clear();
                gRecorder.stop();
                door = false;
            }
        }
    }
}
 
c.onclick = function() {
    if(ws) {
        ws.close();
    }
}
 
var SRecorder = function(stream) {
    config = {};
 
    config.sampleBits = config.smapleBits || 8;
    config.sampleRate = config.sampleRate || (44100 / 6);
 
    var context = new AudioContext();
    var audioInput = context.createMediaStreamSource(stream);
    var recorder = context.createScriptProcessor(4096, 1, 1);
 
    var audioData = {
        size: 0          //录音文件长度
        , buffer: []     //录音缓存
        , inputSampleRate: context.sampleRate    //输入采样率
        , inputSampleBits: 16       //输入采样数位 8, 16
        , outputSampleRate: config.sampleRate    //输出采样率
        , oututSampleBits: config.sampleBits       //输出采样数位 8, 16
        , clear: function() {
            this.buffer = [];
            this.size = 0;
        }
        , input: function (data) {
            this.buffer.push(new Float32Array(data));
            this.size += data.length;
        }
        , compress: function () { //合并压缩
            //合并
            var data = new Float32Array(this.size);
            var offset = 0;
            for (var i = 0; i < this.buffer.length; i++) {
                data.set(this.buffer[i], offset);
                offset += this.buffer[i].length;
            }
            //压缩
            var compression = parseInt(this.inputSampleRate / this.outputSampleRate);
            var length = data.length / compression;
            var result = new Float32Array(length);
            var index = 0, j = 0;
            while (index < length) {
                result[index] = data[j];
                j += compression;
                index++;
            }
            return result;
        }
        , encodeWAV: function () {
            var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);
            var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);
            var bytes = this.compress();
            var dataLength = bytes.length * (sampleBits / 8);
            var buffer = new ArrayBuffer(44 + dataLength);
            var data = new DataView(buffer);
 
            var channelCount = 1;//单声道
            var offset = 0;
 
            var writeString = function (str) {
                for (var i = 0; i < str.length; i++) {
                    data.setUint8(offset + i, str.charCodeAt(i));
                }
            };
 
            // 资源交换文件标识符
            writeString(‘RIFF’); offset += 4;
            // 下个地址开始到文件尾总字节数,即文件大小-8
            data.setUint32(offset, 36 + dataLength, true); offset += 4;
            // WAV文件标志
            writeString(‘WAVE’); offset += 4;
            // 波形格式标志
            writeString(‘fmt ‘); offset += 4;
            // 过滤字节,一般为 0x10 = 16
            data.setUint32(offset, 16, true); offset += 4;
            // 格式类别 (PCM形式采样数据)
            data.setUint16(offset, 1, true); offset += 2;
            // 通道数
            data.setUint16(offset, channelCount, true); offset += 2;
            // 采样率,每秒样本数,表示每个通道的播放速度
            data.setUint32(offset, sampleRate, true); offset += 4;
            // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
            data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
            // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
            data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
            // 每样本数据位数
            data.setUint16(offset, sampleBits, true); offset += 2;
            // 数据标识符
            writeString(‘data’); offset += 4;
            // 采样数据总数,即数据总大小-44
            data.setUint32(offset, dataLength, true); offset += 4;
            // 写入采样数据
            if (sampleBits === 8) {
                for (var i = 0; i < bytes.length; i++, offset++) {
                    var s = Math.max(-1, Math.min(1, bytes[i]));
                    var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
                    val = parseInt(255 / (65535 / (val + 32768)));
                    data.setInt8(offset, val, true);
                }
            } else {
                for (var i = 0; i < bytes.length; i++, offset += 2) {
                    var s = Math.max(-1, Math.min(1, bytes[i]));
                    data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
                }
            }
 
            return new Blob([data], { type: ‘audio/wav’ });
        }
    };
 
    this.start = function () {
        audioInput.connect(recorder);
        recorder.connect(context.destination);
    }
 
    this.stop = function () {
        recorder.disconnect();
    }
 
    this.getBlob = function () {
        return audioData.encodeWAV();
    }
 
    this.clear = function() {
        audioData.clear();
    }
 
    recorder.onaudioprocess = function (e) {
        audioData.input(e.inputBuffer.getChannelData(0));
    }
};
 
SRecorder.get = function (callback) {
    if (callback) {
        if (navigator.getUserMedia) {
            navigator.getUserMedia(
                { audio: true },
                function (stream) {
                    var rec = new SRecorder(stream);
                    callback(rec);
                })
        }
    }
}
 
function receive(e) {
    audio.src = window.URL.createObjectURL(e);
}

注意:按住a键说话,放开a键发送

和谐有品味不按键实时对讲,通过setInterval发送,但意识杂音有点重,效果不好,那么些必要encodeWAV再一层的包裹,多去除环境杂音的功能,自己挑选了更为简便易行的按键说话的方式

 

那篇小说里第一展望了websocket的将来,然后依据正规大家团结一心尝试解析和生成数据帧,对websocket有了更深一步的打听

最后经过五个demo看到了websocket的潜力,关于语音聊天室的demo涉及的较广,没有接触过奥迪(Audi)oContext对象的同室最好先通晓下奥迪(Audi)oContext

小说到那边就死亡啦~有如何想法和题材欢迎大家提议来一起座谈探索~

 

1 赞 11 收藏 3
评论

图片 6

原稿出处:
AlloyTeam   

原文出处:
AlloyTeam   

初稿出处:
AlloyTeam   

说到websocket想比大家不会陌生,如果陌生的话也没提到,一句话概括

说到websocket想比我们不会陌生,假诺陌生的话也没提到,一句话概括

说到websocket想比大家不会陌生,要是陌生的话也没提到,一句话概括

“WebSocket protocol
是HTML5一种新的说道。它完成了浏览器与服务器全双工通讯”

“WebSocket protocol
是HTML5一种新的情商。它完毕了浏览器与服务器全双工通讯”

“WebSocket protocol
是HTML5一种新的合计。它已毕了浏览器与服务器全双工通讯”

WebSocket相相比传统那么些服务器推技术大约好了太多,大家可以挥手向comet和长轮询这么些技术说拜拜啦,庆幸我们生活在富有HTML5的一代~

WebSocket绝相比较传统这一个服务器推技术大约好了太多,大家得以挥手向comet和长轮询那一个技能说拜拜啦,庆幸我们生活在具有HTML5的一世~

WebSocket相比较传统那个服务器推技术大概好了太多,大家得以挥手向comet和长轮询那么些技能说拜拜啦,庆幸大家生存在颇具HTML5的一世~

那篇小说大家将分三局地探索websocket

这篇小说大家将分三部分探索websocket

那篇小说我们将分三有的探索websocket

先是是websocket的广泛使用,其次是全然自己制作服务器端websocket,最后是重点介绍利用websocket制作的多少个demo,传输图片和在线语音聊天室,let’s
go

率先是websocket的常见使用,其次是截然自己创设服务器端websocket,最后是最紧要介绍利用websocket制作的三个demo,传输图片和在线语音聊天室,let’s
go

首先是websocket的广大使用,其次是一心自己创造服务器端websocket,最后是主要介绍利用websocket制作的五个demo,传输图片和在线语音聊天室,let’s
go

一、websocket常见用法

一、websocket常见用法

一、websocket常见用法

此处介绍二种自己认为大规模的websocket落成……(专注:本文建立在node上下文环境

那里介绍二种自我以为大规模的websocket完毕……(留神:本文建立在node上下文环境

那里介绍三种自我觉着大规模的websocket完结……(在意:本文建立在node上下文环境

1、socket.io

1、socket.io

1、socket.io

先给demo

先给demo

先给demo

JavaScript

JavaScript

JavaScript

var http = require(‘http’); var io = require(‘socket.io’); var server =
http.createServer(function(req, res) { res.writeHeader(200,
{‘content-type’: ‘text/html;charset=”utf-8″‘}); res.end();
}).listen(8888); var socket =.io.listen(server);
socket.sockets.on(‘connection’, function(socket) { socket.emit(‘xxx’,
{options}); socket.on(‘xxx’, function(data) { // do someting }); });

var http = require(‘http’); var io = require(‘socket.io’); var server =
http.createServer(function(req, res) { res.writeHeader(200,
{‘content-type’: ‘text/html;charset=”utf-8″‘}); res.end();
}).listen(8888); var socket =.io.listen(server);
socket.sockets.on(‘connection’, function(socket) { socket.emit(‘xxx’,
{options}); socket.on(‘xxx’, function(data) { // do someting }); });

var http = require(‘http’); var io = require(‘socket.io’); var server =
http.createServer(function(req, res) { res.writeHeader(200,
{‘content-type’: ‘text/html;charset=”utf-8″‘}); res.end();
}).listen(8888); var socket =.io.listen(server);
socket.sockets.on(‘connection’, function(socket) { socket.emit(‘xxx’,
{options}); socket.on(‘xxx’, function(data) { // do someting }); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require(‘http’);
var io = require(‘socket.io’);
 
var server = http.createServer(function(req, res) {
    res.writeHeader(200, {‘content-type’: ‘text/html;charset="utf-8"’});
    res.end();
}).listen(8888);
 
var socket =.io.listen(server);
 
socket.sockets.on(‘connection’, function(socket) {
    socket.emit(‘xxx’, {options});
 
    socket.on(‘xxx’, function(data) {
        // do someting
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require(‘http’);
var io = require(‘socket.io’);
 
var server = http.createServer(function(req, res) {
    res.writeHeader(200, {‘content-type’: ‘text/html;charset="utf-8"’});
    res.end();
}).listen(8888);
 
var socket =.io.listen(server);
 
socket.sockets.on(‘connection’, function(socket) {
    socket.emit(‘xxx’, {options});
 
    socket.on(‘xxx’, function(data) {
        // do someting
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require(‘http’);
var io = require(‘socket.io’);
 
var server = http.createServer(function(req, res) {
    res.writeHeader(200, {‘content-type’: ‘text/html;charset="utf-8"’});
    res.end();
}).listen(8888);
 
var socket =.io.listen(server);
 
socket.sockets.on(‘connection’, function(socket) {
    socket.emit(‘xxx’, {options});
 
    socket.on(‘xxx’, function(data) {
        // do someting
    });
});

深信精晓websocket的校友不容许不明白socket.io,因为socket.io太知名了,也很棒,它自身对逾期、握手等都做了拍卖。我猜忌那也是落到实处websocket使用最多的主意。socket.io最最最赏心悦目的少数就是优雅降级,当浏览器不帮助websocket时,它会在中间优雅降级为长轮询等,用户和开发者是不须要关心具体贯彻的,很有益。

相信驾驭websocket的同班不容许不知道socket.io,因为socket.io太有名了,也很棒,它自己对逾期、握手等都做了拍卖。我估量那也是兑现websocket使用最多的措施。socket.io最最最卓越的一些就是优雅降级,当浏览器不帮助websocket时,它会在中间优雅降级为长轮询等,用户和开发者是不须求关切具体达成的,很便宜。

信任了然websocket的校友不容许不晓得socket.io,因为socket.io太闻名了,也很棒,它本身对过期、握手等都做了拍卖。我估摸那也是兑现websocket使用最多的方法。socket.io最最最卓绝的少数就是优雅降级,当浏览器不协理websocket时,它会在其间优雅降级为长轮询等,用户和开发者是不要求关爱具体落到实处的,很有利。

只是事情是有两面性的,socket.io因为它的一帆风顺也带来了坑的地点,最要害的就是臃肿,它的包装也给多少牵动了较多的报纸发布冗余,而且优雅降级这一优点,也陪同浏览器标准化的举行逐级失去了光辉

唯独事情是有两面性的,socket.io因为它的两全也拉动了坑的地点,最重点的就是臃肿,它的卷入也给多少推动了较多的简报冗余,而且优雅降级这一独到之处,也伴随浏览器标准化的进展逐步失去了光辉

不过事情是有两面性的,socket.io因为它的无所不包也带来了坑的地点,最关键的就是臃肿,它的包裹也给多少牵动了较多的广播宣布冗余,而且优雅降级这一独到之处,也陪同浏览器标准化的进行逐级失去了光辉

Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+
Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+
Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+

在此处不是指责说socket.io不佳,已经被淘汰了,而是有时候大家也足以考虑部分任何的落到实处~

在此处不是指责说socket.io倒霉,已经被淘汰了,而是有时候大家也得以设想部分其余的兑现~

在此处不是指责说socket.io不佳,已经被淘汰了,而是有时候大家也可以考虑部分别样的贯彻~

 

 

 

2、http模块

2、http模块

2、http模块

刚好说了socket.io臃肿,那现在就来说说便捷的,首先demo

正要说了socket.io臃肿,那现在就来说说便捷的,首先demo

凑巧说了socket.io臃肿,那现在就来说说便捷的,首先demo

JavaScript

JavaScript

JavaScript

var http = require(‘http’); var server = http.createServer();
server.on(‘upgrade’, function(req) { console.log(req.headers); });
server.listen(8888);

var http = require(‘http’); var server = http.createServer();
server.on(‘upgrade’, function(req) { console.log(req.headers); });
server.listen(8888);

var http = require(‘http’); var server = http.createServer();
server.on(‘upgrade’, function(req) { console.log(req.headers); });
server.listen(8888);

1
2
3
4
5
6
var http = require(‘http’);
var server = http.createServer();
server.on(‘upgrade’, function(req) {
console.log(req.headers);
});
server.listen(8888);
1
2
3
4
5
6
var http = require(‘http’);
var server = http.createServer();
server.on(‘upgrade’, function(req) {
console.log(req.headers);
});
server.listen(8888);
1
2
3
4
5
6
var http = require(‘http’);
var server = http.createServer();
server.on(‘upgrade’, function(req) {
console.log(req.headers);
});
server.listen(8888);

很粗略的落到实处,其实socket.io内部对websocket也是这样完毕的,可是前面帮大家封装了有的handle处理,那里我们也足以团结去丰盛,给出两张socket.io中的源码图

很不难的贯彻,其实socket.io内部对websocket也是那般完成的,不过前边帮大家封装了一些handle处理,那里大家也可以友善去丰裕,给出两张socket.io中的源码图

很简短的贯彻,其实socket.io内部对websocket也是如此落成的,然则前面帮大家封装了有的handle处理,那里大家也得以友善去丰裕,给出两张socket.io中的源码图

图片 7

图片 8

图片 9

图片 10

图片 11

图片 12

 

 

 

3、ws模块

3、ws模块

3、ws模块

后边有个例子会用到,那里就提一下,后边具体看~

末尾有个例证会用到,那里就提一下,前边具体看~

末端有个例证会用到,那里就提一下,前面具体看~

 

 

 

二、自己已毕一套server端websocket

二、自己完成一套server端websocket

二、自己达成一套server端websocket

赶巧说了三种常见的websocket完毕方式,现在大家想想,对于开发者来说

恰恰说了二种常见的websocket完毕格局,现在大家寻思,对于开发者来说

恰巧说了二种常见的websocket达成格局,现在我们寻思,对于开发者来说

websocket绝对于传统http数据交互形式以来,增添了服务器推送的风浪,客户端接收到事件再开展对应处理,开发起来分化并不是太大啊

websocket绝对于传统http数据交互情势以来,增添了服务器推送的轩然大波,客户端接收到事件再举行相应处理,开发起来不相同并不是太大呀

websocket相对于传统http数据交互情势以来,增添了服务器推送的事件,客户端接收到事件再拓展对应处理,开发起来分裂并不是太大啊

那是因为那多少个模块已经帮我们将数量帧解析那里的坑都填好了,第二有些大家将尝试自己制作一套简便的服务器端websocket模块

那是因为这几个模块已经帮大家将数量帧解析此间的坑都填好了,第二局地大家将尝试自己创设一套简便的服务器端websocket模块

那是因为这个模块已经帮大家将数据帧解析那里的坑都填好了,第二有些我们将尝试自己制作一套简便的服务器端websocket模块

谢谢次碳酸钴的探究扶助,我在此地这部分只是简单说下,假如对此有趣味好奇的请百度【web技术探究所】

谢谢次碳酸钴的钻研辅助,本人在此间那有的只是简单说下,假诺对此有趣味好奇的请百度【web技术探究所】

谢谢次碳酸钴的钻研协理,自我在此处那部分只是简单说下,如若对此有趣味好奇的请百度【web技术琢磨所】

友善姣好服务器端websocket紧要有两点,一个是选拔net模块接受数据流,还有一个是对照官方的帧结构图解析数据,完结那两有的就早已成功了一切的底层工作

和谐成功服务器端websocket首要有两点,一个是采取net模块接受数据流,还有一个是比照官方的帧结构图解析数据,完毕那两片段就已经到位了百分之百的尾部工作

温馨做到服务器端websocket主要有两点,一个是采纳net模块接受数据流,还有一个是相比官方的帧结构图解析数据,落成那两有些就曾经形成了全部的最底层工作

率先给一个客户端发送websocket握手报文的抓包内容

先是给一个客户端发送websocket握手报文的抓包内容

率先给一个客户端发送websocket握手报文的抓包内容

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website