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:

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