Swoole从入门到入土(19)——WebSocket服务器[文件传输]
要利用WebSocket进行文件传输,我们需要讨论两种情况,分别是:发送方可以是客户端,和 发送方是服务端。
1、发送方是客户端
1)服务端接收
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) { switch ($frame->opcode) { case 0x09: $pongFrame = new Swoole\WebSocket\Frame; $pongFrame->opcode = WEBSOCKET_OPCODE_PONG; $server->push($frame->fd, $pongFrame); echo "pone\n"; break; case 0x08: echo "Close frame received: Code {$frame->code} Reason {$frame->reason}\n"; break; case 0x01: echo "Text string\n"; break; case 0x02: echo "Binary data\n"; //服务端在这里接收,$frame->data即是客户端发过来的文件二进制数据 $server->push($frame->fd,$frame->data,WEBSOCKET_OPCODE_BINARY); break; default: $server->push($frame->fd, $frame->data); break; } });
2) 客户端(Javascript)发送
form.on('submit(fileSend)',function(data){ var jqFile=$("#file"); try { var file=jqFile.get(0).files[0]; var filesize=file.size; var reader=new FileReader(); //reader.readAsBinaryString(file); //这里可以通过slice函数,对文件进行分割,多次传送 var blob=file.slice(0,filesize); reader.readAsArrayBuffer(blob); reader.onload=function(e){ var arrBuf=e.target.result; oWs.send(arrBuf); }; }catch(e){ layer.msg("文件异常"); } return false; });
到这里,有一点需要进行强调,利用slice对文件进行分割发送,多次send将文件内容多次传输到接收端,websocket协议会保证先发的先到达,后发的后达到,不会存在乱序问题。
2、发送方是服务端
1) 服务端发送
上面的例子已经提到,利用服务端的push可以将文件二进制数据推送到客户端。
$server->push($frame->fd,$frame->data,WEBSOCKET_OPCODE_BINARY);
2)客户端接收
这一部分,就有比较多的讨论内容。首先,服务端将文件数据以二进制的形式传递到客户端,客户端可以选择以blob或arraybuffer方式接收。
下面我们指定以arraybuffer方式接收:
oWs=new WebSocket(url); oWs.binaryType="arraybuffer";
接下来,如果arraybuffer存储的是字符串数据,我们面临的就是要判断这些二进制数是是GBK还是UTF-8,并转成对应的string。
//传入arraybuffer,判断是否utf8 function IsTextUtf8(inputStream) { var byteArray=new Uint8Array(inputStream); var encodingBytesCount = 0; var allTextsAreASCIIChars = true ; for ( var i = 0; i < byteArray.length; i++) { var current = byteArray[i]; if ((current & 0x80) == 0x80) allTextsAreASCIIChars = false ; if (encodingBytesCount == 0) { if ((current & 0x80) == 0) continue ; if ((current & 0xC0) == 0xC0) { encodingBytesCount = 1; current <<= 2; while ((current & 0x80) == 0x80) { current <<= 1; encodingBytesCount++; } } else { // Invalid bits structure for UTF8 encoding rule. return false ; } } else { if ((current & 0xC0) == 0x80) { encodingBytesCount--; } else { return false ; } } } if (encodingBytesCount != 0) return false ; return !allTextsAreASCIIChars; }
//传入arraybuffer,判断是否GBK function IsTextGbk(inputStream) { var byteArray=new Uint8Array(inputStream); var nBytes = 0;//GBK可用1-2个字bai节编码,中文两个 ,英文一个 var chr = byteArray[0]; var bAllAscii = true; //如果全部都是ASCII, for (var i = 0; i<byteArray.length; i++) { chr = byteArray[i]; if ((chr & 0x80) != 0 && nBytes == 0) // 判断是否ASCII编码,如果不是,说明有可能是GBK { bAllAscii = false; } if (nBytes == 0) { if (chr >= 0x80) { if (chr >= 0x81 && chr <= 0xFE) { nBytes = +2; } else { return false; } nBytes--; } } else { if (chr < 0x40 || chr>0xFE) return false; nBytes--; }//else end } if (nBytes != 0) return false; if (bAllAscii) return true; return true; }
/* 以对应字符集读出arraybuffer的内容并转为字符串 u:arraybuffer f:回调函数,参数为string encoding:可选gbk和utf-8 */ function ab2str(u,f,encoding) { var b = new Blob([u]); var r = new FileReader(); r.readAsText(b, encoding); r.onload = function (){if(f)f.call(null,r.result)} }
oWs.onmessage=function(e){ var d;
if(typeof e.data=="object") { if(IsTextUtf8(e.data)) { ab2str(e.data,function(str){ d="接收UTF-8:"+str; },"utf-8"); return; } else if(IsTextGbk(e.data)) { ab2str(e.data,function(str){ d="接收GBK:"+str; },"gbk"); return; } else { var arr=new Uint8Array(e.data); var str=''; for(var i=0;i<arr.length;i++) { str+=String.fromCharCode(arr[i]); } d="接收对象埂进制数据:"+str; } } else d="接收文本数据:"+e.data; };
以上,就是关于文件传输的基本操作。我们可以设定自己的机制,连续传输图片数据,不断画到canvas上,造成小电影即视感;或者采用对传输的二进制数据进行base64编码方法进行传递。办法有很多,期待大家多多练习。本章到此结束。
完整的代码,大家可以看这里:传送门
--------------------------- 我是可爱的分割线 ----------------------------
最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。