在小韦云面板开发中,我们总结了PHP操作linux服务器两个场景:一是获取当前服务器的信息,这是要求实时返回的,属于同步(阻塞)模式。二是命令执行时间比较长,而且要看执行过程日志的,比如安装软件,执行软件更新等操作,这属于异步模式,PHP打开一个日志界面,然后调用执行命令,再通过websocket实时返回日志显示在界面上。
我们先看看小韦云面板使用的同步模式的函数:
$command
要是执行的命令,即shell 的命令
$show_err
直接显示错误信息,调试专用
函数详细见注释
function ssh_execute($command, $show_err = false)
{
if (!function_exists('ssh2_connect')) return ['code' => 3, 'msg' => 'PHP的ssh2扩展没安装'];
set_time_limit(0);
static $conn;
if (!$conn) {
try {
$conn = ssh2_connect(SSH_IP, '22', ['hostkey' => 'ssh-rsa']); //初始化连接
if (!ssh2_auth_pubkey_file($conn, 'root', SITE_PATH . '/config/.ssh/id_rsa.pub', SITE_PATH . '/config/.ssh/id_rsa', 'bctos')) {
return ['code' => 2, 'msg' => 'Public Key Authentication Failed'];
}
} catch (\Exception $e) {
return ['code' => 2, 'msg' => $e->getMessage()];
}
}
//执行命令
$stream = ssh2_exec($conn, $command);
$dio_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO); //获得标准输入输出留
$err_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR); //获得错误输出留
stream_set_blocking($err_stream, true);
stream_set_blocking($dio_stream, true);
$result_err = stream_get_contents($err_stream);
$output = stream_get_contents($dio_stream); //获取流的内容,即命令的返回内容
fclose($stream);
if (!empty($result_err)) {
if ($show_err) {
dump($command);
dump($result_err);
dump($output);
exit;
}
if (strpos($result_err, '[notice]') === false && strpos($result_err, '[Warning]') === false) {
return ['code' => 1, 'msg' => $result_err];
} else {
return ['code' => 0, 'msg' => $output];
}
}
return ['code' => 0, 'msg' => $output];
}
它先判断SSH2扩展有没有安装,然后通过证书连接上服务器,然后执行命令,最后通过stream_get_contents获取返回的内容。
异步模式的输入类似同步模式,但它通过2>&1
把所有输出的内容合并在一起,然后使用while (true)一直循环获取日志,直到出现我们自己约定==over==
结束标示才停止,如果由于异常无法取到结束标示,也会在超过30分钟自动结束。
日志推送到网页是使用web_msg方法,它是web-msg-sender(websocket工具)的发送方式
function ssh_execute_msg($command, $show_err = false)
{
if (!function_exists('ssh2_connect')) {
web_msg('PHP的ssh2扩展没安装==over==error');
return ['code' => 3, 'msg' => 'PHP的ssh2扩展没安装'];
}
set_time_limit(0);
static $conn;
if (!$conn) {
$conn = ssh2_connect(SSH_IP, '22'); //初始化连接
if (!ssh2_auth_pubkey_file($conn, 'root', SITE_PATH . '/config/.ssh/id_rsa.pub', SITE_PATH . '/config/.ssh/id_rsa', 'bctos')) {
return ['code' => 2, 'msg' => 'Public Key Authentication Failed'];
}
}
$command .= " 2>&1";
file_log($command, 'ssh_execute');
$stream = ssh2_exec($conn, $command);
sleep(1);
$dio_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO); //获得标准输入输出留
$time = time();
while (true) {
$data = stream_get_contents($dio_stream);
if (!empty($data)) {
web_msg($data);
if (strpos($data, "==over==") !== false) {
break;
} else {
$time = time();
}
}
if (empty($data)) {
if (time() - $time > 1800) {
//空转超过30分钟自动结束,防止长期运行
web_msg('超时结束==over==error');
break;
}
}
}
fclose($stream);
}
function web_msg($content)
{
// 指明给谁推送,为空表示向所有在线用户推送
$to_uid = 1;
// 推送的url地址,使用自己的服务器地址
$push_api_url = "http://" . SSH_IP . ":2121/";
$post_data = array(
"type" => "publish",
"content" => str_replace("\n", "\r\n", $content),
"to" => $to_uid,
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $push_api_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Expect:"));
curl_exec($ch);
curl_close($ch);
}
本文由小韦云原创,转载请注明出处:https://www.bctos.cn/doc/2/1952,否则追究其法律责任
关键词:SSH2 PHP 小韦云面板