作者:L、218
发布平台:CSDN
发布时间:2025年12月2日


🎯 导读:在上一篇文章中,我们用 Electron 模拟了鸿蒙的“服务卡片”UI 风格。本文将更进一步——实现一个具备设备发现、数据流转和状态同步能力的“类超级终端”系统原型

我们将基于 Electron + WebSocket + Node.js 构建一个多窗口桌面应用集群,模拟鸿蒙 OS 中“手机 → 平板 → 智慧屏”的无缝流转体验。

📌 关键词:鸿蒙分布式理念 | Electron 多实例通信 | 局域网设备发现 | 数据流转动画 | 跨窗口拖拽


一、目标:打造“桌面版超级终端”

✅ 最终效果预览

https://img-blog.csdnimg.cn/direct/9a7e8f3c4d6b4e2a8f1c2b3d4e5f6a7b.gif

动态图说明:

  • 左侧为主控制台(Main Hub)
  • 右侧为多个“设备窗口”(Device Window)
  • 拖动卡片可选择“流转到某设备”
  • 流转后原窗口消失,目标窗口显示内容
  • 所有设备通过局域网自动发现并注册

二、技术架构设计

+------------------+       +------------------+
|   主控制台        |<----->|   设备窗口 A      |
| (Main Hub)       | WS    | (Device Win A)   |
+--------+---------+       +------------------+
         |
         | 广播发现
         v
+------------------+       +------------------+
|   设备窗口 B      |<----->|   其他服务模块    |
| (Device Win B)   |       | (e.g. Web API)   |
+------------------+       +------------------+

所有节点使用 UDP 广播进行设备发现,WebSocket 进行实时通信。

三、核心功能实现

第一步:项目结构升级

/harmony-electron-cluster
├── main/
│   ├── app.js               # 主进程逻辑
│   ├── network-discovery.js # 网络发现模块
│   └── message-bus.js       # 消息总线
├── windows/
│   ├── main-hub.html        # 主控制台界面
│   ├── device-window.html   # 设备窗口界面
│   └── style.css
├── assets/
│   └── logo-small.png       # 小图标用于设备标识
├── preload.js
├── package.json
└── renderer.js              # 渲染层通用逻辑

第二步:局域网设备自动发现(UDP广播)

我们使用 dgram 模块实现简易的设备发现协议。

network-discovery.js

const dgram = require('dgram')
const os = require('os')

const BROADCAST_ADDRESS = '255.255.255.255'
const PORT = 44444
const SERVICE_NAME = 'HarmonyElectronNode'

class NetworkDiscovery {
  constructor(nodeType = 'unknown') {
    this.nodeType = nodeType
    this.host = this.getIPAddress()
    this.clients = new Set() // 存储已发现的设备
    this.socket = dgram.createSocket({ type: 'udp4', reuseAddr: true })
  }

  getIPAddress() {
    const interfaces = os.networkInterfaces()
    for (let iface in interfaces) {
      for (let alias of interfaces[iface]) {
        if (alias.family === 'IPv4' && !alias.internal) {
          return alias.address
        }
      }
    }
    return '127.0.0.1'
  }

  startBroadcasting() {
    this.socket.bind(() => {
      this.socket.setBroadcast(true)
      console.log(`[${this.nodeType}] 开始广播...`)
      
      // 每3秒广播一次
      setInterval(() => {
        const msg = JSON.stringify({
          type: 'HELLO',
          from: this.host,
          name: SERVICE_NAME,
          nodeType: this.nodeType
        })
        this.socket.send(msg, 0, msg.length, PORT, BROADCAST_ADDRESS)
      }, 3000)
    })
  }

  startListening(callback) {
    this.socket.on('message', (msg, rinfo) => {
      try {
        const data = JSON.parse(msg.toString())
        if (data.from !== this.host && data.type === 'HELLO') {
          const clientInfo = { ...data, port: rinfo.port, address: rinfo.address }
          this.clients.add(clientInfo.from)
          callback(clientInfo) // 回调通知新设备上线
        }
      } catch (e) {
        console.error('解析广播消息失败:', e)
      }
    })
  }

  getClients() {
    return Array.from(this.clients)
  }
}

module.exports = NetworkDiscovery

💡 使用 UDP 广播实现轻量级设备发现,避免中心服务器依赖。


第三步:WebSocket 实时通信总线

message-bus.js

const WebSocket = require('ws')

class MessageBus {
  constructor(port = 8080) {
    this.port = port
    this.server = new WebSocket.Server({ port })
    this.clients = new Map() // host -> ws
  }

  start(onMessage) {
    this.server.on('connection', (ws, req) => {
      const clientIP = req.connection.remoteAddress.replace(/^.*:/, '')
      
      console.log(`客户端连接: ${clientIP}`)
      this.clients.set(clientIP, ws)

      ws.on('message', (data) => {
        try {
          const msg = JSON.parse(data)
          onMessage(msg, clientIP, ws)
        } catch (e) {
          console.error('消息解析错误:', e)
        }
      })

      ws.on('close', () => {
        this.clients.delete(clientIP)
        console.log(`客户端断开: ${clientIP}`)
      })
    })

    console.log(`消息总线启动于 ws://localhost:${this.port}`)
  }

  broadcast(message, exclude = null) {
    const payload = JSON.stringify(message)
    this.clients.forEach((ws, ip) => {
      if (ip !== exclude && ws.readyState === WebSocket.OPEN) {
        ws.send(payload)
      }
    })
  }

  sendTo(ip, message) {
    const ws = this.clients.get(ip)
    if (ws && ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(message))
    }
  }
}

module.exports = MessageBus

第四步:主进程整合网络能力 (app.js)

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const NetworkDiscovery = require('./main/network-discovery')
const MessageBus = require('./main/message-bus')

let mainWindow
let deviceWindows = []
let discovery, messageBus

function createMainWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 700,
    title: "鸿蒙超级终端 - 控制中心",
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true
    }
  })
  mainWindow.loadFile('windows/main-hub.html')
}

function createDeviceWindow() {
  const win = new BrowserWindow({
    width: 400,
    height: 600,
    title: "模拟设备终端",
    frame: false,
    transparent: true,
    alwaysOnTop: false,
    resizable: true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  win.loadFile('windows/device-window.html')
  deviceWindows.push(win)

  win.on('closed', () => {
    deviceWindows = deviceWindows.filter(w => w !== win)
  })

  return win
}

app.whenReady().then(() => {
  createMainWindow()

  // 启动网络服务
  discovery = new NetworkDiscovery('MainHub')
  messageBus = new MessageBus(8080)

  messageBus.start((msg, ip) => {
    if (msg.type === 'DEVICE_REGISTER') {
      console.log(`设备注册: ${ip} (${msg.deviceName})`)
      mainWindow.webContents.send('device-online', { ip, name: msg.deviceName })
    }
  })

  discovery.startListening((client) => {
    if (client.nodeType === 'DeviceWindow') {
      messageBus.sendTo(client.from, { type: 'WELCOME', text: '欢迎加入超级终端!' })
    }
  })

  discovery.startBroadcasting()

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
  })
})

// 接收渲染层指令
ipcMain.on('open-device-window', () => {
  const win = createDeviceWindow()
  win.webContents.on('did-finish-load', () => {
    win.webContents.send('init-device', { ip: discovery.host })
  })
})

ipcMain.on('transfer-data', (event, payload) => {
  const { targetIp, data } = payload
  messageBus.sendTo(targetIp, {
    type: 'DATA_TRANSFER',
    data,
    from: discovery.host,
    timestamp: Date.now()
  })
})

第五步:主控制台 UI (main-hub.html)

四、运行与测试

步骤:

  1. 安装依赖
npm install electron ws
  1. 添加启动命令

2.启动主程序

npm start
  1. 点击 “打开模拟设备” 创建多个设备窗口
  2. 拖动卡片选择目标设备完成“流转”

五、总结:我们实现了什么?

鸿蒙特性 本项目实现程度 技术手段
设备发现 ✅ 自动识别局域网内节点 UDP广播
数据流转 ✅ 卡片内容跨窗迁移 WebSocket
多端协同 ✅ 多窗口状态联动 消息总线
分布式调度 ⚠️ 原型级支持 主从架构模拟
性能体验 ❌ 存在延迟 Chromium 开销

🎯 这不是真正的鸿蒙,但它是理解鸿蒙思想的一扇窗


六、未来展望

  1. 接入真实鸿蒙设备 API(如开放 HTTP 接口)
  2. 结合 TSC + Vite 提升开发体验
  3. 引入 AI 能力实现智能流转决策
  4. 打包为独立桌面应用,支持开机自启

结语

“真正的创新,往往始于对未来的模仿。”

鸿蒙所描绘的“万物互联”愿景,正在激励无数开发者用现有技术去逼近那个理想世界。而 Electron,正是我们手中最趁手的工具之一。

希望这个“伪超级终端”项目,能为你带来启发。


获取源码

GitHub 地址:https://github.com/L218/harmony-electron-super-terminal
欢迎 Star ⭐ | Fork 🔁 | 提交 Issue 💬


📢 关注 @L、218,持续输出鸿蒙 × 前端 × 跨端开发实战系列文章!
💬 评论区互动:你希望下一个功能是“音频流转”还是“剪贴板同步”?留言告诉我!

本文原创,转载请注明出处。

 

 

Logo

更多推荐