php无阻塞SSH客户端实例

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

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

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

因为是框架中的一个类,所以个别通用函数(比如debug_print())需要自己提供,我这里就不改写了,代码如下:

  1. <?php
  2. class FSSH{
  3. private $conn;
  4. private $shell;
  5. /**
  6. * key=String 密码认证,key=array('pub'=>,'pri'=>,'type'=>,'phrase'=>)密钥认证
  7. * 密钥认证type分为两种:ssh-rsa,ssh-dss
  8. * $host[addr]=String 地址,$host['fp']=array() 服务器指纹
  9. */
  10. function __construct($host,$user,$key){
  11. if(emptyempty($host['addr'])){
  12. debug_print('Host cant't be emptyempty',E_USER_ERROR);
  13. }
  14. if(emptyempty($host['fp'])){
  15. debug_print('finger print is not specified',E_USER_ERROR);
  16. }//开源代码phpfensi.com
  17. $this->stdin=fopen('php://stdin','r');
  18. $this->stdout=fopen('php://stdout','w');
  19. if(false!==strpos($host['addr'],':')){
  20. $temp=explode(':',$host['addr']);
  21. $host['addr']=$temp[0];
  22. $port=$temp[1];
  23. }else{
  24. $port=22;
  25. }
  26. if(is_string($key) || emptyempty($key['type'])){
  27. $methods=null;
  28. }else{
  29. $methods=array('hostkey'=>$key['type']);
  30. }
  31. $conn=ssh2_connect($host['addr'],$port,$methods,array('disconnect'=>array($this,'disconnect')));
  32. $fp=ssh2_fingerprint($conn,SSH2_FINGERPRINT_MD5);
  33. $success=false;
  34. $fpOK=false;
  35. if(in_array($fp,$host['fp'])){
  36. $fpOK=true;
  37. }else{
  38. fwrite($this->stdout,"$fpnIs fingerprint OK ?(y/n)");
  39. $input=strtolower(stream_get_line($this->stdin,1));
  40. if($input=='y'){
  41. $fpOK=true;
  42. }else{
  43. $fpOK=false;
  44. }
  45. }
  46. if($fpOK){
  47. if(is_array($key)){
  48. if (ssh2_auth_pubkey_file($conn,$user,$key['pub'],$key['pri'],$key['phrase'])){
  49. $success=true;
  50. }else{
  51. debug_print('Public Key Authentication Failed',E_USER_ERROR);
  52. }
  53. }elseif(is_string($key)){
  54. if(ssh2_auth_password($conn,$user,$key)){
  55. $success=true;
  56. }else{
  57. debug_print('Password Authentication Failed',E_USER_ERROR);
  58. }
  59. }
  60. }else{
  61. debug_print('Fingerprint is invalid',E_USER_ERROR);
  62. }
  63. if($success){
  64. $this->conn=$conn;
  65. $this->shell=ssh2_shell($conn,null,null,1024);
  66. }
  67. return $success;
  68. }
  69. function shell(){
  70. //最后一条命令
  71. $last='';
  72. //先结束shell,再结束while
  73. $signalTerminate=false;
  74. while(true){
  75. $cmd=$this->fread($this->stdin);
  76. $out=stream_get_contents($this->shell,1024);
  77. if(!emptyempty($out) and !emptyempty($last)){
  78. $l1=strlen($out);
  79. $l2=strlen($last);
  80. $l=$l1>$l2?$l2:$l1;
  81. $last=substr($last,$l);
  82. $out=substr($out,$l);
  83. }
  84. echo ltrim($out);
  85. if($signalTerminate){
  86. break;
  87. }
  88. if(in_array(trim($cmd),array('exit'))){
  89. $signalTerminate=true;
  90. }
  91. if(!emptyempty($cmd)){
  92. $last=$cmd;
  93. fwrite($this->shell,$cmd);
  94. }
  95. }
  96. }
  97. //解决windows命令行的读取问题,没有别的办法了。
  98. private function fread($fd){
  99. static $data='';
  100. $read = array($fd);
  101. $write = array();
  102. $except = array();
  103. $result = stream_select($read,$write,$except,0,1000);
  104. if($result === false)
  105. debug_print('stream_select failed',E_USER_ERROR);
  106. if($result !== 0){
  107. $c= stream_get_line($fd,1);
  108. if($c!=chr(13))
  109. $data.=$c;
  110. if($c==chr(10)){
  111. $t=$data;
  112. $data='';
  113. return $t;
  114. }
  115. }
  116. }
  117. function __destruct(){
  118. fclose($this->stdin);
  119. fclose($this->stdout);
  120. $this->disconnect();
  121. }
  122. private function disconnect(){
  123. if(is_resource($this->conn)){
  124. unset($this->conn);
  125. fclose($this->shell);
  126. }
  127. }
  128. }
  129. ?>

demo,代码如下:

  1. //$ssh=new FSSH(array('addr'=>'x.x.x.x:22','fp'=>array('')),'tunnel',array('pub'=>'E:Identity.pub','pri'=>'E:Identity','type'=>'ssh-rsa'));
  2. $ssh=new FSSH(array('addr'=>'192.168.2.205','fp'=>array('54ECC700B844DCF0D40554A56C57C01E')),'root','123456');
  3. $ssh->shell();