Electron: IPC vs. WebSocket
For local Node.js applications, which of these two data exchange modes to choose?
We'll compare their features and performance ...
Note that if WebSocket is available for all Node-based systems, even a simple server script such as the one described in the JavaScript section of this site, IPC only works with Electron.
On the server side, the WebSocket commands are based on the ws module. Other choices would be possible, with different syntaxes. On the interface side, we use the standard WebSocket object.
WebSocket | IPC | |
---|---|---|
SERVER side |
||
Import | const WebSocketServer = require("ws").Server; | const {ipcMain}=require('electron').ipcMain |
Creating an object | w = new WebSocketServer(port) | - |
Waiting the communication to open | w.on('connection', (w) =>{}) | - |
Sending data to the interface | w.send(data) | event.sender.send('channel', data) |
Synchronous data transmission | - | event.returnValue = data |
Receiving data from the interface | w.on('message', (m) =>{}) | ipcMain.on('channel', (e, o) => {}) |
Closing the communication channel | w.on('close', () =>{}) | - |
INTERFACE side |
||
Import | - | const ipcRenderer = require('electron').ipcRenderer |
Creating an object | const w = new WebSocket(port); | - |
Sending data to the server | w.send(data) | ipcRenderer.send('channel', data) |
Synchronous data transmission | - | ipcRenderer.sendSync('channel', data) |
Receiving data from the server | w.onmessage = function(event){}) | ipcRenderer.on('channel', (event)=>{}) |
Closing the communication channel | w.close() | - |
We can see the differences between the two protocols:
- For IPC, there is only one ipc object on the server and one for each window in the interface. You can communicate with a window by specifying the name of the channel. For WebSocket, it is the port number that distinguishes the communication points.
- IPC has a synchronous mode and not WebSocket.
- But the second is bidirectional: the server can start the data exchange.
In terms of features, IPC prevails with the ability to exchange data synchronously. Not using the ports of the system also avoids any risk of collision when the port is already used by another application.
IPC demo
We will build a basic application with a window and a backend that communicates with the interface in synchronous and asynchronous mode.
The interface sends the "hello server" message to the backend that replies with "hello interface".
In the asynchronous mode, a listener in the interface waits for the message to be received on the "message" channel.
In synchronous mode, the server response is the return value of the function that sends the message to the server.
Server side code
const path = require("path")
const { app, BrowserWindow, ipcMain } = require('electron')
const print = console.log
let win
function createWindow() {
win = new BrowserWindow({
width: 960, height: 600,
title:"IPC vs. WebSocket",
webPreferences : { nodeIntegration:true }
})
win.setMenu(null)
const fpath = path.join(__dirname, 'ipc.html')
win.loadURL(fpath)
win.on('closed', () => { win = null })
}
// IPC
ipcMain.on('message', (event, data) => {
print("Received: " + data)
event.sender.send('message', 'Hello interface!')
})
ipcMain.on('message-sync', (event, data) => {
print("Received: " + data)
event.returnValue = 'Hello interface (synchronous)!'
})
// App
app.on('ready', createWindow)
app.on('window-all-closed', () => {
app.quit()
process.exit(1)
})
app.on('quit', function () {
print("Done.")
})
In the demo, the event.sender.send command responds on the same "message" channel that is used in reception, but it is also possible to send data to multiple different channels (unlike in synchronous mode).
Browser side code
<!DOCTYPE html>
<html>
<body>
<h1>IPC Demo</h1>
<fieldset id="storage"></fieldset>
<fieldset id="storageSync"></fieldset>
<script>
const {ipcRenderer} = require('electron')
ipcRenderer.on('message', (event, data) => {
document.getElementById("storage").innerHTML = data
})
ipcRenderer.send('message', 'Hello server!')
var answer = ipcRenderer.sendSync('message-sync', 'Hello server sync!')
document.getElementById("storageSync").innerHTML = answer
</script>
</body>
</html>
To run the program, type "electron ipc.js" in the scripts directory.
WebSocket demo
As before, the interface sends the message "Hello server!" To the backend which in return sends "Hello interface!" To the browser.
Server side code
The backend imports the ws module, which is included in the archive.
const path = require("path")
const { app, BrowserWindow } = require('electron')
const WebSocket = require("ws")
const wss = new WebSocket.Server( { port: 1040 } )
let win
function main() {
win = new BrowserWindow({
width: 960, height: 600,
title:"WebSocket Demo"
})
win.setMenu(null)
const fpath = path.join(__dirname, 'websocket.html')
win.loadURL(fpath)
win.on('closed', () => { win = null })
wss.on('connection', function (w) {
w.on( 'message' , function (data) {
console.log(data)
})
w.on('close', function() {
console.log("Closed")
})
w.send("Hello interface!")
})
}
app.on('ready', main)
app.on('window-all-closed', () => {
app.quit()
process.exit(1)
})
app.on('quit', function () {
console.log("Done.")
})
Browser side code
The interface uses the browser's standard WebSocket object.
<!DOCTYPE html>
<html>
<body>
<h1>WebSocket Demo</h1>
<fieldset id="storage"></fieldset>
<script>
const socket = new WebSocket("ws://localhost:1040");
socket.onmessage = function(event) {
var data = event.data
document.getElementById("storage").innerHTML = data
}
socket.onopen = function() {
socket.send('Hello server!')
}
</script>
</body>
</html>
The code is a bit simpler because we are only testing an asynchronous mode and this time we do not need to include the Electron module.
Type "electron websocket.js" to start the script.
Comparative speeds
My initial intention was to continue the comparison with new scripts exchanging a series of data to compare the speed of the two protocols. But when we execute the two scripts above, we see that it is useless. While the data is displayed instantly with IPC, there is a noticeable delay with WebSocket.
And it's normal, IPC is internal to Electron while WebSocket goes through the computer's network system, with all its necessary constraints and controls.
Therefore, as soon as Electron has been chosen, IPC should also be the preferred mode of communication unless there is a need for a bidirectional system, with server notifications for example.
Download the scripts