fix: Web端Socket.IO连接配置修复
- 传输方式从websocket-only改为polling+websocket双传输,增加连接可靠性 - 连接超时从20秒增加到60秒,匹配后端配置 - 重连次数从20次改为Infinity,永不放弃重连 - connect_error不再每次都设置error状态,避免阻断Socket.IO内置重连 - disconnect事件区分断开原因,transport error时保持connecting状态等待自动重连 - 新建连接前先断开旧socket,防止连接泄漏 - ConnectDialog默认地址从ws://改为http://,符合Socket.IO v4规范 - URL验证器兼容http/https/ws/wss四种协议 - connectToServer自动将ws://转换为http://,向后兼容旧地址
This commit is contained in:
@@ -21,20 +21,19 @@ const ConnectDialog: React.FC<ConnectDialogProps> = ({
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { status: connectionStatus, serverUrl } = useSelector((state: RootState) => state.connection)
|
||||
|
||||
// 预设服务器地址选项
|
||||
// Preset server addresses
|
||||
const presetServers = [
|
||||
'ws://localhost:3001',
|
||||
'ws://127.0.0.1:3001',
|
||||
'ws://192.168.1.100:3001', // 示例局域网地址
|
||||
'http://localhost:3001',
|
||||
'http://127.0.0.1:3001',
|
||||
'http://192.168.1.100:3001',
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
// 如果已有服务器地址,使用它作为默认值
|
||||
if (serverUrl) {
|
||||
form.setFieldValue('serverUrl', serverUrl)
|
||||
} else {
|
||||
form.setFieldValue('serverUrl', 'ws://localhost:3001')
|
||||
form.setFieldValue('serverUrl', 'http://localhost:3001')
|
||||
}
|
||||
}
|
||||
}, [visible, serverUrl, form])
|
||||
@@ -61,20 +60,23 @@ const ConnectDialog: React.FC<ConnectDialogProps> = ({
|
||||
form.setFieldValue('serverUrl', url)
|
||||
}
|
||||
|
||||
const validateWebSocketUrl = (_: any, value: string) => {
|
||||
const validateServerUrl = (_: any, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error('请输入服务器地址'))
|
||||
}
|
||||
|
||||
if (!value.startsWith('ws://') && !value.startsWith('wss://')) {
|
||||
return Promise.reject(new Error('地址必须以 ws:// 或 wss:// 开头'))
|
||||
if (!value.startsWith('http://') && !value.startsWith('https://') &&
|
||||
!value.startsWith('ws://') && !value.startsWith('wss://')) {
|
||||
return Promise.reject(new Error('地址必须以 http:// 或 https:// 开头'))
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(value)
|
||||
// Normalize ws:// -> http:// for URL validation
|
||||
const normalized = value.replace(/^ws:\/\//, 'http://').replace(/^wss:\/\//, 'https://')
|
||||
new URL(normalized)
|
||||
return Promise.resolve()
|
||||
} catch {
|
||||
return Promise.reject(new Error('请输入有效的WebSocket地址'))
|
||||
return Promise.reject(new Error('请输入有效的服务器地址'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +105,19 @@ const ConnectDialog: React.FC<ConnectDialogProps> = ({
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
serverUrl: 'ws://localhost:3001'
|
||||
serverUrl: 'http://localhost:3001'
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="服务器地址"
|
||||
name="serverUrl"
|
||||
rules={[
|
||||
{ validator: validateWebSocketUrl }
|
||||
{ validator: validateServerUrl }
|
||||
]}
|
||||
help="输入WebSocket服务器地址,例如: ws://localhost:3001"
|
||||
help="输入服务器地址,例如: http://localhost:3001"
|
||||
>
|
||||
<Input
|
||||
placeholder="ws://localhost:3001"
|
||||
placeholder="http://localhost:3001"
|
||||
size="large"
|
||||
autoFocus
|
||||
onPressEnter={handleConnect}
|
||||
|
||||
@@ -159,29 +159,42 @@ const RemoteControlApp: React.FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Disconnect existing socket before creating a new one
|
||||
if (webSocket) {
|
||||
console.log('[Socket] Disconnecting existing socket before reconnect')
|
||||
webSocket.disconnect()
|
||||
dispatch(setWebSocket(null))
|
||||
}
|
||||
|
||||
dispatch(setConnectionStatus('connecting'))
|
||||
dispatch(setServerUrl(url))
|
||||
|
||||
// Normalize ws:// -> http:// for Socket.IO v4 client
|
||||
let normalizedUrl = url
|
||||
if (normalizedUrl.startsWith('ws://')) {
|
||||
normalizedUrl = normalizedUrl.replace(/^ws:\/\//, 'http://')
|
||||
} else if (normalizedUrl.startsWith('wss://')) {
|
||||
normalizedUrl = normalizedUrl.replace(/^wss:\/\//, 'https://')
|
||||
}
|
||||
|
||||
// Socket.IO v4 client config
|
||||
const socket = io(url, {
|
||||
// Use websocket transport for low-latency streaming
|
||||
transports: ['websocket'],
|
||||
const socket = io(normalizedUrl, {
|
||||
// Transport: polling first then upgrade to websocket for reliability
|
||||
transports: ['polling', 'websocket'],
|
||||
upgrade: true,
|
||||
|
||||
// Reconnection config
|
||||
reconnection: true,
|
||||
reconnectionDelay: 1000, // 1秒重连延迟
|
||||
reconnectionDelayMax: 5000, // 最大5秒重连延迟
|
||||
reconnectionAttempts: 20, // 最多重连20次
|
||||
reconnectionDelay: 1000,
|
||||
reconnectionDelayMax: 10000,
|
||||
reconnectionAttempts: Infinity,
|
||||
|
||||
// Timeout config (match server)
|
||||
timeout: 20000, // 连接超时
|
||||
// Timeout config (match server side)
|
||||
timeout: 60000,
|
||||
|
||||
// Force new connection
|
||||
forceNew: true,
|
||||
|
||||
// Other config
|
||||
autoConnect: true,
|
||||
upgrade: false, // 已经是websocket,无需升级
|
||||
|
||||
// Auth: carry JWT token
|
||||
auth: {
|
||||
@@ -208,35 +221,41 @@ const RemoteControlApp: React.FC = () => {
|
||||
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('与服务器断开连接')
|
||||
dispatch(setConnectionStatus('disconnected'))
|
||||
dispatch(addNotification({
|
||||
type: 'warning',
|
||||
title: '连接断开',
|
||||
message: '与服务器的连接已断开',
|
||||
}))
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log('[Socket] disconnect, reason:', reason)
|
||||
|
||||
if (reason === 'io server disconnect' || reason === 'io client disconnect') {
|
||||
// Server or client explicitly disconnected, no auto-reconnect
|
||||
dispatch(setConnectionStatus('disconnected'))
|
||||
dispatch(addNotification({
|
||||
type: 'warning',
|
||||
title: '连接断开',
|
||||
message: '与服务器的连接已断开',
|
||||
}))
|
||||
} else {
|
||||
// Transport error / ping timeout etc. - Socket.IO will auto-reconnect
|
||||
dispatch(setConnectionStatus('connecting'))
|
||||
console.log('[Socket] Will auto-reconnect...')
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
console.error('连接错误:', error)
|
||||
dispatch(setConnectionStatus('error'))
|
||||
console.error('[Socket] connect_error:', error?.message || error)
|
||||
|
||||
if (autoConnectAttempted) {
|
||||
// 自动连接失败,显示连接对话框让用户手动连接
|
||||
dispatch(addNotification({
|
||||
type: 'warning',
|
||||
title: '自动连接失败',
|
||||
message: '无法自动连接到本地服务器,请手动输入服务器地址',
|
||||
}))
|
||||
setConnectDialogVisible(true)
|
||||
} else {
|
||||
// Only show dialog and set error state when reconnection is exhausted
|
||||
// Socket.IO will auto-reconnect; don't interfere by setting 'error' status on every attempt
|
||||
if (!socket.active) {
|
||||
// socket.active === false means reconnection gave up
|
||||
dispatch(setConnectionStatus('error'))
|
||||
dispatch(addNotification({
|
||||
type: 'error',
|
||||
title: '连接失败',
|
||||
message: `无法连接到服务器: ${error.message}`,
|
||||
message: `无法连接到服务器: ${error?.message || '未知错误'}`,
|
||||
}))
|
||||
setConnectDialogVisible(true)
|
||||
} else {
|
||||
// Still reconnecting, keep 'connecting' status
|
||||
dispatch(setConnectionStatus('connecting'))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user