说到websocket想比大家不会陌生,3.数据长度在65
分类:巴黎人-前端

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 Protocolsrn'); o.write('Upgrade: websocketrn'); o.write('Connection: Upgradern'); // 那几个字段带上服务器管理后的KEY o.write('Sec-WebSocket-Accept: '+key+'rn'); // 输出空行,使HTTP头结束 o.write('rn'); } }); }).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 Protocolsrn');
o.write('Upgrade: websocketrn');
o.write('Connection: Upgradern');
// 这个字段带上服务器处理后的KEY
o.write('Sec-WebSocket-Accept: '+key+'rn');
// 输出空行,使HTTP头结束
o.write('rn');
}
});
}).listen(8888);

这样握手部分就曾经完成了,前面就是多少帧解析与转移的活了

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

巴黎人澳门官网 4

简易介绍下

FIN为是还是不是得了的标识

中华VSV为留住空间,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.readAsDataU奥迪Q7L(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);
};

接受到音讯,然后readAsDataU大切诺基L,直接将图片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对象,后续的操作基本上都在这几个目的上扩充。此时假设代码运转在该地的话浏览器应该升迁您是还是不是启用Mike风输入,确定之后就开动了

接下去大家看下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);
    ……
}

奥迪oContext是一个旋律上下文对象,有做过声音过滤管理的同班应该领会“一段音频到达扬声器举行播报在此以前,半路对其举办阻拦,于是我们就获取了点子数据了,那一个拦截工作是由window.奥迪(Audi)oContext来做的,我们拥有对旋律的操作都基于那些目的”,大家可以通过奥迪(Audi)oContext创设区别的奥迪(Audi)oNode节点,然后增加滤镜播放非常的响声

录音原理同样,我们也急需走奥迪(Audi)oContext,然而多了一步对Mike风音频输入的抽取上,并不是像过去处理音频一下用ajax须要音频的ArrayBuffer对象再decode,Mike风的承受必要用到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方法绝对的赞,外人实行了往往的韵律压缩和优化,那么些最后会伴随完整的代码一同贴出来

那时总体客商踏向频道随后从迈克风输入音频环节就曾经成功啦,上面就该是向服务器端发送音频流,稍微有一点点蛋疼的来了,刚才大家说了,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的搭建在这里就不提了,首如若索要私钥、CSEvoque证书签字和证件文件,感兴趣的同学能够精通下(不过不打听的话在现网遭逢也用持续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 奥迪(Audi)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 //输出采集样品率 , 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('XC90IFF'); 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

[python] view plaincopy巴黎人澳门官网 7巴黎人澳门官网 8

 

 

 

 

windows python 2.79, chrome37 firefox35通过

参考:用Python达成二个简短的WebSocket服务器

 

  1. 数码长度在128-65525以内时, Payload Length位设为126, 后边额外使用16bit意味长度(后边的126不再是长度的一有的)

[html] view plaincopy巴黎人澳门官网 9巴黎人澳门官网 10

服务器

  1. Fin (bit 0): determines if this is the last frame in the message. This would be set to 1 on the end of a series of frames, or in a single-frame message, it would be set to 1 as it is both the first and last frame.
  2. RSV1, RSV2, RSV3 (bits 1-3): these three bits are reserved for websocket extensions, and should be 0 unless a specific extension requires the use of any of these bytes.
  3. Opcode (bits 4-7): these four bits deterimine the type of the frame. Control frames communicate WebSocket state, while non-control frames communicate data. The various types of codes include:

    1. x0: continuation frame; this frame contains data that should be appended to the previous frame
    2. x1: text frame; this frame (and any following) contains text
    3. x2: binary frame; this frame (and any following) contains binary data
    4. x3 - x7: non-control reserved frames; these are reserved for possible websocket extensions
    5. x8: close frame; this frame should end the connection
    6. x9: ping frame
    7. xA: pong frame
    8. xB - xF: control reserved frames
  4. Mask (bit 8): this bit determines whether this specific frame uses a mask or not.

  5. Payload Length (bits 9-15, or 16-31, or 16-79): these seven bytes determine the payload length. If the length is 126, the length is actually determined by bits 16 through 31 (that is, the following two bytes). If the length is 127, the length is actually determined by bits 16 through 79 (that is, the following eight bytes).
  6. Masking Key (the following four bytes): this represents the mask, if the Mask bit is set to 1.
  7. Payload Data (the following data): finally, the data. The payload data may be sent over multiple frames; we know the size of the entire message by the payload length that was sent, and can append data together to form a single message until we receive the message with the Fin flag. Each consecutive payload, if it exists, will contain the 0 “continuation frame” opcode.
  1. #coding=utf8  
  2. #!/usr/bin/python  
  3.   
  4.   
  5. import struct,socket  
  6. import hashlib  
  7. import threading,random  
  8. import time  
  9. import struct  
  10. from base64 import b64encode, b64decode  
  11.   
  12.   
  13. connectionlist = {}  
  14. g_code_length = 0  
  15. g_header_length = 0  
  16.   
  17.   
  18. def hex2dec(string_num):  
  19.     return str(int(string_num.upper(), 16))  
  20.   
  21.   
  22.   
  23.   
  24. def get_datalength(msg):  
  25.     global g_code_length  
  26.     global g_header_length      
  27.       
  28.     print (len(msg))  
  29.     g_code_length = ord(msg[1]) & 127  
  30.     received_length = 0;  
  31.     if g_code_length == 126:  
  32.         #g_code_length = msg[2:4]  
  33.         #g_code_length = (ord(msg[2])<<8) + (ord(msg[3]))  
  34.         g_code_length = struct.unpack('>H', str(msg[2:4]))[0]  
  35.         g_header_length = 8  
  36.     elif g_code_length == 127:  
  37.         #g_code_length = msg[2:10]  
  38.         g_code_length = struct.unpack('>Q', str(msg[2:10]))[0]  
  39.         g_header_length = 14  
  40.     else:  
  41.         g_header_length = 6  
  42.     g_code_length = int(g_code_length)  
  43.     return g_code_length  
  44.           
  45. def parse_data(msg):  
  46.     global g_code_length  
  47.     g_code_length = ord(msg[1]) & 127  
  48.     received_length = 0;  
  49.     if g_code_length == 126:  
  50.         g_code_length = struct.unpack('>H', str(msg[2:4]))[0]  
  51.         masks = msg[4:8]  
  52.         data = msg[8:]  
  53.     elif g_code_length == 127:  
  54.         g_code_length = struct.unpack('>Q', str(msg[2:10]))[0]  
  55.         masks = msg[10:14]  
  56.         data = msg[14:]  
  57.     else:  
  58.         masks = msg[2:6]  
  59.         data = msg[6:]  
  60.   
  61.   
  62.     i = 0  
  63.     raw_str = ''  
  64.   
  65.   
  66.     for d in data:  
  67.         raw_str += chr(ord(d) ^ ord(masks[i%4]))  
  68.         i += 1  
  69.   
  70.   
  71.     print (u"总市长度是:%d" % int(g_code_length))      
  72.     return raw_str    
  73.   
  74.   
  75. def sendMessage(message):  
  76.     global connectionlist  
  77.       
  78.     message_utf_8 = message.encode('utf-8')  
  79.     for connection in connectionlist.values():  
  80.         back_str = []  
  81.         back_str.append('x81')  
  82.         data_length = len(message_utf_8)  
  83.   
  84.   
  85.         if data_length <= 125:  
  86.             back_str.append(chr(data_length))  
  87.         elif data_length <= 65535 :  
  88.             back_str.append(struct.pack('b', 126))  
  89.             back_str.append(struct.pack('>h', data_length))  
  90.             #back_str.append(chr(data_length >> 8))  
  91.             #back_str.append(chr(data_length & 0xFF))  
  92.             #a = struct.pack('>h', data_length)  
  93.             #b = chr(data_length >> 8)  
  94.             #c = chr(data_length & 0xFF)  
  95.         elif data_length <= (2^64-1):  
  96.             #back_str.append(chr(127))  
  97.             back_str.append(struct.pack('b', 127))  
  98.             back_str.append(struct.pack('>q', data_length))  
  99.             #back_str.append(chr(data_length >> 8))  
  100.             #back_str.append(chr(data_length & 0xFF))        
  101.         else :  
  102.                 print (u'太长了')          
  103.         msg = ''  
  104.         for c in back_str:  
  105.             msg += c;  
  106.         back_str = str(msg)   + message_utf_8#.encode('utf-8')      
  107.         #connection.send(str.encode(str(u"x00%sxFFnn" % message))) #本条是旧版  
  108.         #print (u'send message:' +  message)  
  109.         if back_str != None and len(back_str) > 0:  
  110.             print (back_str)  
  111.             connection.send(back_str)  
  112.   
  113.   
  114. def deleteconnection(item):  
  115.     global connectionlist  
  116.     del connectionlist['connection'+item]  
  117.   
  118.   
  119. class WebSocket(threading.Thread):#继承Thread  
  120.   
  121.   
  122.     GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"  
  123.   
  124.   
  125.     def __init__(self,conn,index,name,remote, path="/"):  
  126.         threading.Thread.__init__(self)#开首化父类Thread  
  127.         self.conn = conn  
  128.         self.index = index  
  129.         self.name = name  
  130.         self.remote = remote  
  131.         self.path = path  
  132.         self.buffer = ""  
  133.         self.buffer_utf8 = ""  
  134.         self.length_buffer = 0  
  135.     def run(self):#重载Thread的run  
  136.         print('Socket%s Start!' % self.index)  
  137.         headers = {}  
  138.         self.handshaken = False  
  139.   
  140.   
  141.         while True:  
  142.             if self.handshaken == False:  
  143.                 print ('Socket%s Start Handshaken with %s!' % (self.index,self.remote))  
  144.                 self.buffer += bytes.decode(self.conn.recv(1024))  
  145.   
  146.   
  147.                 if self.buffer.find('rnrn') != -1:  
  148.                     header, data = self.buffer.split('rnrn', 1)  
  149.                     for line in header.split("rn")[1:]:  
  150.                         key, value = line.split(": ", 1)  
  151.                         headers[key] = value  
  152.   
  153.   
  154.                     headers["Location"] = ("ws://%s%s" %(headers["Host"], self.path))  
  155.                     key = headers['Sec-WebSocket-Key']  
  156.                     token = b64encode(hashlib.sha1(str.encode(str(key + self.GUID))).digest())  
  157.   
  158.   
  159.                     handshake="HTTP/1.1 101 Switching Protocolsrn"  
  160.                         "Upgrade: websocketrn"  
  161.                         "Connection: Upgradern"  
  162.                         "Sec-WebSocket-Accept: "+bytes.decode(token)+"rn"  
  163.                         "WebSocket-Origin: "+str(headers["Origin"])+"rn"  
  164.                         "WebSocket-Location: "+str(headers["Location"])+"rnrn"  
  165.   
  166.   
  167.                     self.conn.send(str.encode(str(handshake)))  
  168.                     self.handshaken = True    
  169.                     print ('Socket %s Handshaken with %s success!' %(self.index, self.remote))    
  170.                     sendMessage(u'Welcome, ' + self.name + ' !')    
  171.                     self.buffer_utf8 = ""  
  172.                     g_code_length = 0                      
  173.   
  174.   
  175.             else:  
  176.                 global g_code_length  
  177.                 global g_header_length  
  178.                 mm=self.conn.recv(128)  
  179.                 if len(mm) <= 0:  
  180.                     continue  
  181.                 if g_code_length == 0:  
  182.                     get_datalength(mm)  
  183.                 #收受的长短  
  184.                 self.length_buffer = self.length_buffer + len(mm)  
  185.                 self.buffer = self.buffer + mm  
  186.                 if self.length_buffer - g_header_length < g_code_length :  
  187.                     continue  
  188.                 else :  
  189.                     self.buffer_utf8 = parse_data(self.buffer) #utf8                  
  190.                     msg_unicode = str(self.buffer_utf8).decode('utf-8', 'ignore') #unicode  
  191.                     if msg_unicode=='quit':  
  192.                         print (u'Socket%s Logout!' % (self.index))  
  193.                         nowTime = time.strftime('%H:%M:%S',time.localtime(time.time()))  
  194.                         sendMessage(u'%s %s say: %s' % (nowTime, self.remote, self.name+' Logout'))                        
  195.                         deleteconnection(str(self.index))  
  196.                         self.conn.close()  
  197.                         break #剥离线程  
  198.                     else:  
  199.                         #print (u'Socket%s Got msg:%s from %s!' % (self.index, msg_unicode, self.remote))  
  200.                         nowTime = time.strftime(u'%H:%M:%S',time.localtime(time.time()))  
  201.                         sendMessage(u'%s %s say: %s' % (nowTime, self.remote, msg_unicode))    
  202.                     #重置buffer和bufferlength  
  203.                     self.buffer_utf8 = ""  
  204.                     self.buffer = ""  
  205.                     g_code_length = 0  
  206.                     self.length_buffer = 0  
  207.             self.buffer = ""  
  208.   
  209.   
  210. class WebSocketServer(object):  
  211.     def __init__(self):  
  212.         self.socket = None  
  213.     def begin(self):  
  214.         print( 'WebSocketServer Start!')  
  215.         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  216.         self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  
  217.         self.socket.bind(("127.0.0.1",12345))  
  218.         self.socket.listen(50)  
  219.   
  220.   
  221.         global connectionlist  
  222.   
  223.   
  224.         i=0  
  225.         while True:  
  226.             connection, address = self.socket.accept()  
  227.   
  228.   
  229.             username=address[0]       
  230.             newSocket = WebSocket(connection,i,username,address)  
  231.             newSocket.start() #开始线程,推行run函数  
  232.             connectionlist['connection'+str(i)]=connection  
  233.             i = i + 1  
  234.   
  235.   
  236. if __name__ == "__main__":  
  237.     server = WebSocketServer()  
  238.     server.begin()  

 

 

本文由巴黎人手机版发布于巴黎人-前端,转载请注明出处:说到websocket想比大家不会陌生,3.数据长度在65

上一篇:HTTP的地址格式如下 下一篇:没有了
猜你喜欢
热门排行
精彩图文