This commit is contained in:
Cai
2025-09-16 11:59:19 +00:00
parent 39ac606c93
commit 50a570eccf
4 changed files with 72 additions and 40 deletions

View File

@@ -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

View File

@@ -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):

View File

@@ -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:
# 读取进程的标准输出或错误输出
output = fd.readline()
if output:
emit('terminal_output', {'data': output}, room=socket_id)
else:
# 如果没有数据可读,则逐渐增加超时时间
timeout = min(timeout * 2, max_timeout) timeout = min(timeout * 2, max_timeout)
time.sleep(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)
command_dict[socket_id]+=input_data
if input_data=='\r':
terminals[socket_id].stdin.flush() terminals[socket_id].stdin.flush()
emit('terminal_output', {'data': f'Command sent: {input_data}'}, room=socket_id) 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:

View File

@@ -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}`);