建议:
要发送给客户端的数据如果有几个G,这几个G的数据最好存储在本地磁盘上,避免占用服务器内存。然后根据客户端网络数据拥堵情况分段载入内存并发送。
注意:简单的将大文件分段发送不能避免内存爆的问题
假如10个G的文件发送给客户端,客户端接收速度很慢,虽然服务端将10G文件分成多个小文件发送,但是如果客户端接收速度远远低于服务端发送速度,仍然会导致服务端要发送的数据堆积在发送缓冲区中,导致内存爆掉。就像客户端带宽为10k/S,服务端以1M/S的速度发送,仍然会导致数据积压在服务器发送缓冲区导致内存爆掉。
正确的做法应该是根据客户端网络数据拥堵情况控制发送速度。
如何判断客户端网络数据放生拥堵?如何发送?
workerman提供了网络拥堵控制机制,即 onBufferFull和onBufferDrain事件(具体说明参见手册),当服务端向客户端的发送缓冲区满时(缓冲区大小可控制 参见手册)会产生onBufferFull事件,这时服务端应该停止向这个客户端再发送数据(停止从磁盘read数据到内存),因为onBufferFull发生时说明发送给客户端的数据发生拥堵。
而当发送缓冲区的数据全部发送给客户端后(发送缓冲区空了),将会放生onBufferDrain事件,这时服务端可以继续从磁盘read数据,继续向客户端发送。
通过onBufferFull和onBufferDrain事件可以方便控制网络拥堵,既能够减少内存消耗,又能以最快的速度将数据发送给客户端。
示例:
从磁盘发送大文件到客户端参见下面示例(使用的是http协议,其它协议也适用)
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
$worker = new Worker('http://0.0.0.0:4236');
$worker->onMessage = function($connection, $data)
{
if($_SERVER == '/favicon.ico')
{
return $connection->send("HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n", true);
}
// 这里发送的是一个大的pdf文件,如果是其它格式的文件,请修改下面代码中http头
send_file($connection, "/your/path/xxx.pdf");
};
function send_file($connection, $file_name)
{
if(!is_file($file_name))
{
$connection->send("HTTP/1.0 404 File Not Found\r\nContent-Length: 18\r\n\r\n404 File Not Found", true);
return;
}
// ======发送http头======
$file_size = filesize($file_name);
$header = "HTTP/1.1 200 OK\r\n";
// 这里写的Content-Type是pdf,如果不是pdf文件请修改Content-Type的值
// mime对应关系参见 https://github.com/walkor/Workerman/blob/master/Protocols/Http/mime.types#L30
$header .= "Content-Type: application/pdf\r\n";
$header .= "Connection: keep-alive\r\n";
$header .= "Content-Length: $file_size\r\n\r\n";
$connection->send($header, true);
// ======分段发送文件内容=======
$connection->fileHandler = fopen($file_name, 'r');
$do_write = function()use($connection)
{
// 对应客户端的连接发送缓冲区未满时
while(empty($connection->bufferFull))
{
// 从磁盘读取文件
$buffer = fread($connection->fileHandler, 8192);
// 读不到数据说明文件读到末尾了
if($buffer === '' || $buffer === false)
{
return;
}
$connection->send($buffer, true);
}
};
// 发生连接发送缓冲区满事件时设置一个标记bufferFull
$connection->onBufferFull = function($connection)
{
// 赋值一个bufferFull临时变量给链接对象,标记发送缓冲区满,暂停do_write发送
$connection->bufferFull = true;
};
// 当发送缓冲区数据发送完毕时触发
$connection->onBufferDrain = function($connection)use($do_write)
{
$connection->bufferFull = false;
$do_write();
};
// 执行发送
$do_write();
}
Worker::runAll();
以上例子亲测ok,请试用