From 50a570eccf918c3ab4f34057f3ab01a0bd38d5a2 Mon Sep 17 00:00:00 2001 From: Cai Date: Tue, 16 Sep 2025 11:59:19 +0000 Subject: [PATCH] try tmux --- code-server-like/app/__init__.py | 2 + code-server-like/app/extensions.py | 4 +- .../app/sockets/terminal_service.py | 96 ++++++++++++------- code-server-like/static/js/terminal.js | 10 +- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/code-server-like/app/__init__.py b/code-server-like/app/__init__.py index b03c82a..06c3bb1 100644 --- a/code-server-like/app/__init__.py +++ b/code-server-like/app/__init__.py @@ -1,3 +1,5 @@ +import eventlet +eventlet.monkey_patch() # 进行猴子补丁操作 from flask import Flask from .views.routes import main_bp from .views.file import file_bp diff --git a/code-server-like/app/extensions.py b/code-server-like/app/extensions.py index b17a397..67a14f7 100644 --- a/code-server-like/app/extensions.py +++ b/code-server-like/app/extensions.py @@ -3,12 +3,12 @@ from flask_socketio import SocketIO from .config import Config # 初始化扩展 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 = {} def init_extensions(app): 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 def register_namespaces(app): diff --git a/code-server-like/app/sockets/terminal_service.py b/code-server-like/app/sockets/terminal_service.py index c3ff252..626223d 100644 --- a/code-server-like/app/sockets/terminal_service.py +++ b/code-server-like/app/sockets/terminal_service.py @@ -1,5 +1,7 @@ import os import select +import time +import uuid import subprocess import threading 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') # 存储终端进程 +command_dict = {} +terminal_busy = {} terminals = {} 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 发给前端 使用 select 进行非阻塞的 I/O 操作,并实现动态流量控制 @@ -21,34 +53,23 @@ def start_terminal_process(socket_id): process = terminals[socket_id] # 获取子进程的 stdout 和 stderr - stdout_fd = process.stdout - stderr_fd = process.stderr - + stdout = process.stdout + stdin = process.stdin # 将 stdout 和 stderr 添加到 select 监听的文件描述符列表中 - fds = [stdout_fd, stderr_fd] # 初始等待时间 timeout = 0.1 max_timeout = 2 # 设置最大超时时间为 2 秒 while True: - # 使用 select 监听多个文件描述符,等待数据准备好 - readable, _, _ = select.select(fds, [], [], timeout) - - if readable: - # 有数据可读时,重置超时时间为 0.1 秒 + while True: + output = stdout.readline() + if not output: + break timeout = 0.1 - - for fd in readable: - # 读取进程的标准输出或错误输出 - output = fd.readline() - if output: - emit('terminal_output', {'data': output}, room=socket_id) - else: - # 如果没有数据可读,则逐渐增加超时时间 - timeout = min(timeout * 2, max_timeout) - - # 检查进程是否仍在运行,如果不在运行则退出循环 + namespace.emit('terminal_output', {'data': output+'\r'}, room=socket_id) + timeout = min(timeout * 2, max_timeout) + time.sleep(timeout) if process.poll() is not None: break @@ -71,21 +92,18 @@ class VSCLikeNameSpace(Namespace): # 使用 sudo 启动终端进程,以指定的 user_id 用户身份执行 try: process = subprocess.Popen( - ['sudo', '-u', user_id, 'bash'], # 使用指定的 user_id 启动 bash + ['sudo', '-u', user_id, 'bash', '-il'], # 使用指定的 user_id 启动 bash stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, # 确保输出是文本格式 ) 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'}) - join_room(path, namespace='/vsc-like') - - # 启动监听终端输出的线程 - thread = Thread(target=start_terminal_process, args=(socket_id,)) - thread.daemon = True - thread.start() + socketio.start_background_task(target=start_terminal_process, socket_id=socket_id, namespace=self) except Exception as e: emit('error', {'message': f'Error starting terminal process: {str(e)}'}) @@ -94,7 +112,7 @@ class VSCLikeNameSpace(Namespace): def on_send_input(self, data): socket_id = session.get('uuid') input_data = data.get('input') - + if socket_id not in command_dict:command_dict[socket_id]='' # 检查当前 socket_id 是否有对应的终端 if socket_id in terminals: # 获取该终端的锁进行线程同步 @@ -105,9 +123,19 @@ class VSCLikeNameSpace(Namespace): with lock: # 在锁的保护下操作终端 try: - terminals[socket_id].stdin.write(input_data + '\n') - terminals[socket_id].stdin.flush() - emit('terminal_output', {'data': f'Command sent: {input_data}'}, room=socket_id) + terminals[socket_id].stdin.write(input_data) + command_dict[socket_id]+=input_data + 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: emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id) else: diff --git a/code-server-like/static/js/terminal.js b/code-server-like/static/js/terminal.js index 65c8381..8510969 100644 --- a/code-server-like/static/js/terminal.js +++ b/code-server-like/static/js/terminal.js @@ -14,12 +14,17 @@ const socket = io('https://hsamooc.cn/vsc-like',{path:'/vsc-like-ws'}); // 启动连接并监听数据 socket.on('connected', function (data) { - term.write('Terminal connected\n'); + term.write('>'); }); // 监听后端返回的终端输出并显示 socket.on('terminal_output', function (data) { + if (data.data=='\x7f'){ + term.write('\b \b') + return + } 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: 'start' }); - // 如果终端被断开,显示消息 socket.on('error', function (error) { term.write(`\nError: ${error.message}`);