平时我们上传文件使用的是HTTP方式上传,今天我来给大家分享一下使用HTML5的websocket方式上传文件,后端使用Swoole的Websocket模块接收处理客户端上传的数据并保存为文件。本文实例是一个基础实例,后面我会专门给大家讲解更复杂更实战的文件上传实例。
服务端
我们继续使用Swoole实验室:1-使用Composer构建项目构建好的项目,新建文件\src\App\Uploader.php:
<?php
namespace Helloweba\Swoole;
use swoole_websocket_server;
class Uploader
{
protected $ws;
protected $host = '0.0.0.0';
protected $port = 9505;
// 进程名称
protected $taskName = 'swooleUploader';
// PID路径
protected $pidFile = '/run/swooleUploader.pid';
// 设置运行时参数
protected $options = [
'worker_num' => 4, //worker进程数,一般设置为CPU数的1-4倍
'daemonize' => true, //启用守护进程
'log_file' => '/data/log/swoole.log', //指定swoole错误日志文件
'log_level' => 3, //日志级别 范围是0-5,0-DEBUG,1-TRACE,2-INFO,3-NOTICE,4-WARNING,5-ERROR
'dispatch_mode' => 1, //数据包分发策略,1-轮询模式
];
public function __construct($options = [])
{
$this->ws = new swoole_websocket_server($this->host, $this->port);
if (!empty($options)) {
$this->options = array_merge($this->options, $options);
}
$this->ws->set($this->options);
$this->ws->on("open", [$this, 'onOpen']);
$this->ws->on("message", [$this, 'onMessage']);
$this->ws->on("close", [$this, 'onClose']);
}
public function start()
{
// Run worker
$this->ws->start();
}
public function onOpen(swoole_websocket_server $ws, $request)
{
// 设置进程名
cli_set_process_title($this->taskName);
//记录进程id,脚本实现自动重启
$pid = "{$ws->master_pid}\n{$ws->manager_pid}";
file_put_contents($this->pidFile, $pid);
echo "server: handshake success with fd{$request->fd}\n";
$msg = '{"msg": "connect ok"}';
$ws->push($request->fd, $msg);
}
public function onMessage(swoole_websocket_server $ws, $frame)
{
$opcode = $frame->opcode;
if ($opcode == 0x08) {
echo "Close frame received: Code {$frame->code} Reason {$frame->reason}\n";
} else if ($opcode == 0x1) {
echo "Text string\n";
} else if ($opcode == 0x2) {
echo "Binary data\n"; //
} else {
echo "Message received: {$frame->data}\n";
}
$filename = './files/aaa.jpg';
file_put_contents($filename, $frame->data);
echo "file path : {$filename}\n";
$ws->push($frame->fd, 'upload success');
}
public function onClose($ws, $fid)
{
echo "client {$fid} closed\n";
foreach ($ws->connections as $fd) {
$ws->push($fd, $fid. '已断开!');
}
}
}
知识兔$frame->opcode
,WebSocket的OpCode类型,可以通过它来判断传输的数据是文本内容还是二进制数据。
新建public/uploadServer.php,用于启动服务端脚本:
<?php
require dirname(__DIR__) . '/vendor/autoload.php';
use Helloweba\Swoole\Uploader;
$opt = [
'daemonize' => false
];
$ws = new Uploader($opt);
$ws->start();
知识兔客户端
在本地站点建立客户端文件upload.html。只需在页面中放置一个文件选择控件和一个用于输出上传信息的div#log。
<input type="file" id="myFile">
<div id="log"></div>
知识兔当选择好本地文件后,触发onchange事件,这个时候客户端尝试与服务端建立websocket连接,然后开始读取本地文件,读取完成后将数据发送给服务端。
$('#myFile').on('change', function(event) {
var ws = new WebSocket("ws://192.168.1.31:9505");
ws.onopen = function() {
log('已连接上!');
}
ws.onmessage = function(e) {
log("收到服务器消息:" + e.data + "'\n");
if (e.data == 'connect ok') {
log('开始上传文件');
}
if (e.data == 'upload success') {
log('上传完成');
ws.close();
} else {
var file = document.getElementById("myFile").files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function(e) {
ws.send(e.target.result);
log('正在上传数据...');
}
}
}
ws.onclose = function() {
console.log('连接已关闭!');
log('连接已关闭!');
}
});
//在消息框中打印内容
function log(text) {
$("#log").append(text+"<br/>");
}
知识兔这里讲一下HTML5的FileReader 对象,FileReader允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。FileReader提供了几种读取文件的方法:
reader.readAsArrayBuffer(file|blob)
:用于启动读取指定的 Blob 或 File 内容。读取文件后,会在内存中创建一个ArrayBuffer对象(二进制缓冲区),将二进制数据存放在其中。当读取操作完成时,readyState 变成 DONE(已完成),并触发 loadend 事件,同时 result 属性中将包含一个 ArrayBuffer 对象以表示所读取文件的数据。通过此方式,可以直接在网络中传输二进制内容。此外对于大文件我们可以分段读取二进制内容上传。
reader.readAsDataURL(file|blob)
:该方法会读取指定的 Blob 或 File 对象。读取操作完成的时候,readyState 会变成已完成(DONE),并触发 loadend 事件,同时 result 属性将包含一个data:URL格式的字符串(base64编码)以表示所读取文件的内容。
FileReader.readAsText(file|blob)
:可以将 Blob 或者 File 对象转根据特殊的编码格式转化为内容(字符串形式)。当转化完成后, readyState 这个参数就会转换 为 done 即完成态, event("loadend") 挂载的事件会被触发,并可以通过事件返回的形参得到中的 FileReader.result 属性得到转化后的结果。
FileReader.readAsBinaryString()
:读取文件内容为二进制字符串,已废除,不要用了。
实验
运行服务端
php uploadServer.php
知识兔运行客户端
在本地站点目录,打开upload.html。选择图片上传,即刻显示如下信息:
查看服务端输出:
这时候检查服务器上对应目录下会出现一个aaa.jpg的文件。
Swoole也提供了Websocket客户端,可以使用websocket在不同服务端传输文件。
我们会发现使用Websocket确实把文件上传成功,但是实验中并没有考虑大文件的上传,加入有一个很大的日志文件或者视频文件需要上传到服务器上,那就需要采取分片上传,并考虑断点上传的问题,在下一节文章实验中,我们将具体探讨使用Websocket上传大文件,敬请关注。