본문으로 바로가기

TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.

 

Electron + Vue.js 애플리케이션 만들기

1. Electron 프로젝트 생성하기(Create Electron Application Project)

2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)

3. TCP Router 기능 구현(Implements TCP Router Communication)

4. 화면 UI 구성(Main Window UI)

5. 환경설정 구현하기(Preferences Window)

6. 메뉴 사용하기(Application Menu & Context Menu)

7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)

8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)

9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)

10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)

 

 

이번 포스트에서는 간단하게 Electron 애플리케이션의 구조를 알아보고 지난 시간에 만든 예제에 간단하게 이벤트를 처리하는 코드를 작성해 보겠습니다.

Electron 애플리케이션 개요

 

Electron 애플리케이션 구조의 이해

메인과 렌더러 프로세서

Electron 애플리케이션 가이드 문서에 구조에 대해 다음과 같이 설명하고 있습니다.

메인과 렌더러 프로세서 
Electron에서 package.json의 main 스크립트를 실행하는 프로세스를 메인 프로세스라고 부릅니다. 메인 프로세스에서 실행되는 스크립트는 웹 페이지들을 GUI로 표시합니다. Electron 앱은 항상 하나의 메인 프로세스를 가지며, 둘 이상이 되는 경우는 없습니다.

Electron은 웹페이지를 보여주기 위해 Chromium을 사용하고, 그렇기에 Chromium의 멀티 프로세스 아키텍쳐 또한 사용됩니다. Electron 안에서 보이는 각각의 웹페이지는 자신의 프로세스 안에서 동작하는데, 이 프로세스를 렌더러 (renderer) 프로세스라고 부릅니다.

일반적인 브라우저에서 웹 페이지는 대개 샌드박스 환경에서 실행되고 네이티브 리소스에는 액세스 할 수 없습니다. 그러나 Electron을 사용하는 유저는 웹페이지가 Node.js API들을 이용할 수 있기 때문에, 보다 낮은 수준에서 운영체제와 상호작용하는 것이 허용되어 있습니다.

 

메인 프로세스와 렌더러 프로세스의 차이점

메인 프로세스와 렌더러 프로세스의 차이점 
메인 프로세스는 BrowserWindow 인스턴스를 생성하여 웹페이지를 만듭니다. 각각의 BrowserWindow 인스턴스는 자체 렌더러 프로세스에서 웹 페이지를 실행합니다. BrowserWindow 인스턴스가 소멸되면, 해당 렌더러 프로세스도 종료됩니다. 

메인 프로세스는 모든 웹 페이지와 각 페이지들이 소유한 렌더러 프로세스들을 관리합니다. 각각의 렌더러 프로세스는 서로 독립적으로 동작하고 그들이 실행된 웹페이지 내에서만 관여를 합니다. 

웹 페이지에서 네이티브 GUI 관련 API 호출은 허용되지 않습니다. 왜냐하면 이것은 매우 위험한 일이고, 리소스 릭을 발생시키기 쉽기 때문입니다. 웹페이지에서 GUI작업을 수행하려면, 웹 페이지의 렌더러 프로세스가 메인 프로세스에게 이러한 작업을 수행하도록 요청해야 합니다. 

Aside : 프로세스 간 통신 
Electron에는 메세지를 전송하기 위한 ipcRenderer와 ipcMain 모듈, RPC 방식 통신을 위한 remote 모듈과 같이 메인 프로세스와 렌더러 프로세스가 통신하는 방법이 여러 가지 있습니다. FAQ에 웹 페이지 간에 데이터를 어떻게 공유하나요? how to share data between web pages? 를 참고하세요. 

출처 : Electron 애플리케이션 아키텍처(https://www.electronjs.org/docs/tutorial/application-architecture)

 

위 내용을 정리하면

메인 프로세서

- 1개의 프로세스만 존재

- 보다 낮은 수준의 운영체제와의 상호작용 허용

- ipcMain 모듈

 

렌더러 프로세스

- Chromium을 사용하여 웹페이지를 출력

- 네이티브 리소스에 접근 불가

- ipcRenderer 모듈

 

ipc(Inter-process communication) 모듈의 이해

ipc모듈은 ipcMain 모듈과 ipcRenderer 모듈로 구성됩니다. ipcMain은 Main Process에서 Renderer 프로세스와의 비동기 통신을 처리하고 ipcRederer 모듈은 Renderer 프로세스에서 Main Process 간 비동기 통신을 담당합니다. 

 

ipcMain 예시 코드

// 메인 프로세스에서
const { ipcMain } = require('electron');

// 렌더러 프로세스에서의 요청을 대기
ipcMain.on('async-request', (event, arg) => {
	console.log(arg);
    
    // 이벤트를 전송한 해당 sender(렌더러 프로세스)에 응답을 전송
    event.sender.send('async-response', 'response message');
});

 

ipcRenderer 예시 코드

// Renderer 프로세스(웹 페이지)에서
const { ipcRenderer } = require('electron');

// 메인 프로세스에 메시지 전송
ipcRenderer.send('async-request', 'request message');

// 메인 프로세스로 부터의 응답 대기
ipcRenderer.on('async-response', (event, arg) => {
	console.log(arg);
});

 

그리고, IpcMain 모듈과 ipcRederer모듈을 추상화한 remote모듈이 존재합니다.

메인 프로세스와 렌더러 프로세스(웹 페이지) 사이의 inter-process (IPC) 통신을 간단하게 추상화 한 모듈입니다.

Electron에서는 GUI와 연관된 모듈들(dialog, menu등) 을 메인 프로세스에서만 사용할 수 있으며, 렌더러 프로세스에서는 사용할 수 없습니다. 렌더러 프로세스에서 이러한 모듈들을 사용하려면 ipc 모듈을 통해 메인 프로세스와 inter-process 통신을 해야 합니다. 또한, remote 모듈을 사용하면 inter-process 통신을 하지 않고도 간단한 API를 통해 직접 메인 프로세스의 모듈과 메서드를 사용할 수 있습니다.

Rederer 프로세스에서 다음과 같이 사용이 가능합니다.

// 렌더러 프로세스에서
const { BrowserWindow } = require('electron').remote

// 메인 프로세스의 모듈(BrowserWindow)을 사용
let win = new BrowserWindow({ width: 800, height: 600 })
win.loadURL('https://github.com')

 

 

ipc 사용하기

이번 예제에서는 화면에서 버튼을 클릭하면 서버에 이벤트를 전달하고 서버의 메시지를 받아서 출력하는 예제와 서버에서 화면으로 데이터를 전송하는 예제를 살펴봅니다.

 

주요 처리함수

  • ipcMain.on, ipcRenderer.on : 채널의 데이터를 수신하고 메시지가 도착하면 listener를 실행시킵니다.
  • ipcMain.once, ipcRenderer.once : 채널의 데이터를 수신하고 메시지가 도착하면 listener를 실행시킵니다. 실행 후 자동으로 listener가 등록 해제됩니다.(1회성 데이터 송수신)
  • ipcMain.removeListener, ipcRenderer.removeListener : 채널의 listener를 등록 해제합니다.
  • ipcMain.removeAllListeners, ipcRenderer.removeAllListeners : 지정한 채널의 모든 listener를 등록 해제합니다.

 

화면 이벤트를 서버에 전달하고 응답받기

메시지 수신 등록

/src/main/index.js 에서 이벤트를 등록합니다.

function init() {
  ......(중간생략)......
  
  // Renderer 프로세서의 메시지를 수신하고 응답 데이터를 전송합니다.
  ipcMain.on("request-message", (event, args) => {
    console.log(args);
    event.sender.send("response-message", "This is a Server Message.");
  });

  ......(중간생략)......
}

화면 수정하기

/src/renderer/components/LandingPage.vue에 이벤트를 전달합니다.

<template>
      ......(중간생략)......
        <system-information></system-information>

        <div class="doc" style="margin-top:16px;">
          <div class="title alt">IPC(Inter-Process Communication)</div>
          <p>
            <div>{{ responseMessage }}</div>
          </p>
          <button class="alt" @click="sendMessage">Send message to Main Process</button>
        </div>

      ......(중간생략)......

    </main>
  </div>
</template>

<script>
import { ipcRenderer } from 'electron'
import SystemInformation from './LandingPage/SystemInformation'

  export default {
    name: 'landing-page',
    components: { SystemInformation },
    data() {
      return {
        responseMessage : "",
      }
    },
    methods: {
      ......(중간생략)......
      sendMessage() {
        ipcRenderer.once("response-message", (event, args) => {
          console.log(args);
          this.responseMessage = args;
        });
        ipcRenderer.send("request-message", "This is a Renderer Message.");
      }
    }
  }
</script>
......(이하생략)......

화면 표시 화면

오른쪽 상단의 검은색 부분은 렌더러 프로세스에서 보낸 메시지가 메인 프로세스에서 출력된 로그입니다. 아래의 붉은색 박스는 "Send message to Main Process" 버튼을 누르면 메인 프로세스에서 전달된 값이 출력된 예시입니다.

출력 메시지 예시

 

서버의 데이터를 화면에 전달하기

아래의 예제는 화면에 서버에서 변경되는 값을 화면에 자동으로 출력되게 하는 예제입니다. Main 프로세스에서는 화면이 지정되지 않으면 값을 전달해야 할 위치를 알 수 없기 때문에 화면 초기화(created 이벤트) 시에 먼저 Main 프로세스에게 Renderer 프로세스의 정보를 먼저 전달해야 합니다.

Renderer 프로세스의 정보가 등록된 후에는 Main프로세스에서 send를 통하여 값을 전달할 수 있습니다.

이벤트 등록

/src/main/index.js 에서 이벤트를 등록합니다.

......(상단생략)......
let mainWindow = null;
function init() {

......(중간생략)......

  let pushIndex = 0;
  let pushRenderer = null;
  let timer = null;
  ipcMain.on("push-ipc-init", (event, args) => {
    // console.log(args);
    pushRenderer = event.sender;
    
    if(!timer) {
      timer = setInterval(() => {
        pushIndex++;
        if(pushRenderer) {
          pushRenderer.send("push-ipc-message", "Push message at " + pushIndex);
        }
      }, 1000);
    }
    // event.sender.send("push-ipc-init-res", "Initialize successfully..");
  });
  ipcMain.once("push-ipc-end", (event, args) => {
    // console.log(args);
    if(timer) clearInterval(timer);
    // event.sender.send("push-ipc-end-res", "Terminate successfully..");
  });
}
......(이하생략)......

화면 수정

/src/renderer/components/LandingPage.vue에 이벤트를 초기화합니다.

<template>
  <div id="wrapper">
        ......(중간생략)......
        <system-information></system-information>

        <div class="doc" style="margin-top:16px;">
          <div class="title alt">IPC(Inter-Process Communication)</div>
          <p>
            <div>{{ pushMessage }} </div>
          </p>
        </div>
      </div>
      
        ......(중간생략)......

    </main>
  </div>
</template>

<script>
import { ipcRenderer } from 'electron'
import SystemInformation from './LandingPage/SystemInformation'

  export default {
    name: 'landing-page',
    components: { SystemInformation },
    data() {
      return {
        pushMessage: "",
      }
    },
    created() {
        ipcRenderer.on("push-ipc-message", (event, args) => {
          // console.log(args);
          this.pushMessage = args;
        });
        ipcRenderer.send("push-ipc-init", "");
    },
    destroyed() {
        ipcRenderer.send("push-ipc-end", "");
    },
  }
</script>
......(이하생략)......

 

화면 표시 화면

화면 출력 예시

 

글을 마치며

ipcMain 모듈과 ipcRenderer 모듈의 on, once, send 함수를 이해한다면 프로세스가 통신에는 크게 어려움이 없습니다. Renderer 프로세스와 다른 Renderer 프로세스 간 통신하는 방법은 없습니다. 다만, 각각의 Renderer 프로세스가 데이터를 공유하는 방법은 웹브라우저의 Storage API, localStorage, sessionStorage, IndexedDB를 통해 처리할 수 있습니다. 자세한 사항은 Electron FAQ를 참고하십시오.

그리고, ipcMain 모듈과 ipcRenderer 모듈에는 값을 전달할 수 있는 몇몇 함수가 더 있습니다. 자세한 사항은 아래의 가이드 문서를 참고하십시오.

 

참고 자료

 

소스코드

본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.

 

 

728x90