VSCode进程间通信构建过程详解

  • VSCode一共有三种形态:

    • 客户端形态:windows、linux、macos三种主流操作系统的客户端
    • server形态:用于构建vscode的网页版,另外ssh版本也需要在远端建立起一个server版的vscode
    • cli形态:命令行形态
  • 下图就是官方提供了一个关于SSH如何链接到远端进行远程开发的示意图。

  • 这也就是实现了渲染和工作空间分离的好处。也可以看出来VSCode这个产品设计的精巧之处。

  • 这篇文章讨论的主要是桌面版VSCode的进程间通信构建过程

桌面版VSCode进程架构

  • Main Process(主进程):是VSCode的入口进程,负责窗口管理、进程间通信、自动更新等全局任务。是一个唯一的进程,即即使开启多个窗口都是只有一个VSCode主进程。
  • Shared Process(共享进程):全局只有一个共享进程。为多个渲染进行提供一些共享的服务和数据。
  • Render Process(渲染进程):负责一个窗口的渲染。一个窗口对应一个Render Process。一个Main Process与多个Render Process构成一个Electron应用程序的基本框架。
  • Extension Host Process(插件宿主进程/插件主机进程):本质上是一个由主进程启动、渲染进程管理的插件管理器。该进程可以运行满足触发条件的插件进程。插件在启动过程中,插件被禁止直接访问UI。
  • Debugger Process(调试进程):这是一个特殊化的插件
  • Search Process:搜索是一类计算密集型的任务,单开进程保证软件整体的体验和性能。

进程间通信构建过程详解

基于MessagePortMain(通信桥)的通信实现

  • 根据Electron的文档,我们可以知道,在Electron框架中实现进程间通信有两种方式,第一种是常见的IPCMain和IPCRender,这是主进程和渲染进程或者是工具进程(UtilityProcess)的通信通道,特点是通信所有权不可转让。第二种就是MessagePortMain,也就是通信桥。其最大的特点就是通信所有权可以转让,实现更加灵活。
  • 看如下的源码:
kotlin 复制代码
connect(payload?: unknown): Electron.MessagePortMain {
		const { port1: outPort, port2: utilityProcessPort } = new MessageChannelMain();
		this.postMessage(payload, [utilityProcessPort]);

		return outPort;
	}
  • 这段代码是在主进程中运行的。主要的功能就是创建一个和子进程联通的通信桥,然后将主进程端的通信所有权移交给其他进程。

  • 第一种通信实现,是基于Electron IPC的,而第二种则是基于Node IPC了。这也是为什么渲染进程和共享进程以及渲染进程和Extension Host等之间的通信是Node IPC。这是因为主进程不仅创建了对应的子进程,而且通过这种通信桥的通信实现,将对应的沟通通道转接了其他的进程。

Caller-Service机制

  • VSCode的很多概念都被包装成了Service,因此这一小节讲一下Caller-Service机制,即服务调用的最基本的机制。

  • 部分Service实际上是基于IServerChannel进行了包裹,本质上就是一个对Caller(请求方)的进行响应的Service。结合描述,可以做出如下的结论:

    • Protocol是基于不同场景下实现的真实通信信道,比如:基于Electron的ipcMain和ipcRender构建的信道,或者基于Electron的MessagePortMain构建的信道,这些都是框架赋予的基本信道,是真实存在的,而其他的概念都是基于逻辑衍生出来的逻辑处理。
    • IChannel是一个请求发送的请求者,Client就是实际发送请求的人。Client除了替IChannel发送请求之外,在具体的是线上,Client还会保存一个接收到响应之后的handler,用于处理收到对应的响应消息。
    • IServerChannel是一个与IChannel相对应的请求处理者,IChannelServer是根据传过来的消息,将请求发送给对应ServerChannel处理的请求路由。
    • Service和Caller就是分别基于IServerChannel和IChannel构建出来的具体的服务逻辑。
    • Caller-Service机制就是一个请求-响应-处理响应的单向消息传递机制。

ChannelClient-ChannelServer源码解析

  • 首先看ChannelClient的源码:
ini 复制代码
export class ChannelClient implements IChannelClient, IDisposable {

	private isDisposed: boolean = false;
	private state: State = State.Uninitialized;
	private activeRequests = new Set<IDisposable>();
	private handlers = new Map<number, IHandler>();
	private lastRequestId: number = 0;
	private protocolListener: IDisposable | null;
	private logger: IIPCLogger | null;

	private readonly _onDidInitialize = new Emitter<void>();
	readonly onDidInitialize = this._onDidInitialize.event;

	constructor(private protocol: IMessagePassingProtocol, logger: IIPCLogger | null = null) {
		this.protocolListener = this.protocol.onMessage(msg => this.onBuffer(msg));
		this.logger = logger;
	}

	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;
	}

	private requestPromise(channelName: string, name: string, arg?: any, cancellationToken = CancellationToken.None): Promise<any> {
		const id = this.lastRequestId++;
		const type = RequestType.Promise;
		const request: IRawRequest = { id, type, channelName, name, arg };

		if (cancellationToken.isCancellationRequested) {
			return Promise.reject(new CancellationError());
		}

		let disposable: IDisposable;

		const result = new Promise((c, e) => {
			if (cancellationToken.isCancellationRequested) {
				return e(new CancellationError());
			}

			const doRequest = () => {
				const handler: IHandler = response => {
					switch (response.type) {
						case ResponseType.PromiseSuccess:
							this.handlers.delete(id);
							c(response.data);
							break;

						case ResponseType.PromiseError: {
							this.handlers.delete(id);
							const error = new Error(response.data.message);
							(<any>error).stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack;
							error.name = response.data.name;
							e(error);
							break;
						}
						case ResponseType.PromiseErrorObj:
							this.handlers.delete(id);
							e(response.data);
							break;
					}
				};

				this.handlers.set(id, handler);
				this.sendRequest(request);
			};

			let uninitializedPromise: CancelablePromise<void> | null = null;
			if (this.state === State.Idle) {
				doRequest();
			} else {
				uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
				uninitializedPromise.then(() => {
					uninitializedPromise = null;
					doRequest();
				});
			}

			const cancel = () => {
				if (uninitializedPromise) {
					uninitializedPromise.cancel();
					uninitializedPromise = null;
				} else {
					this.sendRequest({ id, type: RequestType.PromiseCancel });
				}

				e(new CancellationError());
			};

			const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel);
			disposable = combinedDisposable(toDisposable(cancel), cancellationTokenListener);
			this.activeRequests.add(disposable);
		});

		return result.finally(() => {
			disposable.dispose();
			this.activeRequests.delete(disposable);
		});
	}

	private requestEvent(channelName: string, name: string, arg?: any): Event<any> {
		const id = this.lastRequestId++;
		const type = RequestType.EventListen;
		const request: IRawRequest = { id, type, channelName, name, arg };

		let uninitializedPromise: CancelablePromise<void> | null = null;

		const emitter = new Emitter<any>({
			onWillAddFirstListener: () => {
				uninitializedPromise = createCancelablePromise(_ => this.whenInitialized());
				uninitializedPromise.then(() => {
					uninitializedPromise = null;
					this.activeRequests.add(emitter);
					this.sendRequest(request);
				});
			},
			onDidRemoveLastListener: () => {
				if (uninitializedPromise) {
					uninitializedPromise.cancel();
					uninitializedPromise = null;
				} else {
					this.activeRequests.delete(emitter);
					this.sendRequest({ id, type: RequestType.EventDispose });
				}
			}
		});

		const handler: IHandler = (res: IRawResponse) => emitter.fire((res as IRawEventFireResponse).data);
		this.handlers.set(id, handler);

		return emitter.event;
	}

	private sendRequest(request: IRawRequest): void {
		switch (request.type) {
			case RequestType.Promise:
			case RequestType.EventListen: {
				const msgLength = this.send([request.type, request.id, request.channelName, request.name], request.arg);
				this.logger?.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, `${requestTypeToStr(request.type)}: ${request.channelName}.${request.name}`, request.arg);
				return;
			}

			case RequestType.PromiseCancel:
			case RequestType.EventDispose: {
				const msgLength = this.send([request.type, request.id]);
				this.logger?.logOutgoing(msgLength, request.id, RequestInitiator.LocalSide, requestTypeToStr(request.type));
				return;
			}
		}
	}

	private send(header: any, body: any = undefined): number {
		const writer = new BufferWriter();
		serialize(writer, header);
		serialize(writer, body);
		return this.sendBuffer(writer.buffer);
	}

	private sendBuffer(message: VSBuffer): number {
		try {
			this.protocol.send(message);
			return message.byteLength;
		} catch (err) {
			// noop
			return 0;
		}
	}

	private onBuffer(message: VSBuffer): void {
		const reader = new BufferReader(message);
		const header = deserialize(reader);
		const body = deserialize(reader);
		const type: ResponseType = header[0];

		switch (type) {
			case ResponseType.Initialize:
				this.logger?.logIncoming(message.byteLength, 0, RequestInitiator.LocalSide, responseTypeToStr(type));
				return this.onResponse({ type: header[0] });

			case ResponseType.PromiseSuccess:
			case ResponseType.PromiseError:
			case ResponseType.EventFire:
			case ResponseType.PromiseErrorObj:
				this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.LocalSide, responseTypeToStr(type), body);
				return this.onResponse({ type: header[0], id: header[1], data: body });
		}
	}

	private onResponse(response: IRawResponse): void {
		if (response.type === ResponseType.Initialize) {
			this.state = State.Idle;
			this._onDidInitialize.fire();
			return;
		}

		const handler = this.handlers.get(response.id);

		handler?.(response);
	}

	@memoize
	get onDidInitializePromise(): Promise<void> {
		return Event.toPromise(this.onDidInitialize);
	}

	private whenInitialized(): Promise<void> {
		if (this.state === State.Idle) {
			return Promise.resolve();
		} else {
			return this.onDidInitializePromise;
		}
	}

	dispose(): void {
		this.isDisposed = true;
		if (this.protocolListener) {
			this.protocolListener.dispose();
			this.protocolListener = null;
		}
		dispose(this.activeRequests.values());
		this.activeRequests.clear();
	}
}
  • 上述是一个比较通用的ChannelClient的实现。可以看到ChannelClient实现了IChannelClient接口,也就是实现了getChannel这个方法。可以看出来,getChannel是一个闭包的方法,是将ChannelClient内的方法暴露给外界使用,并且内存中只有一个Client对象,并且ChannelClient可以根据传入的不同IChannel,调用不同的请求方式。

    • 在构造函数中,也可以看到,这个Client对信道进行了onMessage的监听。
    • 在这个实现中,有一个handlers的Map对象。这个对象用于在发送请求时,存储一个响应返回后的Handler,用于处理响应。
  • 其次看ChannelServer的源码:

kotlin 复制代码
export class ChannelServer<TContext = string> implements IChannelServer<TContext>, IDisposable {

	private channels = new Map<string, IServerChannel<TContext>>();
	private activeRequests = new Map<number, IDisposable>();
	private protocolListener: IDisposable | null;

	// Requests might come in for channels which are not yet registered.
	// They will timeout after `timeoutDelay`.
	private pendingRequests = new Map<string, PendingRequest[]>();

	constructor(private protocol: IMessagePassingProtocol, private ctx: TContext, private logger: IIPCLogger | null = null, private timeoutDelay: number = 1000) {
		this.protocolListener = this.protocol.onMessage(msg => this.onRawMessage(msg));
		this.sendResponse({ type: ResponseType.Initialize });
	}

	registerChannel(channelName: string, channel: IServerChannel<TContext>): void {
		this.channels.set(channelName, channel);

		// https://github.com/microsoft/vscode/issues/72531
		setTimeout(() => this.flushPendingRequests(channelName), 0);
	}

	private sendResponse(response: IRawResponse): void {
		switch (response.type) {
			case ResponseType.Initialize: {
				const msgLength = this.send([response.type]);
				this.logger?.logOutgoing(msgLength, 0, RequestInitiator.OtherSide, responseTypeToStr(response.type));
				return;
			}

			case ResponseType.PromiseSuccess:
			case ResponseType.PromiseError:
			case ResponseType.EventFire:
			case ResponseType.PromiseErrorObj: {
				const msgLength = this.send([response.type, response.id], response.data);
				this.logger?.logOutgoing(msgLength, response.id, RequestInitiator.OtherSide, responseTypeToStr(response.type), response.data);
				return;
			}
		}
	}

	private send(header: any, body: any = undefined): number {
		const writer = new BufferWriter();
		serialize(writer, header);
		serialize(writer, body);
		return this.sendBuffer(writer.buffer);
	}

	private sendBuffer(message: VSBuffer): number {
		try {
			this.protocol.send(message);
			return message.byteLength;
		} catch (err) {
			// noop
			return 0;
		}
	}

	private onRawMessage(message: VSBuffer): void {
		const reader = new BufferReader(message);
		const header = deserialize(reader);
		const body = deserialize(reader);
		const type = header[0] as RequestType;

		switch (type) {
			case RequestType.Promise:
				this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
				return this.onPromise({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
			case RequestType.EventListen:
				this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}: ${header[2]}.${header[3]}`, body);
				return this.onEventListen({ type, id: header[1], channelName: header[2], name: header[3], arg: body });
			case RequestType.PromiseCancel:
				this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}`);
				return this.disposeActiveRequest({ type, id: header[1] });
			case RequestType.EventDispose:
				this.logger?.logIncoming(message.byteLength, header[1], RequestInitiator.OtherSide, `${requestTypeToStr(type)}`);
				return this.disposeActiveRequest({ type, id: header[1] });
		}
	}

	private onPromise(request: IRawPromiseRequest): void {
		const channel = this.channels.get(request.channelName);

		if (!channel) {
			this.collectPendingRequest(request);
			return;
		}

		const cancellationTokenSource = new CancellationTokenSource();
		let promise: Promise<any>;

		try {
			promise = channel.call(this.ctx, request.name, request.arg, cancellationTokenSource.token);
		} catch (err) {
			promise = Promise.reject(err);
		}

		const id = request.id;

		promise.then(data => {
			this.sendResponse(<IRawResponse>{ id, data, type: ResponseType.PromiseSuccess });
		}, err => {
			if (err instanceof Error) {
				this.sendResponse(<IRawResponse>{
					id, data: {
						message: err.message,
						name: err.name,
						stack: err.stack ? (err.stack.split ? err.stack.split('\n') : err.stack) : undefined
					}, type: ResponseType.PromiseError
				});
			} else {
				this.sendResponse(<IRawResponse>{ id, data: err, type: ResponseType.PromiseErrorObj });
			}
		}).finally(() => {
			disposable.dispose();
			this.activeRequests.delete(request.id);
		});

		const disposable = toDisposable(() => cancellationTokenSource.cancel());
		this.activeRequests.set(request.id, disposable);
	}

	private onEventListen(request: IRawEventListenRequest): void {
		const channel = this.channels.get(request.channelName);

		if (!channel) {
			this.collectPendingRequest(request);
			return;
		}

		const id = request.id;
		const event = channel.listen(this.ctx, request.name, request.arg);
		const disposable = event(data => this.sendResponse(<IRawResponse>{ id, data, type: ResponseType.EventFire }));

		this.activeRequests.set(request.id, disposable);
	}

	private disposeActiveRequest(request: IRawRequest): void {
		const disposable = this.activeRequests.get(request.id);

		if (disposable) {
			disposable.dispose();
			this.activeRequests.delete(request.id);
		}
	}

	private collectPendingRequest(request: IRawPromiseRequest | IRawEventListenRequest): void {
		let pendingRequests = this.pendingRequests.get(request.channelName);

		if (!pendingRequests) {
			pendingRequests = [];
			this.pendingRequests.set(request.channelName, pendingRequests);
		}

		const timer = setTimeout(() => {
			console.error(`Unknown channel: ${request.channelName}`);

			if (request.type === RequestType.Promise) {
				this.sendResponse(<IRawResponse>{
					id: request.id,
					data: { name: 'Unknown channel', message: `Channel name '${request.channelName}' timed out after ${this.timeoutDelay}ms`, stack: undefined },
					type: ResponseType.PromiseError
				});
			}
		}, this.timeoutDelay);

		pendingRequests.push({ request, timeoutTimer: timer });
	}

	private flushPendingRequests(channelName: string): void {
		const requests = this.pendingRequests.get(channelName);

		if (requests) {
			for (const request of requests) {
				clearTimeout(request.timeoutTimer);

				switch (request.request.type) {
					case RequestType.Promise: this.onPromise(request.request); break;
					case RequestType.EventListen: this.onEventListen(request.request); break;
				}
			}

			this.pendingRequests.delete(channelName);
		}
	}

	public dispose(): void {
		if (this.protocolListener) {
			this.protocolListener.dispose();
			this.protocolListener = null;
		}
		dispose(this.activeRequests.values());
		this.activeRequests.clear();
	}
}
  • 上述也是一个比较通用的ChannelServer的实现。可以看到了ChannelServer实现了IChannelServer接口,也就是实现了registerChannel这个方法。这个方法比较简单,也就是向ChannelServer内的channels Map注册对应的ServerChannel,也就是处理频道。对过来的消息,也会根据不同的标识进行处理。
    • 在构造函数中,也可以看到和Client一样的行为,那就是对信道进行onMessage的监听。
    • 除此之外,还需要向Client发送一个初始化信息。

一对一通信和一对多通信

  • 在上述描述的基础上,我们可以看到了一个比较完整的单向通信方式,也就是Caller-Service机制。那么如何实现一对一的双向通信呢?
  • 很简单,让一个对象同时具有ChannelServer和ChannelClient就可以了。
  • 这时候就会有一个新的概念,那就是IPCClient(IPC Client)。这里和网上有一些资料描述的不一样,那就是一对IPC Client就可以具有一对一的通信,而不是非要一个Client对应一个Server。后者的说法很容易造成误解。
  • IPC Client的源码也很简单,就是包裹了一个ChannelServer和一个ChannelClient。
typescript 复制代码
export class IPCClient<TContext = string> implements IChannelClient, IChannelServer<TContext>, IDisposable {

	private channelClient: ChannelClient;
	private channelServer: ChannelServer<TContext>;

	constructor(protocol: IMessagePassingProtocol, ctx: TContext, ipcLogger: IIPCLogger | null = null) {
		const writer = new BufferWriter();
		serialize(writer, ctx);
		protocol.send(writer.buffer);

		this.channelClient = new ChannelClient(protocol, ipcLogger);
		this.channelServer = new ChannelServer(protocol, ctx, ipcLogger);
	}

	getChannel<T extends IChannel>(channelName: string): T {
		return this.channelClient.getChannel(channelName) as T;
	}

	registerChannel(channelName: string, channel: IServerChannel<TContext>): void {
		this.channelServer.registerChannel(channelName, channel);
	}

	dispose(): void {
		this.channelClient.dispose();
		this.channelServer.dispose();
	}
}
  • 那么此时问题又来了,在Electron框架下,主进程只有一个,而且VSCode还构建了全局唯一的一个共享进程,这就需要一个一对多的通信。这里就需要引入两个概念,一个Connection,另外一个IPCServer(IPC Server)。主进程和共享进程可能会同时服务多个渲染进程,此时就需要一个对象在主进程端,对不同的渲染进程连接的IPC Client进行管理。
  • Connection本质上就是一个IPCClient和一个唯一标识ctx组成的,可以看成是一个IPCClient。而IPCServer就是对多个Connection进行管理和通信的对象。
  • 先看一下Connection的源码:
csharp 复制代码
interface Connection<TContext> extends Client<TContext> {
	readonly channelServer: ChannelServer<TContext>;
	readonly channelClient: ChannelClient;
}

export interface Client<TContext> {
	readonly ctx: TContext;
}
  • Connection就是一个接口规范,没有具体的实现

  • 再看一下IPC Server的源码:

ini 复制代码
export class IPCServer<TContext = string> implements IChannelServer<TContext>, IRoutingChannelClient<TContext>, IConnectionHub<TContext>, IDisposable {

	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(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);
				}));
			}));
		}));
	}

	/**
	 * Get a channel from a remote client. When passed a router,
	 * one can specify which client it wants to call and listen to/from.
	 * Otherwise, when calling without a router, a random client will
	 * be selected and when listening without a router, every client
	 * will be listened to.
	 */
	getChannel<T extends IChannel>(channelName: string, router: IClientRouter<TContext>): T;
	getChannel<T extends IChannel>(channelName: string, clientFilter: (client: Client<TContext>) => boolean): T;
	getChannel<T extends IChannel>(channelName: string, routerOrClientFilter: IClientRouter<TContext> | ((client: Client<TContext>) => boolean)): T {
		const that = this;

		return {
			call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise<T> {
				let connectionPromise: Promise<Client<TContext>>;

				if (isFunction(routerOrClientFilter)) {
					// when no router is provided, we go random client picking
					const connection = getRandomElement(that.connections.filter(routerOrClientFilter));

					connectionPromise = connection
						// if we found a client, let's call on it
						? Promise.resolve(connection)
						// else, let's wait for a client to come along
						: Event.toPromise(Event.filter(that.onDidAddConnection, routerOrClientFilter));
				} else {
					connectionPromise = routerOrClientFilter.routeCall(that, command, arg);
				}

				const channelPromise = connectionPromise
					.then(connection => (connection as Connection<TContext>).channelClient.getChannel(channelName));

				return getDelayedChannel(channelPromise)
					.call(command, arg, cancellationToken);
			},
			listen(event: string, arg: any): Event<T> {
				if (isFunction(routerOrClientFilter)) {
					return that.getMulticastEvent(channelName, routerOrClientFilter, event, arg);
				}

				const channelPromise = routerOrClientFilter.routeEvent(that, event, arg)
					.then(connection => (connection as Connection<TContext>).channelClient.getChannel(channelName));

				return getDelayedChannel(channelPromise)
					.listen(event, arg);
			}
		} as T;
	}

	private getMulticastEvent<T extends IChannel>(channelName: string, clientFilter: (client: Client<TContext>) => boolean, eventName: string, arg: any): Event<T> {
		const that = this;
		let disposables: DisposableStore | undefined;

		// Create an emitter which hooks up to all clients
		// as soon as first listener is added. It also
		// disconnects from all clients as soon as the last listener
		// is removed.
		const emitter = new Emitter<T>({
			onWillAddFirstListener: () => {
				disposables = new DisposableStore();

				// The event multiplexer is useful since the active
				// client list is dynamic. We need to hook up and disconnection
				// to/from clients as they come and go.
				const eventMultiplexer = new EventMultiplexer<T>();
				const map = new Map<Connection<TContext>, IDisposable>();

				const onDidAddConnection = (connection: Connection<TContext>) => {
					const channel = connection.channelClient.getChannel(channelName);
					const event = channel.listen<T>(eventName, arg);
					const disposable = eventMultiplexer.add(event);

					map.set(connection, disposable);
				};

				const onDidRemoveConnection = (connection: Connection<TContext>) => {
					const disposable = map.get(connection);

					if (!disposable) {
						return;
					}

					disposable.dispose();
					map.delete(connection);
				};

				that.connections.filter(clientFilter).forEach(onDidAddConnection);
				Event.filter(that.onDidAddConnection, clientFilter)(onDidAddConnection, undefined, disposables);
				that.onDidRemoveConnection(onDidRemoveConnection, undefined, disposables);
				eventMultiplexer.event(emitter.fire, emitter, disposables);

				disposables.add(eventMultiplexer);
			},
			onDidRemoveLastListener: () => {
				disposables?.dispose();
				disposables = undefined;
			}
		});

		return emitter.event;
	}

	registerChannel(channelName: string, channel: IServerChannel<TContext>): void {
		this.channels.set(channelName, channel);

		for (const connection of this._connections) {
			connection.channelServer.registerChannel(channelName, channel);
		}
	}

	dispose(): void {
		this.disposables.dispose();

		for (const connection of this._connections) {
			connection.channelClient.dispose();
			connection.channelServer.dispose();
		}

		this._connections.clear();
		this.channels.clear();
		this._onDidAddConnection.dispose();
		this._onDidRemoveConnection.dispose();
	}
}
  • 可以看到源码中IPCServer不仅对Connection进行了管理,还有单个IPC Client的功能,具体原因是因为主进程还需要跟共享进程进行通信,所以这里也有一部分点对点的通信,因此IPC Server不仅有管理IPCClient的功能,还有单个IPCClient的具体功能。

VSCode进程通信构建流程

  1. 主进程启动,构建一个IPCServer,

  2. 主进程-共享进程

    1. 主进程 fork 共享进程,两者构建基于Electron IPC的通信,主进程侧通过闭包的形式将连接共享进程的方式暴露。
    2. 共享进程创建一个IPC Server,用于管理渲染进程Client
  3. 主进程 - 渲染进程

    1. 主进程 fork 渲染进程,两者构建Electron IPC通信,同时构建渲染进程侧的IPCClient。主进程的IPCServer将其纳入管理。
    2. 渲染进程生成RequestChannel和ResponseChannel,向主进程请求连接共享进程。
    3. 主进程在收到请求之后,通过上述提及的通信桥通信机制,构建渲染进程和共享进程的通信。
    4. 共享进程将上述构建的通信通道纳入到Client管理中。
    5. 渲染进程生成ResponseChannel,向主进程请求创建不同的工具进程。
    6. 主进程在收到请求之后,通过上述提及的通信桥通信机制,fork新的子进程,并将通信通道所有权移交给渲染进程。
  • 综上所属:IPCServer只存在于主进程和共享进程中,渲染进程中没有IPCServer,其与不同工具进程间的通信都是一对一式的,并且包装成了不同的Service提供服务。
相关推荐
小林学习编程11 分钟前
Springboot考研信息平台
spring boot·后端·考研
长勺1 小时前
Spring Security vs Shiro vs Sa-Token
java·后端·spring
yezipi耶不耶1 小时前
Rust入门之高级Trait
开发语言·后端·rust
qq_12498707532 小时前
原生小程序+springboot+vue+协同过滤算法的音乐推荐系统(源码+论文+讲解+安装+部署+调试)
java·spring boot·后端·小程序·毕业设计·课程设计·协同过滤
后青春期的诗go2 小时前
基于Rust语言的Rocket框架和Sqlx库开发WebAPI项目记录(一)
开发语言·后端·rust
信徒_3 小时前
SpringBoot 自动装配流程
java·spring boot·后端
景天科技苑3 小时前
【Rust闭包】rust语言闭包函数原理用法汇总与应用实战
开发语言·后端·rust·闭包·闭包函数·rust闭包·rust闭包用法
-曾牛12 小时前
基于微信小程序的在线聊天功能实现:WebSocket通信实战
前端·后端·websocket·网络协议·微信小程序·小程序·notepad++
Warren9813 小时前
Java面试八股Spring篇(4500字)
java·开发语言·spring boot·后端·spring·面试
背帆14 小时前
go的interface接口底层实现
开发语言·后端·golang