Electron

Electron, Main과 Renderer 프로세스 그리고 IPC

iKay 2018. 10. 21. 22:46
반응형

0. 들어가며

Electron은 main, renderer 두 개의 프로세스에 의해 동작한다. 이 두 프로세스에 대해 알아보고, 프로세스간 통신 즉, IPC(Inter Process Communication)를 다뤄본다. 

 

 

1. main 프로세스와 renderer 프로세스

Electron으로 애플리케이션을 실행시킬 때, main 프로세스가 생성되는데, 이것은 OS의 native GUI와 상호작용하여 애플리케이션의 GUI를 만든다. 즉, main 프로세스는 native GUI 에 접근 가능하다. 또한 NodeJS 모듈도 접근가능하기 때문에 앱 개발이 용이하다.   

 

그러나 단순히 main 프로세스를 실행시킨다고 애플리케이션 창이 만들어 지는 것은 아니다. 애플리케이션 창은 main 프로세스가 실행되는 main.js 파일 속 BrowserWindow 라는 모듈에 의해 만들어진다. 즉, BrowserWindow 모듈에 의해 생성된 객체 win 이 renderer 프로세스를 생성하는 것이다. 이 renderer 프로세스는 HTML로 코딩돼있고, CSS, JavaScript, 이미지 등을 불러옴으로서 창이 보여지게 된다. 

 

요약하자면, main 프로세스는 실행시 생성되고 모든 웹 페이지(애플리케이션)를 관리한다. renderer 프로세스는 main 프로세스의 BroserWindow 모듈에 의해 생성되고, 각 win 객체는 각각의 애플리케이션 창을 독립적으로 render 한다.

 

 

2. IPC(Inter Process Communication)

Electron에는 ipcMain, ipcRender 라는 두 가지 IPC 모듈이 있다. 

 

ipcMain 모듈은 main 프로세스에서 사용되는 된다. renderer 프로세스로부터 메시지를 받고, 응답할 수 있다. ipcRenderer 모듈도 마찬가지이다. renderer 에서 사용되며, main 프로세스로 메시지를 전달할 수 있고, 응답을 받을 수 있다. 참고로 이렇게 IPC로 메시지를 주고 받을 때 async(비동기), sync(동기) 두 가지 방식이 가능하다.  

 

프로젝트 디렉토리를 하나 생성하고 main.js를 생성 후, 아래 소스코드를 입력하자. ipcMain 이벤트 핸들러는 아래에서 보면 알겠지만 첫 번째 인자는 메시지를 주고받기 위한 channel 이다. 두 번째 인자는 메시지가 renderer로부터 왔을 때 처리하는 callback function이다. callback function에서 async, sync일때 응답하는 방식이 다르다. async 일 때는 callback function에서 event.sender.send({channel}, {message_buffer}) 로 응답하고, sync 일 때는 event.returnValue = {message_buffer} 로 응답한다.  

 

/* main.js */

const {app, BrowserWindow} = require('electron');
const {ipcMain} = require('electron');

let win;

function createWindow() {
win = new BrowserWindow({width: 800, height: 600});
/* load local .html file*/
win.loadURL(`file://${__dirname}/index.html`);
}

/* Event handler for asynchronous incoming messages */
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg);

/* Event emitter for sending asynchronous messages */
event.sender.send('asynchronous-reply', 'async pong');
});

/* Event handler for synchronous incoming messages */
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg);

/* Synchronous event emmision in 10 sec */
setTimeout(()=> {
event.returnValue = 'sync pong';
}, 10*1000);

});

app.on('ready', createWindow);

 

 
 
index.html을 생성하고 아래 소스코드를 입력하자. 보다시피 async로 메시지를 받을 때는 이벤트 핸들러로, sync는 전송 후 리턴되는 방식으로 처리된다.
sync를 사용하게 되면, 요청 후 결과처리 응답이 올 때 까지 그 동안 다른 루틴을 처리할 수 없게되니 사용시 주의해야 한다.
 
<!-- index.html -->

<body>
<script>
const {ipcRenderer} = require('electron')

/* Synchronous message emmiter and handler */
console.log(ipcRenderer.sendSync('synchronous-message', 'sync ping'))

/* Blocked for 10 sec!!! as waiting for synchronous-message. */

/* Async message handler */
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg)
});

/* Async message sender */
ipcRenderer.send('asynchronous-message', 'async ping')
</script>
</body>
 
 
 
이제 cmd에서 main.js를 electron으로 실행해보자. main 쪽에서는 sync ping을 받은 후 10초 뒤에 async ping을 받음을 알 수 있다. renderer에서는 10초
뒤에 sync pong, async pong을 모두 받는다. 그 이유는 renderer에서 sync 방식의 IPC를 사용했기 때문이다. 더 자세히 설명하자면, 아까 설명한대로
renderer에서 sendSync()는 결과를 받을 때 까지 다른 루틴을 실행할 수 없기 때문에 이런 비효적인 상황이 야기된 것이다.
 
사실 async, sync 이야기는 어렵고, IPC 주제에서 벗어나기 때문에 혼동을 줄 수도 있다. 하지만 'single thread event loop' NodeJS를 사용한다면 async,
sync를 정확히 아는 것이 중요하기 때문에 이 점을 강조하고 싶어 살짝 다루어 봤다.
 

3. 결론

Electron은 main 프로세스, renderer 프로세스 두 프로세스에 의해 애플리케이션이 동작한다. 처음에 main 프로세스에 의해 애플리케이션이 실행된다. 이 후 창을 보이기 위해서는 BrowserWindow 모듈이 있어야 하고 이 모듈에 의해 renderer 프로세스가 생성된다. renderer는 HTML로 css. JavaScript, 이미지 등을 render 하는 것이다. 

 

main, renderer 이 두 프로세스간 통신을 하기 위해서는 IPC 모듈을 사용한다. 통신방식에는 동기, 비동기가 있는데 NodeJS가 싱글 쓰레드이기 때문에 동기 방식으로 통신할 때는 주의한다. 

 

 

4. 소스코드

 

https://github.com/hgs0426/electron/tree/master/ipc

 

 

5. 참고

[1] https://www.tutorialspoint.com/electron/

 

[2] https://electronjs.org/docs/api/ipc-main

반응형