php非阻塞SSH客户端

之前工作中必须和国外服务器打交道,延迟和丢包问题有时候非常严重,已经到了不可忍受的地步,输入一条sql都是很费劲的事情。google搜了一遍没有找到非阻塞的ssh客户端,PHP有SSH2扩展,利用标准输入输出理论上可以实现一个基于命令行的SSH客户端,这样就解决了网络问题带来的不便,于是开发了一个PHP非阻塞SSH客户端。

价值:

  • 基于命令,最大程度解决了网络延迟和丢包问题。
  • windows和Linux下测试通过。

不足:

  • 没有自动补全功能
  • 没有sftp和scp等其他功能
  • 没有颜色和粗体显示
  • 个别情况下显示上不是很完美
  • 因为现在基本不用它了,所以暂时先不进行改进。

linux 运行效果
2013-02-04_133311
windows下运行效果
2013-02-04_133509
2013-02-04_133727
2013-02-04_133842

因为是框架中的一个类,所以个别通用函数(比如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(empty($host['addr'])){
			debug_print('Host cant't be empty',E_USER_ERROR);
		}
		if(empty($host['fp'])){
			debug_print('finger print is not specified',E_USER_ERROR);
		}
		$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) || empty($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(!empty($out) and !empty($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(!empty($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();

较新版本,SSH2

linux 下ssh2扩展安装(需要epel和remi源)
yum install php-pecl-ssh2
源码安装:http://pecl.php.net/package/ssh2

windows pecl:http://downloads.php.net/pierre/
这里只有VC9版本,我之前保存了一个VC6版本,忘了从哪里找到的。

php非阻塞SSH客户端》上有1条评论

  1. Pingback引用通告: php非阻塞ssh客户端 | 乐都网技术团队博客

发表评论

电子邮件地址不会被公开。

*