try tmux
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
# 如果没有数据可读,则逐渐增加超时时间
|
||||
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.write(input_data)
|
||||
command_dict[socket_id]+=input_data
|
||||
if input_data=='\r':
|
||||
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:
|
||||
emit('error', {'message': f'Error sending input: {str(e)}'}, room=socket_id)
|
||||
else:
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user