Eletron開發和Web開發通信實戰總結

系列篇1: Electron 通信方式總結

背景和前言

Electron底層是基于瀏覽器內核實現的,所以它和瀏覽器一樣有多進程的概念,就是有一個主進程和N多個渲染進程,那么我們在開發過程中如何實現這些渲染進程之間的通信,是我們后續開發過程中必須要解決的問題
接下來我會輸出一系列的文章和解決方案,歡迎各位開發和讀者訂閱和指點

Electron通信方式

先看下Demo默認配置
.main.ts

 mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
    },
  })

preload.ts

import { ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer;

模式 1:渲染器進程到主進程(單向)ipcRenderer / ipcMain

render.ts

// 發送事件
const sendMessageToMain = () => {
   window.ipcRenderer.send('messageToMain', 'hello main');
}

main.ts

// 監聽事件
ipcMain.on('messageToMain', (event, arg) => {
    console.log('messageToMain 主進程收到的消息:', arg, event.sender.id)
});

結論: 這種方式主要用于總渲染進程給主進程發消息,并且不支持消息回調

模式 2:主進程到渲染器進程 ipcRenderer / ipcMain

main.ts

// 發送事件
mainWindow?.webContents.send('mainSendMessageToRanderer', '主進程廣播的消息', arg, event.sender.id)
mainWindow?.webContents 這個是接受消息的主體即渲染在任意window上的渲染進程主題

render.ts

// 監聽事件
window.ipcRenderer.on('mainSendMessageToRanderer', (event, arg) => {
    console.log('從主進程收到的消息:', arg, event);
})
結論: 模式1和模式2只是單項發送數據和監聽事件不支持,監聽回調

模式 3:渲染器進程到主進程(雙向) ipcRenderer / ipcMain

render.ts

// 發送事件
const sendMessageToMainCallback = async () => {
  window.ipcRenderer.invoke('messageToMainCallback', '從渲染進程發送的消息').then(response => {
    console.log('從主進程收到的響應:', response);
  }).catch(err => {
    console.error('從主進程收到的錯誤:', err);
  })
}

main.ts

// 監聽事件并且返回
ipcMain.handle('messageToMainCallback', async (event, arg) => {
  console.log('messageToMainCallback 主進程收到的消息:', arg, event.sender.id)
  const sum = await new Promise((resolve) => {
    let total = 0;
    for (let i = 1; i <= 99; i++) {
      total += i;
    }
    resolve(total);
  });

  return sum;
});
結論: 前三種模式我們看到,消息通信是主進程和渲染進程之間的通信,那如果我們想實現渲染進程和渲染進程通信我們怎么辦啊?

很遺憾,Electron官方并沒有直接的Api支持這種模式,它不允許渲染進程之間相互通信

模式 4:渲染器進程到主進程(雙向)MessageChannel 基于 port

注意: 要使用這種模式下面的屬性必須打開,否則將導致收不到從主進程發送的消息,導致 port1 是空的

webPreferences: {
    nodeIntegration: true,
    contextIsolation: false,
},

render.ts

const sendMessageToMainFromPort = () => {
  const { port1, port2 } = new MessageChannel();
  window.ipcRenderer.postMessage('port', null, [port1]);
  if (port2) {
    port2.onmessage = (event: { data: any }) => {
      console.log('從主進程收到的消息:', event.data);
    };
    port2.postMessage({ message: 'hello main' });
  }
}

main.ts

// 監聽port消息
ipcMain.on('port', (event) => {
  const port = event.ports[0]
  console.log('主進程收到的port:', port)
  port.on('message', (event) => {
    const data = event.data
    console.log('主進程發送的消息:', data)
    port.postMessage(data)
  })
  port.start()
})

不打開也可以但是需要做中轉

preload.ts

import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer))

contextBridge.exposeInMainWorld('ipcRendererCustom', {
  createMessageChannelPort: () => {
    const { port1, port2 } = new MessageChannel()
    ipcRenderer.postMessage('port', '', [port1]);
    return {
      postMessage: (message: object) => port2.postMessage(message),
      onmessage: (listener: ((this: MessagePort, ev: MessageEvent<any>) => any) | null) => port2.onmessage = listener,
    };
  },
});

reder.ts

// 發送消息
const sendMessageToMainFromPort = () => {
  const port = window.ipcRendererCustom.createMessageChannelPort();
  port.onmessage((event) => {
    console.log('從主進程收到的消息:', event.data);
  });
  port.postMessage({ message: 'hello main' });
}

main.ts

// 監聽port消息
ipcMain.on('port', (event) => {
  const port = event.ports[0]
  port.on('message', (event) => {
    const data = event.data
    console.log('主進程發送的消息:', data)
    port.postMessage(data)
  })
  port.start()
})

模式 5:渲染器進程到主進程(雙向)MessageChannel 基于 port

render.ts 讓渲染進程觸發事件

// 發送消息, 讓渲染進程發消息
const sendMessageToMainFromMainPort = () => {
  window.ipcRenderer.send('request-main-channel')
  window.ipcRenderer.once('provide-main-channel', (event) => {
    const [port] = event.ports
    console.log('port:', port)
    port.onmessage = (event) => {
      console.log('received result:', event.data)
    }
    port.postMessage({ message: 'hello main' })
  })
}

main.ts

// 在主進程中, 監聽消息并且把端口發送給渲染進程
ipcMain.on('request-main-channel', (event) => {
  const { port1, port2 } = new MessageChannelMain();
  port2.on('message', (event) => {
    console.log('主進程收到的消息:', event.data);
    port2.postMessage('這是測試數據');
  });
  port2.start();
  event.sender.postMessage('provide-main-channel', null, [port1]);
});
結論: 基于端口的消息專遞我們看到是實現點對點的數據傳遞,他還是需要借助主進程去完成消息轉發

模式 6:渲染器進程到主進程(雙向)worker渲染進程基于 port

其實如果我們不想讓主進程承擔太多的邏輯, 那可以新啟動一個后臺渲染進程,把事件都掛在在這個后臺渲染進程中

main.ts
在創建主window的地方,同時創建后臺渲染進程

async function createWindow() {

  workerWindow = new BrowserWindow({
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'workerProload.js'),
      nodeIntegration: true,
      contextIsolation: false,
    }
  })
  const workerPath = path.join(__dirname, `../src/renderer/worker.html`)
  await workerWindow.loadFile(workerPath);
  // 創建主窗口
}

workerProload.ts

import { ipcRenderer } from 'electron'

ipcRenderer.on('new-client', (event) => {
  const [port] = event.ports
  port.onmessage = (event) => {
    console.log('worker: 接受數據: ', event.data)
    port.postMessage('從worker返回的數據給其他渲染進程')
  }
  port.start()
})

render.ts

const sendMessageToWorkerFromMainPort = () => {
  window.ipcRenderer.send('request-worker-channel')
  window.ipcRenderer.once('provide-worker-channel', (event) => {
    const [port] = event.ports
    console.log('port:', port)
    port.onmessage = (event) => {
      console.log('received result:', event.data)
    }
    port.postMessage('hello worker')
  })
}

main.ts

// 給我另一個渲染進程發
ipcMain.on('request-worker-channel', (event) => {
  console.log('workerWindow: event.sender: ', event)
  const { port1, port2 } = new MessageChannelMain()
  workerWindow?.webContents.postMessage('new-client', null, [port2])
  event.sender.postMessage('provide-worker-channel', null, [port1])
})


小結

基于上面的通信模式如果僅僅是在項目比較簡單的項目中實現通信已經足夠了, 但是如果你的項目很復雜,你覺得你會把越來越多的業務通信放到主進程嗎,顯然不合理,我們需要自己開發一個優秀的框架,能夠實現渲染進程和渲染進程之間的通信而不再自己單獨維護一套邏輯

關于自己實現通信框架,后續文章再說, 我們還有一個問題需要處理: Electron開發中不可能沒有Web界面嵌入, 那我們如何實現Web界面和Electron通信將是我們需要解決的第二個問題,下一篇文章 我將詳細介紹

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容