export class Client extends IPCClient implements IDisposable { private protocol: Protocol private static createProtocol(): Protocol { const onMessage = Event.fromNodeEventEmitter<ELBuffer>(ipcRenderer, 'vscode:message', (_, message) => ELBuffer.wrap(message)) ipcRenderer.send('vscode:hello') return new Protocol(ipcRenderer, onMessage) } // 堆代码 duidaima.com constructor(id: string) { const protocol = Client.createProtocol() super(protocol, id) this.protocol = protocol } override dispose(): void { this.protocol.disconnect() super.dispose() } }解释一下,Client先监听了ipcRenderer上的vscode:message事件,然后发送了vscode:hello通知主进程Client连接,然后创建了Protocol类传给IPCClient基类
class IPCClient<TContext = string> implements IDisposable { private channelClient: ChannelClient private channelServer: ChannelServer constructor(protocol: IMessagePassingProtocol, ctx: TContext) { const writer = new BufferWriter() serialize(writer, ctx) protocol.send(writer.buffer) this.channelClient = new ChannelClient(protocol) this.channelServer = new ChannelServer(protocol, ctx) } getChannel<T extends IChannel>(channelName: string): T { return this.channelClient.getChannel(channelName) as T } registerChannel(channelName: string, channel: IServerChannel<string>): void { this.channelServer.registerChannel(channelName, channel) } dispose(): void { this.channelClient.dispose() this.channelServer.dispose() } }可以看到IPCClient的作用就是管理Channel.
const writer = new BufferWriter() serialize(writer, ctx) // ctx的实际值为windowId rotocol.send(writer.buffer)这里其实是向主进程发送了当前窗口的id作为标识
class Server extends IPCServer { private static readonly Clients = new Map<number, IDisposable>() private static getOnDidClientConnect(): Event<ClientConnectionEvent> { const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'vscode:hello', ({ sender }) => sender) return Event.map(onHello, (webContents) => { const id = webContents.id const client = Server.Clients.get(id) client?.dispose() const onDidClientReconnect = new Emitter<void>() Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire())) const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<ELBuffer> const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event) const protocol = new ElectronProtocol(webContents, onMessage) return { protocol, onDidClientDisconnect } }) } constructor() { super(Server.getOnDidClientConnect()) } }
这段代码比较简单,就是处理了一下重新连接的问题。
class IPCServer<TContext extends string = string> { private channels = new Map<string, IServerChannel<TContext>>() private _connections = new Set<Connection<TContext>>() private readonly _onDidAddConnection = new Emitter<Connection<TContext>>() readonly onDidAddConnection: Event<Connection<TContext>> = this._onDidAddConnection.event private readonly _onDidRemoveConnection = new Emitter<Connection<TContext>>() readonly onDidRemoveConnection: Event<Connection<TContext>> = this._onDidRemoveConnection.event private readonly disposables = new DisposableStore() get connections(): Connection<TContext>[] { const result: Connection<TContext>[] = [] this._connections.forEach(ctx => result.push(ctx)) return result } constructor(onDidClientConnect: Event<ClientConnectionEvent>) { this.disposables.add(onDidClientConnect(({ protocol, onDidClientDisconnect }) => { const onFirstMessage = Event.once(protocol.onMessage) this.disposables.add(onFirstMessage((msg) => { const reader = new BufferReader(msg) const ctx = deserialize(reader) as TContext const channelServer = new ChannelServer<TContext>(protocol, ctx) const channelClient = new ChannelClient(protocol) this.channels.forEach((channel, name) => channelServer.registerChannel(name, channel)) const connection: Connection<TContext> = { channelServer, channelClient, ctx } this._connections.add(connection) this._onDidAddConnection.fire(connection) this.disposables.add(onDidClientDisconnect(() => { channelServer.dispose() channelClient.dispose() this._connections.delete(connection) this._onDidRemoveConnection.fire(connection) })) })) })) } getChannel<T extends IChannel>(channelName: string, routerOrClientFilter: IClientRouter<TContext> | ((client: Client<TContext>) => boolean)): T { } private getMulticastEvent<T extends IChannel>(channelName: string, clientFilter: (client: Client<TContext>) => boolean, eventName: string, arg: any): Event<T> { } registerChannel(channelName: string, channel: IServerChannel<TContext>): void { } dispose(): void { }
可以看到Server上管理了所有的Channel和Connection。
// services/fileSystem.ts class IFileSystem { stat:(source:string)=>Promise<Stat> } // main.ts const server = new Server() server.registerChannel( "fileSystem", ProxyChannel.fromService({ stat(source:string){ return fs.stat(source) } })) // 这里的ProxyChannel.fromService后面再解释 // renderer.ts const client = new Client() const fileSystemChannel = client.getChannel("fileSystem") const stat = awite fileSystemChannel.call("stat") // 或者 const client = new Client() const fileSystemChannel = client.getChannel("fileSystem") const fileSystemService = ProxyChannel.toService<IFileSystemChannel>(fileSystemChannel)// 后面解释 const stat = awite fileSystemChannel.call("stat")要搞清楚它们如何调用,首先要明白Channel的构成以及Channel如何被创建
interface IChannel { call: <T>(command: string, arg?: any, cancellationToken?: CancellationToken) => Promise<T> listen: <T>(event: string, arg?: any) => Event<T> } // Channel的创建在ChannelClient类中 getChannel<T extends IChannel>(channelName: string): T { const that = this return { call(command: string, arg?: any, cancellationToken?: CancellationToken) { if (that.isDisposed) { return Promise.reject(new CancellationError()) } return that.requestPromise(channelName, command, arg, cancellationToken) }, listen(event: string, arg: any) { if (that.isDisposed) { return Event.None } return that.requestEvent(channelName, event, arg) }, } as T }这里的that.requestPromise其实就是调用ipcRender.invoke('vscode:message',.....),会把channelName,command和其他参数一起传过去
private onPromise(request: IRawPromiseRequest): void { const channel = this.channels.get(request.channelName) if (!channel) { return } let promise: Promise<any> try { promise = channel.call(this.ctx, request.name, request.arg) } catch (e) { promise = Promise.reject(e) } const id = request.id promise.then((data) => { this.sendResponse({ id, data, type: ResponseType.PromiseSuccess }) }, (err) => { this.sendResponse({ id, data: err, type: ResponseType.PromiseErrorObj }) }) }Server在接受request之后找到对应Channel的Command进行调用,然后返回封装后的执行结果,最后我们看一下ProxyChannel.toService和ProxyChannel.fromService的代码
export function fromService<TContext>(service: unknown, disposables: DisposableStore, options?: ICreateServiceChannelOptions): IServerChannel<TContext> { const handler = service as { [key: string]: unknown } const disableMarshalling = options && options.disableMarshalling const mapEventNameToEvent = new Map<string, Event<unknown>>() for (const key in handler) { if (propertyIsEvent(key)) { mapEventNameToEvent.set(key, EventType.buffer(handler[key] as Event<unknown>, true, undefined, disposables)) } } return new class implements IServerChannel { listen<T>(_: unknown, event: string, arg: any): Event<T> { const eventImpl = mapEventNameToEvent.get(event) if (eventImpl) { return eventImpl as Event<T> } const target = handler[event] if (typeof target === 'function') { if (propertyIsDynamicEvent(event)) { return target.call(handler, arg) } if (propertyIsEvent(event)) { mapEventNameToEvent.set(event, EventType.buffer(handler[event] as Event<unknown>, true, undefined, disposables)) return mapEventNameToEvent.get(event) as Event<T> } } throw new Error(`Event not found: ${event}`) } call(_: unknown, command: string, args?: any[]): Promise<any> { const target = handler[command] if (typeof target === 'function') { // Revive unless marshalling disabled if (!disableMarshalling && Array.isArray(args)) { for (let i = 0; i < args.length; i++) { args[i] = revive(args[i]) } } let res = target.apply(handler, args) if (!(res instanceof Promise)) { res = Promise.resolve(res) } return res } throw new Error(`Method not found: ${command}`) } }() } export function toService<T extends object>(channel: IChannel, options?: ICreateProxyServiceOptions): T { return new Proxy({}, { get(_target: T, propKey: PropertyKey) { if (typeof propKey === 'string') { if (options?.properties?.has(propKey)) { return options.properties.get(propKey) } return async function (...args: any[]) { const result = await channel.call(propKey, args) return result } } throw new Error(`Property not found: ${String(propKey)}`) }, }) as T }
可以看到fromService是将类转换为Channel,而toService是将Channel转换为有类型提示的Service。