try tmux
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch() # 进行猴子补丁操作
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from .views.routes import main_bp
|
from .views.routes import main_bp
|
||||||
from .views.file import file_bp
|
from .views.file import file_bp
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ from flask_socketio import SocketIO
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
# 初始化扩展
|
# 初始化扩展
|
||||||
cors = CORS()
|
cors = CORS()
|
||||||
socketio = SocketIO(path='vsc-like-ws',cors_allowed_origins="*",max_payload_size=52428800, async_mode="eventlet") # 不直接传 app
|
socketio = SocketIO(path='vsc-like-ws',cors_allowed_origins="*",max_payload_size=5242880, async_mode="eventlet") # 不直接传 app
|
||||||
|
|
||||||
session_paths = {}
|
session_paths = {}
|
||||||
def init_extensions(app):
|
def init_extensions(app):
|
||||||
cors.init_app(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
cors.init_app(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
||||||
socketio.init_app(app, cors_allowed_origins="*", max_payload_size=52428800, async_mode="threading")
|
socketio.init_app(app, cors_allowed_origins="*", max_payload_size=5242880, async_mode="eventlet")
|
||||||
app.extensions["session_paths"] = session_paths
|
app.extensions["session_paths"] = session_paths
|
||||||
|
|
||||||
def register_namespaces(app):
|
def register_namespaces(app):
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
from flask import Blueprint, request, current_app, session
|
from flask import Blueprint, request, current_app, session
|
||||||
@@ -11,9 +13,39 @@ from ..extensions import socketio
|
|||||||
terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
|
terminal_bp = Blueprint('terminal', __name__, url_prefix='/vsc-like')
|
||||||
|
|
||||||
# 存储终端进程
|
# 存储终端进程
|
||||||
|
command_dict = {}
|
||||||
|
terminal_busy = {}
|
||||||
terminals = {}
|
terminals = {}
|
||||||
terminal_locks = {}
|
terminal_locks = {}
|
||||||
def start_terminal_process(socket_id):
|
|
||||||
|
def check_process_running(command_name):
|
||||||
|
try:
|
||||||
|
# 使用 pgrep 查找与给定命令相关的进程
|
||||||
|
result = subprocess.run(
|
||||||
|
["pgrep", "-f", command_name],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
return result.returncode == 0 # 如果返回值为 0,说明命令仍在运行
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error checking process: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def monitor_process(socket_id, command_name, namespace):
|
||||||
|
while True:
|
||||||
|
is_running = check_process_running(command_name)
|
||||||
|
if is_running:
|
||||||
|
print(f"Process '{command_name}' is still running.")
|
||||||
|
else:
|
||||||
|
print(f"Process '{command_name}' is not running.")
|
||||||
|
break # 停止检查,如果进程不再运行
|
||||||
|
|
||||||
|
time.sleep(0.1) # 每 0.1 秒检查一次
|
||||||
|
command_dict[socket_id] = ''
|
||||||
|
terminal_busy[socket_id] = False
|
||||||
|
namespace.emit('terminal_output',{'data': '>'})
|
||||||
|
|
||||||
|
|
||||||
|
def start_terminal_process(socket_id, namespace):
|
||||||
"""
|
"""
|
||||||
监听多个子进程的输出,并通过 WebSocket 发给前端
|
监听多个子进程的输出,并通过 WebSocket 发给前端
|
||||||
使用 select 进行非阻塞的 I/O 操作,并实现动态流量控制
|
使用 select 进行非阻塞的 I/O 操作,并实现动态流量控制
|
||||||
@@ -21,34 +53,23 @@ def start_terminal_process(socket_id):
|
|||||||
process = terminals[socket_id]
|
process = terminals[socket_id]
|
||||||
|
|
||||||
# 获取子进程的 stdout 和 stderr
|
# 获取子进程的 stdout 和 stderr
|
||||||
stdout_fd = process.stdout
|
stdout = process.stdout
|
||||||
stderr_fd = process.stderr
|
stdin = process.stdin
|
||||||
|
|
||||||
# 将 stdout 和 stderr 添加到 select 监听的文件描述符列表中
|
# 将 stdout 和 stderr 添加到 select 监听的文件描述符列表中
|
||||||
fds = [stdout_fd, stderr_fd]
|
|
||||||
|
|
||||||
# 初始等待时间
|
# 初始等待时间
|
||||||
timeout = 0.1
|
timeout = 0.1
|
||||||
max_timeout = 2 # 设置最大超时时间为 2 秒
|
max_timeout = 2 # 设置最大超时时间为 2 秒
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# 使用 select 监听多个文件描述符,等待数据准备好
|
while True:
|
||||||
readable, _, _ = select.select(fds, [], [], timeout)
|
output = stdout.readline()
|
||||||
|
if not output:
|
||||||
if readable:
|
break
|
||||||
# 有数据可读时,重置超时时间为 0.1 秒
|
|
||||||
timeout = 0.1
|
timeout = 0.1
|
||||||
|
namespace.emit('terminal_output', {'data': output+'\r'}, room=socket_id)
|
||||||
for fd in readable:
|
timeout = min(timeout * 2, max_timeout)
|
||||||
# 读取进程的标准输出或错误输出
|
time.sleep(timeout)
|
||||||
output = fd.readline()
|
|
||||||
if output:
|
|
||||||
emit('terminal_output', {'data': output}, room=socket_id)
|
|
||||||
else:
|
|
||||||
# 如果没有数据可读,则逐渐增加超时时间
|
|
||||||
timeout = min(timeout * 2, max_timeout)
|
|
||||||
|
|
||||||
# 检查进程是否仍在运行,如果不在运行则退出循环
|
|
||||||
if process.poll() is not None:
|
if process.poll() is not None:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -71,21 +92,18 @@ class VSCLikeNameSpace(Namespace):
|
|||||||
# 使用 sudo 启动终端进程,以指定的 user_id 用户身份执行
|
# 使用 sudo 启动终端进程,以指定的 user_id 用户身份执行
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
['sudo', '-u', user_id, 'bash'], # 使用指定的 user_id 启动 bash
|
['sudo', '-u', user_id, 'bash', '-il'], # 使用指定的 user_id 启动 bash
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True
|
text=True, # 确保输出是文本格式
|
||||||
)
|
)
|
||||||
terminals[socket_id] = process
|
terminals[socket_id] = process
|
||||||
|
terminal_busy[socket_id] = False
|
||||||
|
emit('connected', {'message': 'OK'})
|
||||||
|
join_room(socket_id, namespace='/vsc-like')
|
||||||
|
|
||||||
emit('connected', {'message': 'Terminal connected'})
|
socketio.start_background_task(target=start_terminal_process, socket_id=socket_id, namespace=self)
|
||||||
join_room(path, namespace='/vsc-like')
|
|
||||||
|
|
||||||
# 启动监听终端输出的线程
|
|
||||||
thread = Thread(target=start_terminal_process, args=(socket_id,))
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
emit('error', {'message': f'Error starting terminal process: {str(e)}'})
|
emit('error', {'message': f'Error starting terminal process: {str(e)}'})
|
||||||
@@ -94,7 +112,7 @@ class VSCLikeNameSpace(Namespace):
|
|||||||
def on_send_input(self, data):
|
def on_send_input(self, data):
|
||||||
socket_id = session.get('uuid')
|
socket_id = session.get('uuid')
|
||||||
input_data = data.get('input')
|
input_data = data.get('input')
|
||||||
|
if socket_id not in command_dict:command_dict[socket_id]=''
|
||||||
# 检查当前 socket_id 是否有对应的终端
|
# 检查当前 socket_id 是否有对应的终端
|
||||||
if socket_id in terminals:
|
if socket_id in terminals:
|
||||||
# 获取该终端的锁进行线程同步
|
# 获取该终端的锁进行线程同步
|
||||||
@@ -105,9 +123,19 @@ class VSCLikeNameSpace(Namespace):
|
|||||||
|
|
||||||
with lock: # 在锁的保护下操作终端
|
with lock: # 在锁的保护下操作终端
|
||||||
try:
|
try:
|
||||||
terminals[socket_id].stdin.write(input_data + '\n')
|
terminals[socket_id].stdin.write(input_data)
|
||||||
terminals[socket_id].stdin.flush()
|
command_dict[socket_id]+=input_data
|
||||||
emit('terminal_output', {'data': f'Command sent: {input_data}'}, room=socket_id)
|
if input_data=='\r':
|
||||||
|
terminals[socket_id].stdin.flush()
|
||||||
|
emit('terminal_output', {'data': f'\r\n'}, room=socket_id)
|
||||||
|
if terminal_busy[socket_id]==False:
|
||||||
|
terminal_busy[socket_id] = True
|
||||||
|
socketio.start_background_task(monitor_process, socket_id, command_dict[socket_id], namespace=self)
|
||||||
|
elif input_data=='\x03':
|
||||||
|
terminals[socket_id].stdin.flush()
|
||||||
|
emit('terminal_output', {'data': f'^C\x03\r\n'}, room=socket_id)
|
||||||
|
else:
|
||||||
|
emit('terminal_output', {'data': input_data}, room=socket_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id)
|
emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -14,12 +14,17 @@ const socket = io('https://hsamooc.cn/vsc-like',{path:'/vsc-like-ws'});
|
|||||||
|
|
||||||
// 启动连接并监听数据
|
// 启动连接并监听数据
|
||||||
socket.on('connected', function (data) {
|
socket.on('connected', function (data) {
|
||||||
term.write('Terminal connected\n');
|
term.write('>');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听后端返回的终端输出并显示
|
// 监听后端返回的终端输出并显示
|
||||||
socket.on('terminal_output', function (data) {
|
socket.on('terminal_output', function (data) {
|
||||||
|
if (data.data=='\x7f'){
|
||||||
|
term.write('\b \b')
|
||||||
|
return
|
||||||
|
}
|
||||||
term.write(data.data); // 输出来自后端的数据
|
term.write(data.data); // 输出来自后端的数据
|
||||||
|
console.log("get",data.data)
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听用户在终端中输入的命令
|
// 监听用户在终端中输入的命令
|
||||||
@@ -27,9 +32,6 @@ const socket = io('https://hsamooc.cn/vsc-like',{path:'/vsc-like-ws'});
|
|||||||
socket.emit('send_input', { input: data }); // 将用户输入通过 WebSocket 发送给后端
|
socket.emit('send_input', { input: data }); // 将用户输入通过 WebSocket 发送给后端
|
||||||
});
|
});
|
||||||
|
|
||||||
// 用户输入命令时,将其发送给后端
|
|
||||||
socket.emit('send_input', { input: 'start' });
|
|
||||||
|
|
||||||
// 如果终端被断开,显示消息
|
// 如果终端被断开,显示消息
|
||||||
socket.on('error', function (error) {
|
socket.on('error', function (error) {
|
||||||
term.write(`\nError: ${error.message}`);
|
term.write(`\nError: ${error.message}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user