本来是想通过PHP的proc_open和进程进行交互,可是中间的坑太多了,不得不转换一下思路,然后想起来宝塔有网页版shell客户端,然后研究了一下,嘿嘿,发现能成 。

一、前期准备

PHP连接ssh是基于第三方拓展库,PECL/ssh2( libssh2的php扩展,允许php程序调用libssh2中的函数)

然后有一个现成的、封装好大部分常用操作的库phpseclib

通过swoole的协程实现SSH的读和写并发进行以及websocket和浏览器进行通信。

1、安装ssh2拓展库

1.1、Linux安装

首先要安装libssh2(libssh2是一个C 函数库,用来实现SSH2协议。)

yum install libssh2 libssh2-devel 

然后通过pcel安装ssh2拓展 ,找准版本

pecl install ssh2-1.1.2

当然也可以通过phpize进行手动安装。

1.2、window安装

libssh2好像一般都有,没有就下载丢到系统里,主要是安装ssh2。根据自己PHP的版本去下载,可以看下自己的php版本,以及是32位的还是64位的,32位的下载x86, 64位的下载x64

下载地址

php.ini中加入 extension=php_ssh2.dll ,完事。

2、swoole安装

参考官网:https://wiki.swoole.com/#/environment

3、phpseclib

官网:https://phpseclib.com,composer安装即可:

composer require phpseclib/phpseclib:~3.0

二、编写代码

测试Demo:http://cname.teiao.com:5707/

通过swoole创建一个websocket,连接成功时创建一个协程专门读取ssh返回的内容发送到websocket,客户端发送消息时转发给shell。

以下是简单的功能实现,不可应用于生产,经测试,实际使用过程中某些命令的输出需要进行特殊处理。

1、swoole.php

<?php

include_once 'include/functions.php';
include_once 'vendor/autoload.php';

use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
use Swoole\Coroutine\Http\Server;

use Swoole\Coroutine;
use function Swoole\Coroutine\go;
use function Swoole\Coroutine\run;
use function Swoole\Coroutine\defer;
use phpseclib3\Net\SSH2;



/*
 * 设置协程运行相关的参数
 * */
Co::set([
    'socket_timeout'=>-1, //tcp超时
    'hook_flags' => SWOOLE_HOOK_ALL  //HOOK函数范围
]);


/*
 * 创建协程容器
 * */
run(function () {

    /*
     * 第三个参数 代表是否开启ssl
     * */
    $server = new Server('0.0.0.0', 5707, false);

    $server->handle('/ws', function (Request $request, Response $ws) {

        /*websocket协议*/
        $ws->upgrade();

        /*连接ssh*/
        $ssh = new SSH2('localhost',22);

        /*如果登录失败*/
        if (!$ssh->login('root', 'Qq461625091@')) {
            $ws->close();
            return;
        }

        /*命令输出内容的读取时间*/
        $ssh->setTimeout(0.1);



        /*
         * 创建协程,专门输出命令行内容
         * */
        $subscribe=function () use($ws,$ssh){


            /*
             * 保存id,用于取消协程
             * */
            $ws->Gid = go(function () use ($ws,$ssh){

                /*
                 * 协程退出时清理
                 * */
                defer(function () use ($ssh,$ws) {
                    /*
                     * 退出
                     * */
                    logs($ws->qq.',已断开链接!');
                    $ssh->disconnect();
                });


                try {

                    while (true){
                        $msg=$ssh->read('username@username:~$');
                        if(!empty($msg)){
                            $ws->push($msg);
                        }
                    }

                } catch (\Throwable $e) {
                    logs('读取异常');
                }

            });
        };


        /*
         * 清理
         * */
        $quit=function ($log) use ($ws){

            logs($log);//记录退出原因

            /*
             * 如果协程已经运行
             * */
            if(isset($ws->Gid)){
                Coroutine::cancel($ws->Gid); //关闭协程
            }

            $ws->close(); //断开ws

        };


        /*
         * 正常处理逻辑
         * */

        $subscribe(); //开始订阅

        $cmd=[
            'ps -ef',
            'ping 127.0.0.1',
            'ifconfig',
            "\x03"
        ];


        while (true) {

            $frame = $ws->recv(); //阻塞接收消息

            if ($frame === '') {

                $quit("断开连接,收到空数据!");
                break;

            } else if ($frame === false) {

                $quit(swoole_last_error());
                break;

            } else {

                if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
                    $quit("用户主动关闭\n");
                    break;
                }

                /*
                  * 如果不在测试命令,则终止
                  * */
                if(!in_array($frame->data,$cmd)){
                    continue;
                }

                $ssh->write($frame->data."\n"); // note the "\n"

            }
        }
    });


    /*
     * 输出默认测试模板
     * */
    $server->handle('/', function (Request $request, Response $response) {
        $response->end(getTest());
    });

    $server->start();
});

2、function.php

<?php

/*
 * 打印测试的html模板
 * */
function getTest(): string
{
    $test = <<<HTML
        <!DOCTYPE html>
        <html lang="zh-cn" xmlns="http://www.w3.org/1999/html">
        <head>
            <meta charset="UTF-8"/>
            <meta charset="UTF-8"/>
            <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
            <title>Web SSH客户端</title>
            <link href="https://nicen.cn/wp-content/themes/document/favicon.ico" rel="external nofollow"  rel="shortcut icon" type="image/x-icon"/>
            <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js" type="application/javascript"></script>
            <script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/keyboardjs/2.6.2/keyboard.min.js" type="application/javascript"></script>
            <style>
                body{
                    background-color: #000000;
                    color: #e2e2e2;
                    padding: 15px;
                }
                 input{
                    background-color: black;
                    border: none;
                    color: white;
                    outline: none;
                    font-size: 17px;
                }
            </style>
        </head>
        <body>
        <h1>Web SSH测试</h1>
        <div>须知:测试环境只支持:ps -ef、ping 127.0.0.1、ifconfig,三个命令。</div>
        <div>提示:回车提交、ctrl c中断(终端现在连接的是网站的主机)</div>
        <br />
        <main>
             <span id="content"></span>
             <input type="text">
        </main>

        </body>
        <script>

         window.onload=function (){

            let content=$("#content");
            let input= $('input');
            let wsServer = 'ws://cname.teiao.com:5707/ws';
            let websocket = new WebSocket(wsServer);

            websocket.onopen = function (evt) {
                content.append("Connected to WebSocket server.<br />");
            };

            websocket.onclose = function (evt) {
                content.append("Disconnected.<br />");
            };

            websocket.onmessage = function (evt) {
                content.append(evt.data.replaceAll("\\n",'<br />'));
                input.val("");
                $(window).scrollTop(document.documentElement.scrollHeight)  
            };

            websocket.onerror = function (evt, e) {
                content.append("Error occured: "   evt.data "<br />");
            };


            input.focus();

            /*
            * 自动聚焦
            * */
            $(window).on("click",function (){
                input.focus();
            })

            /*
            * 回车提交
            * */
            keyboardJS.bind('enter', (e) => {
              websocket.send(input.val());
            });

            /*
            * ctrl c
            * */
             keyboardJS.bind('ctrl > c', (e) => {
              websocket.send("\x03");
            });
        }


        </script>
HTML;

    return $test;
}


/*
 * 记录日志
 * */
function logs(string $log, bool $flag = true): void
{
    $time = date("Y-m-d H:i:s", time());

    if ($flag) {
        echo $time . ',' . $log . "\n";
    } else {
        file_put_contents('log.txt', $time . ',' . $log . "\n", FILE_APPEND);
    }
}

3、运行

php swoole.php

以上就是PHP Swoole实现web版的shell客户端详解的详细内容,更多关于PHP Swoole shell客户端的资料请关注Devmax其它相关文章!

PHP+Swoole实现web版的shell客户端详解的更多相关文章

  1. 为什么PATH不适用于从Xcode执行的自定义shell脚本?

    我观察到Xcode在运行脚本阶段执行的自定义shell脚本没有设置任何环境变量.他们有很多其他变量,但不是PATH.有可能解决这个问题,怎么样?我只想运行一个应该在路径中的工具,我不想开始手动检查可能的位置.解决方法你可以明确地找到用户.bashrc,.profile等.或者更好的是,运行类似的东西这不会有污染其他变量的风险.

  2. ios – Xcode Server 4.0 git从构建触发脚本推送

    我为一个托管在github上的项目安装了一个XcodeBot.我按照步骤和设置机器人来使用我现有的SSH密钥.验证成功,项目结算和建立.然后,我在预触发器操作中添加了一个shell脚本,它增加了plist中的版本,将其标记,并将该更改提交到github.但是当我尝试从shell脚本执行gitpush时,我得到:–推送到git@github.com:spex-app/spex-ios.git权限被拒

  3. ios – 超时等待120秒的模拟器启动

    看起来像Teamcity代理(TC版本是9.0EAP)不能通过测试shell脚本运行iOS模拟器.我正在使用BuildStep:命令行,它运行自定义脚本并将参数传递给它.通过使用shell脚本../bin/mac.launchd.sh,在MacOSXYosemite10.10上启动了Teamcity代理.构建日志错误:我的shell脚本进行测试:我也试过从这个question的解决方案,但没有帮助

  4. 从iOS应用程序发送帖子到PHP脚本不工作…简单的解决方案就像

    我之前已经做了好几次了但是由于某些原因我无法通过这个帖子…我尝试了设置为_POST且没有的变量的PHP脚本……当它们未设置为发布时它工作精细.这是我的iOS代码:这里是PHP的一大块,POST变量不在正确的位置?我想这对于更有经验的开发人员来说是一个相当简单的答案,感谢您的帮助!解决方法$_POST是一个数组,而不是一个函数.您需要使用方括号来访问数组索引:

  5. 在附加到XCode项目的shell脚本中无法识别$SRCROOT

    尝试运行附加到我的xcode项目的简单脚本,如下所示……如果我在XCode之外运行脚本似乎运行正常但是从XCode运行时我收到以下错误…似乎SRCROOT变量在脚本中是不可检测的,但我的理解是这是应该传递并可由脚本访问的环境变量之一.任何想法?解决方法原来这是我的错.该剧本实际上根本没有被调用.在XCode中,我指的是使用脚本的路径…更正了问题,我现在可以从我的脚本访问$SRCROOT.

  6. swift学习2 元组 tuples

    swift中出现了一种新的数据结构,非常牛掰的元组tuples如果懂PHP的猿,会发现这个元组和PHP的数组非常类似,同样是可以默认不指定key,也可以指定key目前的学习疑问是,如何进行元组的遍历?

  7. 尝试使用swift mailer,gmail smtp,php发送邮件

    这里是我的代码:在运行时出现此错误…

  8. swift – Xcode 8 Shell脚本调用错误

    我试图解决这个问题几个小时,但它仍然存在.在论坛上尝试了一切,没有任何帮助.我正在使用Cocoapods最新版本1.2.0.beta.1当我尝试构建项目时,它给了我:再次安装pod并运行该项目.使用命令:

  9. android – 来自adb的’grep’命令的问题

    当我用adb写的时候:我得到错误输出:但如果我将它拆分为两个操作符:它工作正常.如果唯一的方法是将它拆分为两个–首先进入adbshell,然后运行Inquire,有一种方法可以从c#中执行此操作吗?

  10. 如何在Android Shell中获得以毫秒为单位的时间?

    3个我正在尝试制作一个在Android上运行的shell脚本.我需要以比秒更精确的时间来测量时间–毫秒或纳秒.我怎样才能在AndroidShell中执行此操作?id=stericson.busybox&hl=en然后你可以这样做:adbshell“busyBoxdate%s”以秒为单位获得时间:adbshell“busyBoxdate%N”获得纳秒秒.

随机推荐

  1. PHP个人网站架设连环讲(一)

    先下一个OmnihttpdProffesinalV2.06,装上就有PHP4beta3可以用了。PHP4给我们带来一个简单的方法,就是使用SESSION(会话)级变量。但是如果不是PHP4又该怎么办?我们可以假设某人在15分钟以内对你的网页的请求都不属于一个新的人次,这样你可以做个计数的过程存在INC里,在每一个页面引用,访客第一次进入时将访问时间送到cookie里。以后每个页面被访问时都检查cookie上次访问时间值。

  2. PHP函数学习之PHP函数点评

    PHP函数使用说明,应用举例,精简点评,希望对您学习php有所帮助

  3. ecshop2.7.3 在php5.4下的各种错误问题处理

    将方法内的函数,分拆为2个部分。这个和gd库没有一点关系,是ecshop程序的问题。会出现这种问题,不外乎就是当前会员的session或者程序对cookie的处理存在漏洞。进过本地测试,includes\modules\integrates\ecshop.php这个整合自身会员的类中没有重写integrate.php中的check_cookie()方法导致,验证cookie时返回的username为空,丢失了登录状态,在ecshop.php中重写了此方法就可以了。把他加到ecshop.php的最后面去就可

  4. NT IIS下用ODBC连接数据库

    $connection=intodbc_connect建立数据库连接,$query_string="查询记录的条件"如:$query_string="select*fromtable"用$cur=intodbc_exec检索数据库,将记录集放入$cur变量中。再用while{$var1=odbc_result;$var2=odbc_result;...}读取odbc_exec()返回的数据集$cur。最后是odbc_close关闭数据库的连接。odbc_result()函数是取当前记录的指定字段值。

  5. PHP使用JpGraph绘制折线图操作示例【附源码下载】

    这篇文章主要介绍了PHP使用JpGraph绘制折线图操作,结合实例形式分析了php使用JpGraph的相关操作技巧与注意事项,并附带源码供读者下载参考,需要的朋友可以参考下

  6. zen_cart实现支付前生成订单的方法

    这篇文章主要介绍了zen_cart实现支付前生成订单的方法,结合实例形式详细分析了zen_cart支付前生成订单的具体步骤与相关实现技巧,需要的朋友可以参考下

  7. Thinkphp5框架实现获取数据库数据到视图的方法

    这篇文章主要介绍了Thinkphp5框架实现获取数据库数据到视图的方法,涉及thinkPHP5数据库配置、读取、模型操作及视图调用相关操作技巧,需要的朋友可以参考下

  8. PHP+jquery+CSS制作头像登录窗(仿QQ登陆)

    本篇文章介绍了PHP结合jQ和CSS制作头像登录窗(仿QQ登陆),实现了类似QQ的登陆界面,很有参考价值,有需要的朋友可以了解一下。

  9. 基于win2003虚拟机中apache服务器的访问

    下面小编就为大家带来一篇基于win2003虚拟机中apache服务器的访问。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  10. Yii2中组件的注册与创建方法

    这篇文章主要介绍了Yii2之组件的注册与创建的实现方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下

返回
顶部