swoole-TCP连接输出缓冲区

缓冲区的作用

下面是个人总结的,希望对大家理解记忆有帮助

  • 提升效率:批量发送、批量读取(数据量较小轻松应对)
  • 拥塞机制:发送之前询问接收方是否处理有压力,发多少数据合适 (正常运行)
  • IO差异:快速输出但网络延迟,快速输入但读取延迟 (数据量偏大可以应对)
  • 预警:通过对缓冲区大小的控制,防止爆满内存(数据量超大困难应对)

预警

此篇重点讨论服务端和客户端两个参数,这两个参数个人认为可划分到预警作用中。

客户端

  • socket_buffer_size “ 包括socket底层操作系统缓存区、应用层接收数据内存缓存区、应用层发送数据内存缓冲区 ”。可能是因为客户端通常只向一个服务端发送、接收数据,相对简单,所以整合在了一起。

服务端

  • buffer_output_size

一个worker单次send的缓冲区,最大占用内存worker_num * buffer_output_size

  • socket_buffer_size

为每个客户端连接(socket套接字)分配的输出缓冲区大小,待发送的数据上限。 最大占用内存max_connection * socket_buffer_size

在这里插入图片描述

测试

客户端代码

<?php
/**
 * 客户端代码保持不变
 * 接收服务器多次发送过来的数据 采用异步客户端
 * 将package_max_length调到不受此项影响的大小
 */
$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

$client->set([
    'open_length_check' => true,
    'package_max_length' => 5*1024*1024,
    'package_length_type' => 'N',
    'package_length_offset' => 0,
    'package_body_offset' => 4,
    ]);

$client->on("connect", function(swoole_client $cli) {
    echo "connecting\n";
    $cli->send("GET / HTTP/1.1\r\n\r\n");
});
$client->on("receive", function(swoole_client $cli, $data){
    if($data){
        $len = unpack('N',$data);
        $body = substr($data,4,$len[1]);
        var_dump(strlen($body));
    }
});
$client->on("error", function(swoole_client $cli){
    echo "error\n";
});
$client->on("close", function(swoole_client $cli){
    echo "Connection close\n";
});

$client->connect('127.0.0.1', 6001);

buffer output size

单次send()最大发送的数据

<?php
/**
 * 测试buffer_output_size
 * 将socket_buffer_size调到不受此项影响的大小
 * 单次发送2M<buffer_output_size,循环40次
 */
$serv = new swoole_server("127.0.0.1", 6001);
$serv->set([
    'worker_num'=>1,
    'buffer_output_size' => 3 * 1024 *1024,
    'socket_buffer_size' => 128 * 1024 *1024,
]);
$serv->on('connect', function (swoole_server $serv, int $fd) {

    $body = str_repeat('a',2*1024*1024);
    $head = pack('N',strlen($body));
    $pack = $head.$body;

    for ($i=0; $i < 40; $i++) {
        $serv->send($fd,$pack);
    }
});
$serv->on('receive',function($serv, $fd, $reactor_id, $data){
    echo $data;
});
$serv->start();

结果:没有异常,客户端全部接收。说明buffer_output_size和调用send多少次无关,只要单次不超过即可。

//修改为单词发送4M>buffer_output_size
$body = str_repeat('a',4*1024*1024);

结果:

在这里插入图片描述

socket_buffer_size

单个客户端连接(socket套接字)分配的输出缓冲区大小,待发送的数据上限

/**
 * 测试socket_buffer_size
 * 将buffer_output_size调到不受此项影响的大小
 * 单次发送3M>socket_buffer_size,发送一次
 */
$serv = new swoole_server("127.0.0.1", 6001);

$serv->set([
    'worker_num'=>1,
    'buffer_output_size' => 5 * 1024 *1024,
    'socket_buffer_size' => 2 * 1024 *1024,
]);

$serv->on('connect', function (swoole_server $serv, int $fd) {

    $body = str_repeat('a',3*1024*1024);
    $head = pack('N',strlen($body));
    $pack = $head.$body;

    for ($i=0; $i < 1; $i++) {
        $serv->send($fd,$pack);
    }
});

$serv->on('receive',function($serv, $fd, $reactor_id, $data){
    echo $data;
});

$serv->start();

结果:没有异常,客户端正常接收数据。明明发送的3M大于socket_buffer_size的2M,为什么还能发送成功呢?

//调大循环发送次数 经测试在循环4次时提示缓存溢出
for ($i=0; $i < 4; $i++) {
        $serv->send($fd,$pack);
}

在这里插入图片描述 这说明缓冲区像一个漏桶,即使大于漏桶体积的水进来,可能也不会溢出,但多次累积则造成溢出。那么如果小于漏桶体积的水进来,能保证不会溢出吗?

//修改单次发送1M (<socket_buffer_size=2M)
$body = str_repeat('a',1*1024*1024);
//循环发送10次
for ($i=0; $i < 10; $i++) {
        $serv->send($fd,$pack);
}

为了模仿客户端阻塞,修改客户端代码 阻塞1毫秒
$client->on("receive", function(swoole_client $cli, $data){
    usleep(1000);
    ....

结果:

在这里插入图片描述

客户端成功接收8次后,输出缓存溢出。

在这里插入图片描述

总结

socket_buffer_size即使设置的足够大,也有可能溢出。 解决办法可通过buffer_output_size限制单次send()大小,最可靠的办法是不能一味的发送,而是得到客户端的响应后再继续发送。 socket_buffer_size不能设置的过大,此选项是单个套接字连接占用的内存,如果是1W个客户端连接,则总占用内存数1W*socket_buffer_size

登录后进行讨论