php无阻塞SSH客户端实例
之前工作中必须和国外服务器打交道,延迟和丢包问题有时候非常严重,已经到了不可忍受的地步,输入一条sql都是很费劲的事情,google搜了一遍没有找到非阻塞的ssh客户端,PHP有SSH2扩展,利用标准输入输出理论上可以实现一个基于命令的SSH客户端,这样就解决了网络问题带来的不便,于是开发了一个PHP非阻塞SSH客户端.
价值:基于命令,最大程度解决了网络延迟和丢包问题,windows和Linux下测试通过.
不足:没有自动补全功能,没有sftp和scp等其他功能,没有颜色和粗体显示,个别情况下显示上不是很完美,因为现在基本不用它了,所以暂时先不进行改进.
因为是框架中的一个类,所以个别通用函数(比如debug_print())需要自己提供,我这里就不改写了,代码如下:
- <?php
- class FSSH{
- private $conn;
- private $shell;
- /**
- * key=String 密码认证,key=array('pub'=>,'pri'=>,'type'=>,'phrase'=>)密钥认证
- * 密钥认证type分为两种:ssh-rsa,ssh-dss
- * $host[addr]=String 地址,$host['fp']=array() 服务器指纹
- */
- function __construct($host,$user,$key){
- if(emptyempty($host['addr'])){
- debug_print('Host cant't be emptyempty',E_USER_ERROR);
- }
- if(emptyempty($host['fp'])){
- debug_print('finger print is not specified',E_USER_ERROR);
- }//开源代码phpfensi.com
- $this->stdin=fopen('php://stdin','r');
- $this->stdout=fopen('php://stdout','w');
- if(false!==strpos($host['addr'],':')){
- $temp=explode(':',$host['addr']);
- $host['addr']=$temp[0];
- $port=$temp[1];
- }else{
- $port=22;
- }
- if(is_string($key) || emptyempty($key['type'])){
- $methods=null;
- }else{
- $methods=array('hostkey'=>$key['type']);
- }
- $conn=ssh2_connect($host['addr'],$port,$methods,array('disconnect'=>array($this,'disconnect')));
- $fp=ssh2_fingerprint($conn,SSH2_FINGERPRINT_MD5);
- $success=false;
- $fpOK=false;
- if(in_array($fp,$host['fp'])){
- $fpOK=true;
- }else{
- fwrite($this->stdout,"$fpnIs fingerprint OK ?(y/n)");
- $input=strtolower(stream_get_line($this->stdin,1));
- if($input=='y'){
- $fpOK=true;
- }else{
- $fpOK=false;
- }
- }
- if($fpOK){
- if(is_array($key)){
- if (ssh2_auth_pubkey_file($conn,$user,$key['pub'],$key['pri'],$key['phrase'])){
- $success=true;
- }else{
- debug_print('Public Key Authentication Failed',E_USER_ERROR);
- }
- }elseif(is_string($key)){
- if(ssh2_auth_password($conn,$user,$key)){
- $success=true;
- }else{
- debug_print('Password Authentication Failed',E_USER_ERROR);
- }
- }
- }else{
- debug_print('Fingerprint is invalid',E_USER_ERROR);
- }
- if($success){
- $this->conn=$conn;
- $this->shell=ssh2_shell($conn,null,null,1024);
- }
- return $success;
- }
- function shell(){
- //最后一条命令
- $last='';
- //先结束shell,再结束while
- $signalTerminate=false;
- while(true){
- $cmd=$this->fread($this->stdin);
- $out=stream_get_contents($this->shell,1024);
- if(!emptyempty($out) and !emptyempty($last)){
- $l1=strlen($out);
- $l2=strlen($last);
- $l=$l1>$l2?$l2:$l1;
- $last=substr($last,$l);
- $out=substr($out,$l);
- }
- echo ltrim($out);
- if($signalTerminate){
- break;
- }
- if(in_array(trim($cmd),array('exit'))){
- $signalTerminate=true;
- }
- if(!emptyempty($cmd)){
- $last=$cmd;
- fwrite($this->shell,$cmd);
- }
- }
- }
- //解决windows命令行的读取问题,没有别的办法了。
- private function fread($fd){
- static $data='';
- $read = array($fd);
- $write = array();
- $except = array();
- $result = stream_select($read,$write,$except,0,1000);
- if($result === false)
- debug_print('stream_select failed',E_USER_ERROR);
- if($result !== 0){
- $c= stream_get_line($fd,1);
- if($c!=chr(13))
- $data.=$c;
- if($c==chr(10)){
- $t=$data;
- $data='';
- return $t;
- }
- }
- }
- function __destruct(){
- fclose($this->stdin);
- fclose($this->stdout);
- $this->disconnect();
- }
- private function disconnect(){
- if(is_resource($this->conn)){
- unset($this->conn);
- fclose($this->shell);
- }
- }
- }
- ?>
demo,代码如下:
- //$ssh=new FSSH(array('addr'=>'x.x.x.x:22','fp'=>array('')),'tunnel',array('pub'=>'E:Identity.pub','pri'=>'E:Identity','type'=>'ssh-rsa'));
- $ssh=new FSSH(array('addr'=>'192.168.2.205','fp'=>array('54ECC700B844DCF0D40554A56C57C01E')),'root','123456');
- $ssh->shell();