-
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进程通信构建流程
-
主进程启动,构建一个IPCServer,
-
主进程-共享进程
- 主进程 fork 共享进程,两者构建基于Electron IPC的通信,主进程侧通过闭包的形式将连接共享进程的方式暴露。
- 共享进程创建一个IPC Server,用于管理渲染进程Client
-
主进程 - 渲染进程
- 主进程 fork 渲染进程,两者构建Electron IPC通信,同时构建渲染进程侧的IPCClient。主进程的IPCServer将其纳入管理。
- 渲染进程生成RequestChannel和ResponseChannel,向主进程请求连接共享进程。
- 主进程在收到请求之后,通过上述提及的通信桥通信机制,构建渲染进程和共享进程的通信。
- 共享进程将上述构建的通信通道纳入到Client管理中。
- 渲染进程生成ResponseChannel,向主进程请求创建不同的工具进程。
- 主进程在收到请求之后,通过上述提及的通信桥通信机制,fork新的子进程,并将通信通道所有权移交给渲染进程。
- 综上所属:IPCServer只存在于主进程和共享进程中,渲染进程中没有IPCServer,其与不同工具进程间的通信都是一对一式的,并且包装成了不同的Service提供服务。