dongqiao9583 2013-05-02 14:13
浏览 39
已采纳

PHP fread ssh stream显着慢

Scenario: I needed a function to get STDOUT of a command run through SSH asynchronously. This has various uses including (and most importantly) reading files through SSH. An important feature of this function is that it is asynchronous, hence I can display output as it is returned from the server (or give an estimate of the file download progress). This differs from the common approach of using ssh_move() to download files.

function ssh_exec($dsn, $cmd, $return=true, $size_est=null){
    $BUFFER_SIZE = $return ? (1024 * 1024) : 1;
    debug('ssh2_exec '.$cmd);
    $stream = ssh2_exec(ssh_open($dsn), $cmd);
    debug('stream_set_blocking');
    stream_set_blocking($stream, true);
    debug('ssh2_fetch_stream');
    $stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
    stream_set_blocking($stream_out, true);
    debug('stream_get_contents');
    $data = ''; $stime = $oldtime = microtime(true); $data_len = 0;
    if(!$return){
        write_message("\033[0m".'  Execution Output:'.PHP_EOL.'    ');
    }
    while(!feof($stream_out)){
        $buff = fread($stream_out, $BUFFER_SIZE);
        if($buff===false)throw new Exception('Unexpected result from fread().');
        if($buff===''){
            debug('Empty result from fread()...breaking.');
            break;
        }
        $data .= $buff;
        if($return){
            $buff_len = strlen($buff);
            $data_len += $buff_len;
            $newtime = microtime(true);
            debugo('stream_get_contents '.bytes_to_human($data_len)
                .' @ '.bytes_to_human($buff_len / ($newtime - $oldtime)).'/s'
                .' ['.($size_est ? number_format($data_len / $size_est * 100, 2) : '???').'%]');
            $oldtime = $newtime;
        }else{
            echo str_replace(PHP_EOL, PHP_EOL.'    ', $buff);
        }
    }
    if($return){
        debugo('stream_get_contents Transferred '.bytes_to_human(strlen($data)).' in '.number_format(microtime(true) - $stime, 2).'s');
        return $data;
    }
}

Usage: The function is used like so:

$dsn = 'ssh2.exec://root:pass@host/';
$size = ssh_size($dsn, 'bigfile.zip');
$zip = ssh_exec($dsn, 'cat bigfile.zip', true, $size);

Note 1: An explanation of some non-standard functions:

  • debug($message) - Writes debug message to console.
  • ssh_open($dsn) - Takes in an SSH URI and returns an SSH connection handle.
  • bytes_to_human($bytes) - Converts the number of bytes to human readable format (eg: 6gb)
  • debugo($message) - The same as debug() but overwrites last line.

Note 2: The parameter $size_est is used in the progress indicator; usually you'd first get the file size and then attempt to download it (as in my example). It is optional so that it can be ignored when you just want to run an SSH command.

The Problem: Running the same download operation via scp root@host:/bigfile.zip ./, I get speeds up to 1mb/s whereas this script seems to limit to 70kb/s. I'd like to know why and how to improve this.

Edit: Also, I'd like to know how/if $BUFFER_SIZE does any difference.

  • 写回答

1条回答 默认 最新

  • drqrdkfue521903877 2013-05-02 15:25
    关注

    You should be able to use phpseclib, a pure PHP SSH implementation, to do this. eg.

    $ssh->exec('cat bigfile.zip', false);
    
    while (true) {
        $temp = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC);
        if (is_bool($temp)) {
            break;
        }
        echo $temp;
    }
    

    Might actually be faster too.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 关于#hadoop#的问题
  • ¥15 (标签-Python|关键词-socket)
  • ¥15 keil里为什么main.c定义的函数在it.c调用不了
  • ¥50 切换TabTip键盘的输入法
  • ¥15 可否在不同线程中调用封装数据库操作的类
  • ¥15 微带串馈天线阵列每个阵元宽度计算
  • ¥15 keil的map文件中Image component sizes各项意思
  • ¥20 求个正点原子stm32f407开发版的贪吃蛇游戏
  • ¥15 划分vlan后,链路不通了?
  • ¥20 求各位懂行的人,注册表能不能看到usb使用得具体信息,干了什么,传输了什么数据