Из сообщества IMWeb, автор: лайнехен,Оригинальная ссылка
в предыдущем постеЭлектронная технологическая связь, вводит два метода взаимодействия процессов в Electron, а именно:
- использовать
ipcMain
а такжеipcRenderer
два модуля - Используйте удаленный модуль
Вместо двух модулей IPC используйтеremote
Модули относительно естественны.remote
Модуль помогает нам защитить внутреннюю связь процесса, так что мы совершенно не знаем о существовании основного процесса при вызове метода основного процесса.
ПредыдущийЭлектронная технологическая связьв, даremote
Реализация состоит в том, чтобы просто сказать, что его нижний уровень все еще взаимодействует через модуль ipc:
С удаленным объектом мы можем общаться без отправки межпроцессных сообщений. Но на самом деле, когда мы вызываем метод или функцию удаленного объекта или создаем новый объект через удаленный конструктор, мы фактически отправляем синхронное межпроцессное сообщение (в официальном документе говорится, что это похоже на RMI в JAVA).
То есть удаленный метод — это просто метод, который не позволяет нам явно писать сообщения между процессами. В приведенном выше примере создания BrowserWindow через удаленный модуль. Объект BrowserWindow, который мы создали в процессе рендеринга, на самом деле не находится в нашем процессе рендеринга, он просто позволяет основному процессу создать объект BrowserWindow и возвращает соответствующий удаленный объект в процесс рендеринга.
Но так ли это?
Эта статья начнется сremote
Реализация модуля анализируется на уровне исходного кода модуля.
«Фальшивая» многопроцессорность?
Давайте рассмотрим пример, чтобы понять прямое использование связи IPC и использованиеremote
Различия между модулями:
через модуль IPC иremote
Реализация модуля получает объект основного процесса в процессе рендеринга, а затем изменяет значение атрибута объекта в основном процессе, чтобы увидеть, изменится ли соответствующим образом значение атрибута, соответствующего объекту в процессе рендеринга.
Логика относительно проста, просто посмотрите на код напрямую.
Использование модуля IPC
Код основного процесса:
const remoteObj = {
name: 'remote',
};
const getRemoteObject = (event) => {
// 一秒后修改 remoteObj.name 的值
// 并通知渲染进程重新打印一遍 remoteObj 对象
setTimeout(() => {
remoteObj.name = 'modified name';
win.webContents.send('modified');
}, 1000);
event.returnValue = remoteObj;
}
Код процесса рендеринга:
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Electron</title>
<style>
body {
margin: 30px;
}
#container {
font-weight: bold;
font-size: 32px;
}
</style>
</head>
<body>
<pre id="container"></pre>
<script src="./index.js"></script>
</body>
</html>
index.js
:
const { remote, ipcRenderer } = window.require('electron');
const container = document.querySelector('#container');
const remoteObj = ipcRenderer.sendSync('getRemoteObject');
container.innerText = `Before modified\n${JSON.stringify(remoteObj, null, ' ')}`;
ipcRenderer.on('modified', () => {
container.innerText = `${container.innerText}\n
After modified\n${JSON.stringify(remoteObj, null, ' ')}`;
});
Вывод интерфейса следующий:
Хм... нет проблем, как и ожидалось. Поскольку передача данных в процессе коммуникации сериализуется и десериализуется, объекты в процессе, полученные процессом рендеринга, не являются одним и тем же объектом, а адреса памяти, на которые указывают разные.
использоватьremote
модуль
Код основного процесса:
const remoteObj = {
name: 'remote',
};
const getRemoteObject = (event) => {
// 一秒后修改 remoteObj.name 的值
// 并通知渲染进程重新打印一遍 remoteObj 对象
setTimeout(() => {
remoteObj.name = 'modified name';
win.webContents.send('modified');
}, 1000);
return remoteObj;
}
// 挂载方法到 app 模块上,供 remote 模块使用
app.getRemoteObject = getRemoteObject;
Код процесса рендеринга:
index.html
Файл тот же, что и выше.
index.js
изменено, чтобы пройтиremote
Модуль получает remoteObj :
...
const remoteObj = remote.app.getRemoteObject();
...
Вывод интерфейса следующий:
Мы обнаружили, что поremote
модуль полученremoteObj
Это ссылка, точно так же, как мы берем объект в процессе рендеринга. На самом деле нет основного процесса и процесса рендеринга? другими словамиremote
Какую черную магию использует модуль, чтобы мы могли ссылаться на объекты в основном процессе в процессе рендерера?
Java's RMI
Официальная документация находится наremote
Во введении к модулю упоминается, что его реализация аналогична RMI в Java.
Так что же такое РМИ?remote
В нем скрыта черная магия?
RMI (Remote Method Invoke)
Вызов удаленного метода — это механизм связи, в котором компьютеры используют удаленные объекты для вызова друг друга для обеспечения взаимной связи. Используя этот механизм, объекты на одном компьютере могут вызывать объекты на другом компьютере для получения удаленных данных.
Если мы используем протокол HTTP для реализации звонков удаленного метода, мы можем реализовать его так:
Хотя базовый RMI не использует протокол http, общая идея та же. а такжеremote
Аналогичным образом, связь процесса неотделима от модуля IPC.
Но связь IPC может быть скрыта от пользователя. Цель RMI та же, чтобы позволить клиентам вызывать методы удаленных объектов, как если бы они были локальными методами, базовая связь не должна быть доступна пользователю.
Принцип реализации RMI
RMI не взаимодействует через протокол http, а используетJRMP (Java Remote Method Protocol)
. Ниже приведен процесс реализации связи между сервером и клиентом через JRMP:
Аналогичен http, но с дополнительным реестром.
Реестр здесь может быть аналогом нашего DNS-сервера.
Сервер должен сообщить DNS-серверу, что доменное имя xxx должно указывать на IP-адрес этого сервера, и клиент может получить доступ к серверу, запросив у DNS-сервера IP-адрес сервера через доменное имя. В RMI сервер регистрируется в реестре,rmi://localhost:8000/hello
Указывает на объект A на сервере, когда клиент проходитrmi://localhost:8000/hello
При поиске объекта на стороне сервера возвращается этот объект A.
Передача данных
Как объект A возврата реестра передается клиенту? Первое, что приходит на ум, это сериализация и десериализация. RMI также реализован таким образом, но есть несколько случаев:
- Простые типы данных (int, boolean, double и т. д.): передаются напрямую без сериализации
- Объект: сериализация объекта для передачи копии всего объекта
- Достигнуто
java.rmi.Remote
объект интерфейса (!!фокус): удаленная ссылка
Еще одним важным моментом в RMI является удаленный объект. RMI реализует этиRemote
Объект интерфейса имеет некоторую инкапсуляцию, которая защищает от нас лежащую в основе связь, поэтому, когда клиент вызывает метод для этих удаленных объектов, это то же самое, что и вызов локального метода.
Грубый процесс RMI
Смущенный? Не беда, посмотрите на реализацию кода:
Простая реализация RMI
(Рекомендуется, чтобы вы выполнили этот пример вместе~ Как вы можете иметь чувство достижения, если вы не реализуете его самостоятельно!!)
Файл интерфейса удаленного объекта для клиента и сервераHelloRMI.java
:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloRMI extends Remote {
String sayHi(String name) throws RemoteException;
}
Реализация сервераHelloRMI
интерфейсHelloImpl.java
:
import java.rmi.RemoteException;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
public class HelloRMIImpl extends UnicastRemoteObject implements HelloRMI {
protected HelloRMIImpl() throws RemoteException {
super();
}
@Override
public String sayHi(String name) throws RemoteException {
try {
System.out.println("Server: Hi " + name + " " + getClientHost());
} catch (ServerNotActiveException e) {
e.printStackTrace();
}
return "Server";
}
}
программа тестирования сервераServer.java
:
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class Server {
public static void main(String[] args) {
try {
// 创建远程服务对象实例
HelloRMI hr = new HelloRMIImpl();
// 在注册表中注册
LocateRegistry.createRegistry(9999);
// 绑定对象到注册表中
Naming.bind("rmi://localhost:9999/hello", hr);
System.out.println("RMI Server bind success");
} catch (RemoteException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (AlreadyBoundException e) {
e.printStackTrace();
}
}
}
клиентская тестовая программаClient.java
:
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class Client {
public static void main(String[] args) {
try {
HelloRMI hr = (HelloRMI) Naming.lookup("rmi://localhost:9999/hello");
System.out.println("Client: Hi " + hr.sayHi("Client"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
беги первымServer.java
, откройте реестр и привяжите удаленный объект к реестру. Затем запущенный клиент может найти и запустить удаленный объект на сервере.
remote
RMI в
Давайте посмотрим на предыдущий пример, используяremote
Что происходит за модулем получения объекта в основном процессе:
если мы предположимremote
Это просто помогает нам защитить операцию IPC, тогда объекты в основном процессе, полученные в процессе рендеринга, не должны иметь ничего общего с объектами в основном процессе и не должны быть затронуты модификацией основного процесса. Такremote
Что еще вы сделали для нас?
На самом деле суть не вremote
Это помогло нам сделать IPC за кулисами, но оно заключается в передаче данных. Как упоминалось в предыдущем RMI, передача данных разделена на простые типы данных и не имеет наследования.Remote
объекты и наследоватьRemote
удаленный объект. наследоватьRemote
Удаленный объект передается по удаленной ссылке вместо простой сериализации и десериализации при передаче данных. существуетremote
модуль, это равносильно тому, чтобы помочь нам разместить всеObject
преобразуются в удаленные объекты.
Учитесь на исходном кодеremote
Вот как выполняется это преобразование:
lib/renderer/api/remote.js
:
...
const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
})
}
const browserModules =
require('../../common/api/module-list').concat(
require('../../browser/api/module-list'))
// And add a helper receiver for each one.
browserModules
.filter((m) => !m.private)
.map((m) => m.name)
.forEach(addBuiltinProperty)
Что делает этот код, должен добавить модули, которые можно использовать только основным процессом дляremote
Свойства модуля находятся в .
...
exports.getBuiltin = (module) => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN'
const meta = ipcRenderer.sendSync(command, module)
return metaToValue(meta)
}
...
getBuiltin
Метод обработки заключается в отправке синхронного межпроцессного сообщения для запроса объекта модуля из основного процесса. наконец вернет значениеmeta
передачаmetaToValue
Вернуться позже. Все секреты в этом методе.
// Convert meta data from browser into real value.
function metaToValue (meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map((member) => metaToValue(member)),
buffer: () => bufferUtils.metaToBuffer(meta.value),
promise: () => resolvePromise({then: metaToValue(meta.then)}),
error: () => metaToPlainObject(meta),
date: () => new Date(meta.value),
exception: () => { throw metaToException(meta) }
}
if (meta.type in types) {
return types[meta.type]()
} else {
let ret
if (remoteObjectCache.has(meta.id)) {
return remoteObjectCache.get(meta.id)
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
let remoteFunction = function (...args) {
let command
if (this && this.constructor === remoteFunction) {
command = 'ELECTRON_BROWSER_CONSTRUCTOR'
} else {
command = 'ELECTRON_BROWSER_FUNCTION_CALL'
}
const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args))
return metaToValue(obj)
}
ret = remoteFunction
} else {
ret = {}
}
setObjectMembers(ret, ret, meta.id, meta.members)
setObjectPrototype(ret, ret, meta.id, meta.proto)
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
v8Util.setRemoteObjectFreer(ret, meta.id)
v8Util.setHiddenValue(ret, 'atomId', meta.id)
remoteObjectCache.set(meta.id, ret)
return ret
}
}
Различные типы обрабатываются по-разному. При обработке функции функция инкапсулируется вне исходной функции для отправки синхронных межпроцессных сообщений, а возвращаемое значение также называетсяmetaToValue
Возврат после конвертации.
Кроме того, даObject
Типовые объекты также должны инкапсулировать свои свойства, такие как функции:
function metaToValue (meta) {
...
setObjectMembers(ret, ret, meta.id, meta.members)
setObjectPrototype(ret, ret, meta.id, meta.proto)
...
}
Переопределите методы get и set для возвращаемых свойств объекта.Для вызова свойств на удаленных объектах он также получается путем отправки синхронных межпроцессных сообщений., поэтому основной процесс изменяет значение, и процесс рендеринга может его воспринимать.
Еще один момент, на который следует обратить внимание, это то, что для того, чтобы повторно не получать удаленные объекты, возвращаемый объектremote
будет кэшироваться, см.metaToValue
Предпоследняя линия:remoteObjectCache.set(meta.id, ret)
читатели думают
На данный момент мы знаем причину магического явления, с которым столкнулись в начале статьи. Вот вопрос к читателю: подумайте, если функция основного процесса асинхронна (функция возвращает объект Promise), то как объект Promise реализует передачу данных? Будет ли это блокировать процесс рендеринга?
Суммировать
Из вышеприведенного анализа мы знаем, чтоremote
Модуль не только помогает нам реализовать связь IPC, но также использует RMI, аналогичный Java, для достижения эффекта передачи по ссылке, и инкапсулирует объект основного процесса, так что когда мы обращаемся к свойствам удаленного объекта, мы также необходимо отправить сообщение процесса синхронизации в основной процесс, чтобы получить фактическое значение объекта в текущем основном процессе.
【Использованная литература】
- Коммуникация Electron Process — веб-интерфейс Tencent Сообщество команды IMWeb | блог | Блог команды
- remote | Electron
- Учебные заметки: Простой пример вызова удаленного метода JAVA RMI — требуются программисты
- Использование RMI в Java от незнания до внезапного осознания - Блог CSDN
- Java RMI - Java - Блог ITeye