必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

3.数据长度在65526-2必威:^64-1之间时,如果陌生的

2019-09-16 11:59 来源:未知

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为是不是得了的标示

RubiconSV为留下空间,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奥迪TTL(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搭建语音聊天室

先来收拾一下语音聊天室的效应

客商步入频道随后从迈克风输入音频,然后发送给后台转载给频道里面包车型客车其余人,别的人接收到音讯实行播放

看起来困难在三个地点,第多个是节奏的输入,第二是吸取到多少流举办广播

先说音频的输入,这里运用了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);
    ……
}

奥迪oContext是三个旋律上下文对象,有做过声音过滤管理的同室应该明白“一段音频达到扬声器进行广播从前,半路对其举行阻拦,于是大家就获得了点子数据了,那几个拦截工作是由window.奥迪(Audi)oContext来做的,大家具备对旋律的操作都遵照这一个目的”,咱们得以由此奥迪(Audi)oContext创建不一致的奥迪oNode节点,然后增多滤镜播放特别的声音

录音原理同样,大家也供给走奥迪oContext,可是多了一步对迈克风音频输入的接受上,而不是像过去管理音频一下用ajax央求音频的ArrayBuffer对象再decode,迈克风的接受必要用到createMediaStreamSource方法,注意那些参数正是getUserMedia方法第3个参数的参数

更而且createScriptProcessor方法,它官方的解释是:

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

——————

席卷下就是以此办法是利用JavaScript去管理音频收罗操作

到头来到点子搜集了!胜利就在前边!

接下去让大家把Mike风的输入和拍子收罗相连起来

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的搭建在此处就不提了,主假诺内需私钥、CS奥迪Q5证书具名和注脚文件,感兴趣的校友能够精通下(然则不打听的话在现网情况也用持续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 //输入采集样品率 , 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('奥德赛IFF'); 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涉及的较广,未有接触过奥迪oContext对象的同班最棒先精晓下奥迪(Audi)oContext

作品到此处就终止啦~有哪些主张和主题材料款待我们建议来一同商议查究~

 

1 赞 11 收藏 3 评论

必威 6

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

windows python 2.79, chrome37 firefox35通过

1.长短小于125时(由于选用126, 127用作标记位.)

 

 

服务器

 

 

 

 

 

3.数额长度在65526-2^64-1时期时, Payload Length位设为127, 前边额外使用64bit意味着长度(后面包车型大巴127不再是长度的一部分)

代码是在外人(cddn有人提问)基础上改的, 主要改造了parsedata和sendmessage那2个函数.

  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()  

测试了chrome37, firefox35

TAG标签:
版权声明:本文由必威发布于必威-前端,转载请注明出处:3.数据长度在65526-2必威:^64-1之间时,如果陌生的