Skip to content

Commit d39b731

Browse files
committed
refactor: 优化资源清理与连接管理,避免内存泄漏
1 parent 62860a5 commit d39b731

5 files changed

Lines changed: 127 additions & 28 deletions

File tree

backend/socket/socketHandler.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,21 @@ const { Client } = require('ssh2');
33
// 存储活跃的SSH连接
44
const activeConnections = new Map();
55

6+
// 清理连接的辅助函数
7+
const cleanupConnection = (socketId) => {
8+
const connection = activeConnections.get(socketId);
9+
if (connection) {
10+
try {
11+
if (connection.conn) {
12+
connection.conn.end();
13+
}
14+
} catch (error) {
15+
console.error('清理连接时出错:', error);
16+
}
17+
activeConnections.delete(socketId);
18+
}
19+
};
20+
621
const socketHandler = (io) => {
722
io.on('connection', (socket) => {
823
console.log('快速连接用户连接:', socket.id);
@@ -92,21 +107,19 @@ const socketHandler = (io) => {
92107

93108
// 断开SSH连接
94109
socket.on('disconnect-ssh', () => {
95-
const connection = activeConnections.get(socket.id);
96-
if (connection) {
97-
connection.conn.end();
98-
activeConnections.delete(socket.id);
99-
}
110+
cleanupConnection(socket.id);
100111
});
101112

102113
// 处理连接断开
103114
socket.on('disconnect', () => {
104115
console.log('用户断开连接:', socket.id);
105-
const connection = activeConnections.get(socket.id);
106-
if (connection) {
107-
connection.conn.end();
108-
activeConnections.delete(socket.id);
109-
}
116+
cleanupConnection(socket.id);
117+
});
118+
119+
// 处理错误
120+
socket.on('error', (error) => {
121+
console.error('Socket错误:', socket.id, error);
122+
cleanupConnection(socket.id);
110123
});
111124

112125
// 心跳检测

backend/utils/sftpHandler.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ class SftpHandler {
3737

3838
async disconnect() {
3939
if (this.sshClient) {
40-
this.sshClient.end();
40+
try {
41+
this.sshClient.end()
42+
} catch (error) {
43+
console.error('断开SFTP连接时出错:', error)
44+
}
4145
}
42-
this.isConnected = false;
43-
this.sftp = null;
46+
this.isConnected = false
47+
this.sftp = null
48+
this.sshClient = null
4449
}
4550

4651
async listDirectory(directory = '.') {

frontend/src/components/XtermTerminal.vue

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ let isComposing = false
109109
let wheelHandler = null
110110
let viewportWheelHandler = null
111111
let initialScrollTop = 0
112+
let mutationObserver = null
113+
let wheelObserver = null
112114
113115
// Context Menu State
114116
const contextMenu = ref({
@@ -242,14 +244,14 @@ const initTerminal = () => {
242244
243245
// IME Fix for composing
244246
setupHelperTextarea()
245-
const observer = new MutationObserver((mutations) => {
247+
mutationObserver = new MutationObserver((mutations) => {
246248
mutations.forEach((mutation) => {
247249
if (mutation.type === 'childList') {
248250
setupHelperTextarea()
249251
}
250252
})
251253
})
252-
observer.observe(terminalRef.value, { childList: true, subtree: true })
254+
mutationObserver.observe(terminalRef.value, { childList: true, subtree: true })
253255
254256
// Terminal input handler
255257
term.onData((data) => {
@@ -264,7 +266,7 @@ const initTerminal = () => {
264266
265267
// Optimize wheel scroll
266268
enableWheelScroll()
267-
const wheelObserver = new MutationObserver(() => enableWheelScroll())
269+
wheelObserver = new MutationObserver(() => enableWheelScroll())
268270
wheelObserver.observe(terminalRef.value, { childList: true, subtree: true })
269271
270272
wheelHandler = (e) => {
@@ -548,10 +550,28 @@ onMounted(async () => {
548550
})
549551
550552
onUnmounted(() => {
553+
// 断开所有 MutationObserver
554+
if (mutationObserver) {
555+
mutationObserver.disconnect()
556+
mutationObserver = null
557+
}
558+
if (wheelObserver) {
559+
wheelObserver.disconnect()
560+
wheelObserver = null
561+
}
562+
563+
// 清理终端实例
551564
if (term) {
552565
term.dispose()
553566
term = null
554567
}
568+
569+
// 清理 FitAddon
570+
if (fitAddon) {
571+
fitAddon = null
572+
}
573+
574+
// 移除事件监听器
555575
if (terminalRef.value && wheelHandler) {
556576
terminalRef.value.removeEventListener('wheel', wheelHandler)
557577
wheelHandler = null

frontend/src/stores/terminal.js

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,52 +51,104 @@ export const useTerminalStore = defineStore('terminal', () => {
5151
})
5252
}
5353

54+
// 存储事件处理器引用,便于清理
55+
const eventHandlers = {
56+
sshConnected: null,
57+
sshData: null,
58+
sshError: null,
59+
sshClosed: null,
60+
disconnect: null
61+
}
62+
5463
// 监听Socket事件(在Socket连接建立后设置)
5564
const setupSocketListeners = () => {
5665
if (!socket.value || socket.value._listenersSetup) return
5766

5867
// 标记已设置监听器,避免重复设置
5968
socket.value._listenersSetup = true
6069

61-
socket.value.on('ssh-connected', () => {
70+
// 创建事件处理器
71+
eventHandlers.sshConnected = () => {
6272
isConnected.value = true
6373
isConnecting.value = false
6474
connectionError.value = null
6575
console.log('SSH连接成功')
66-
})
76+
}
6777

68-
socket.value.on('ssh-data', (data) => {
78+
eventHandlers.sshData = (data) => {
6979
// 确保数据是字符串格式
7080
const output = typeof data === 'string' ? data : String(data)
7181
terminalOutput.value += output
72-
82+
7383
// 限制输出长度,防止内存溢出
7484
if (terminalOutput.value.length > 50000) {
7585
terminalOutput.value = terminalOutput.value.slice(-25000)
7686
}
77-
})
87+
}
7888

79-
socket.value.on('ssh-error', (data) => {
89+
eventHandlers.sshError = (data) => {
8090
isConnecting.value = false
8191
isConnected.value = false
8292
connectionError.value = data.error
8393
console.error('SSH连接错误:', data.error)
84-
})
94+
}
8595

86-
socket.value.on('ssh-closed', () => {
96+
eventHandlers.sshClosed = () => {
8797
isConnected.value = false
8898
isConnecting.value = false
8999
currentConnection.value = null
90100
console.log('SSH连接已关闭')
91-
})
101+
}
92102

93-
socket.value.on('disconnect', () => {
103+
eventHandlers.disconnect = () => {
94104
isConnected.value = false
95105
isConnecting.value = false
96106
console.log('Socket连接断开')
97107
// 清除监听器标记
108+
if (socket.value) {
109+
socket.value._listenersSetup = false
110+
}
111+
}
112+
113+
// 注册事件监听器
114+
socket.value.on('ssh-connected', eventHandlers.sshConnected)
115+
socket.value.on('ssh-data', eventHandlers.sshData)
116+
socket.value.on('ssh-error', eventHandlers.sshError)
117+
socket.value.on('ssh-closed', eventHandlers.sshClosed)
118+
socket.value.on('disconnect', eventHandlers.disconnect)
119+
}
120+
121+
// 移除所有事件监听器
122+
const removeSocketListeners = () => {
123+
if (!socket.value) return
124+
125+
if (eventHandlers.sshConnected) {
126+
socket.value.off('ssh-connected', eventHandlers.sshConnected)
127+
}
128+
if (eventHandlers.sshData) {
129+
socket.value.off('ssh-data', eventHandlers.sshData)
130+
}
131+
if (eventHandlers.sshError) {
132+
socket.value.off('ssh-error', eventHandlers.sshError)
133+
}
134+
if (eventHandlers.sshClosed) {
135+
socket.value.off('ssh-closed', eventHandlers.sshClosed)
136+
}
137+
if (eventHandlers.disconnect) {
138+
socket.value.off('disconnect', eventHandlers.disconnect)
139+
}
140+
141+
// 清空处理器引用
142+
eventHandlers.sshConnected = null
143+
eventHandlers.sshData = null
144+
eventHandlers.sshError = null
145+
eventHandlers.sshClosed = null
146+
eventHandlers.disconnect = null
147+
148+
// 清除标记
149+
if (socket.value) {
98150
socket.value._listenersSetup = false
99-
})
151+
}
100152
}
101153

102154
// 连接到SSH服务器
@@ -169,10 +221,13 @@ export const useTerminalStore = defineStore('terminal', () => {
169221
// 断开Socket连接
170222
const disconnectSocket = () => {
171223
if (socket.value) {
224+
// 先移除所有事件监听器
225+
removeSocketListeners()
226+
// 断开连接
172227
socket.value.disconnect()
173228
socket.value = null
174229
}
175-
230+
176231
isConnected.value = false
177232
isConnecting.value = false
178233
currentConnection.value = null

frontend/src/views/QuickConnect.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ import WebdavSync from '@/components/WebdavSync.vue'
319319
import GithubCard from '@/components/GithubCard.vue'
320320
321321
const HISTORY_KEY = 'webssh_quick_connect_history'
322+
const MAX_HISTORY_ITEMS = 100 // 最多保存100条历史记录
322323
323324
const router = useRouter()
324325
const themeStore = useThemeStore()
@@ -526,6 +527,11 @@ const saveToHistory = () => {
526527
527528
historyList.value.unshift(record)
528529
530+
// 限制历史记录数量,防止无限增长
531+
if (historyList.value.length > MAX_HISTORY_ITEMS) {
532+
historyList.value = historyList.value.slice(0, MAX_HISTORY_ITEMS)
533+
}
534+
529535
persistHistory()
530536
}
531537

0 commit comments

Comments
 (0)