全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-10-11_VSCode的Electron通信方案

您的位置:首页 >> 新闻 >> 行业资讯

VSCode的Electron通信方案 点击关注公众号,“技术干货”及时达! vscode方案梳理vscode的ipc方案经历以下几个过程: preload.js中暴露ipcRender.invoke,ipcRender.call等方法供渲染进程调用在主进程创建Server类管理各个窗口和主进程的Connection在Server上注册Channel供渲染进程调用在渲染进程创建Client类连接Server最后就可以在渲染进程通过Channel调用主进程的方法连接篇由上面的架构图开始,我们先来看渲染端的Client类 exportclassClientextendsIPCClientimplementsIDisposable{ privateprotocol:Protocol privatestaticcreateProtocol():Protocol{ constonMessage=Event.fromNodeEventEmitterELBuffer(ipcRenderer,'vscode:message',(_,message)=ELBuffer.wrap(message)) ipcRenderer.send('vscode:hello') returnnewProtocol(ipcRenderer,onMessage) } constructor(id:string){ constprotocol=Client.createProtocol() super(protocol,id) this.protocol=protocol } overridedispose():void{ this.protocol.disconnect() super.dispose() } } 解释一下,Client先监听了ipcRenderer上的vscode:message事件,然后发送了vscode:hello通知主进程Client连接,然后创建了Protocol类传给IPCClient基类 接下来看一下IPCClient classIPCClientTContext=stringimplementsIDisposable{ privatechannelClient:ChannelClient privatechannelServer:ChannelServer constructor(protocol:IMessagePassingProtocol,ctx:TContext){ constwriter=newBufferWriter() serialize(writer,ctx) protocol.send(writer.buffer) this.channelClient=newChannelClient(protocol) this.channelServer=newChannelServer(protocol,ctx) } getChannelTextendsIChannel(channelName:string):T{ returnthis.channelClient.getChannel(channelName)asT } registerChannel(channelName:string,channel:IServerChannelstring):void{ this.channelServer.registerChannel(channelName,channel) } dispose():void{ this.channelClient.dispose() this.channelServer.dispose() } } 可以看到IPCClient的作用就是管理Channel. 注意看这一段代码 constwriter=newBufferWriter() serialize(writer,ctx)//ctx的实际值为windowId rotocol.send(writer.buffer) 这里其实是向主进程发送了当前窗口的id作为标识 接下来再看下主进程的Server类 classServerextendsIPCServer{ privatestaticreadonlyClients=newMapnumber,IDisposable() privatestaticgetOnDidClientConnect():EventClientConnectionEvent{ constonHello=Event.fromNodeEventEmitterWebContents(ipcMain,'vscode:hello',({sender})=sender) returnEvent.map(onHello,(webContents)={ constid=webContents.id constclient=Server.Clients.get(id) client?.dispose() constonDidClientReconnect=newEmittervoid() Server.Clients.set(id,toDisposable(()=onDidClientReconnect.fire())) constonMessage=createScopedOnMessageEvent(id,'vscode:message')asEventELBuffer constonDidClientDisconnect=Event.any(Event.signal(createScopedOnMessageEvent(id,'vscode:disconnect')),onDidClientReconnect.event) constprotocol=newElectronProtocol(webContents,onMessage) return{protocol,onDidClientDisconnect} }) } constructor(){ super(Server.getOnDidClientConnect()) } } 这段代码比较简单,就是处理了一下重新连接的问题 接下来IPCServer classIPCServerTContextextendsstring=string{ privatechannels=newMapstring,IServerChannelTContext() private_connections=newSetConnectionTContext() privatereadonly_onDidAddConnection=newEmitterConnectionTContext() readonlyonDidAddConnection:EventConnectionTContext=this._onDidAddConnection.event privatereadonly_onDidRemoveConnection=newEmitterConnectionTContext() readonlyonDidRemoveConnection:EventConnectionTContext=this._onDidRemoveConnection.event privatereadonlydisposables=newDisposableStore() getconnections():ConnectionTContext[]{ constresult:ConnectionTContext[]=[] this._connections.forEach(ctx=result.push(ctx)) returnresult } constructor(onDidClientConnect:EventClientConnectionEvent){ this.disposables.add(onDidClientConnect(({protocol,onDidClientDisconnect})={ constonFirstMessage=Event.once(protocol.onMessage) this.disposables.add(onFirstMessage((msg)={ constreader=newBufferReader(msg) constctx=deserialize(reader)asTContext constchannelServer=newChannelServerTContext(protocol,ctx) constchannelClient=newChannelClient(protocol) this.channels.forEach((channel,name)=channelServer.registerChannel(name,channel)) constconnection:ConnectionTContext={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) })) })) })) } getChannelTextendsIChannel(channelName:string,routerOrClientFilter:IClientRouterTContext|((client:ClientTContext)=boolean)):T{ } privategetMulticastEventTextendsIChannel(channelName:string,clientFilter:(client:ClientTContext)=boolean,eventName:string,arg:any):EventT{ } registerChannel(channelName:string,channel:IServerChannelTContext):void{ } dispose():void{ } 可以看到Server上管理了所有的Channel和Connection 通信篇通过连接篇的介绍,主进程和渲染进程建立起了连接,那接下来就是如何进行通信,以下是一个简易的例子: //services/fileSystem.ts classIFileSystem{ stat:(source:string)=PromiseStat } //main.ts constserver=newServer() server.registerChannel( "fileSystem", ProxyChannel.fromService({ stat(source:string){ returnfs.stat(source) } }))//这里的ProxyChannel.fromService后面再解释 //renderer.ts constclient=newClient() constfileSystemChannel=client.getChannel("fileSystem") conststat=awitefileSystemChannel.call("stat") //或者 constclient=newClient() constfileSystemChannel=client.getChannel("fileSystem") constfileSystemService=ProxyChannel.toServiceIFileSystemChannel(fileSystemChannel)//后面解释 conststat=awitefileSystemChannel.call("stat") 要搞清楚它们如何调用,首先要明白Channel的构成以及Channel如何被创建 interfaceIChannel{ call:T(command:string,arg?:any,cancellationToken?:CancellationToken)=Promise listen:T(event:string,arg?:any)=Event } //Channel的创建在ChannelClient类中 getChannelTextendsIChannel(channelName:string):T{ constthat=this return{ call(command:string,arg?:any,cancellationToken?:CancellationToken){ if(that.isDisposed){ returnPromise.reject(newCancellationError()) } returnthat.requestPromise(channelName,command,arg,cancellationToken) }, listen(event:string,arg:any){ if(that.isDisposed){ returnEvent.None } returnthat.requestEvent(channelName,event,arg) }, }asT } 这里的that.requestPromise其实就是调用ipcRender.invoke('vscode:message',.....),会把channelName,command和其他参数一起传过去 然后我们看一下Server怎么处理的请求(删减了部分代码) privateonPromise(request:IRawPromiseRequest):void{ constchannel=this.channels.get(request.channelName) if(!channel){ return } letpromise:Promiseany try{ promise=channel.call(this.ctx,request.name,request.arg) } catch(e){ promise=Promise.reject(e) } constid=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的代码 exportfunctionfromServiceTContext(service:unknown,disposables:DisposableStore,options?:ICreateServiceChannelOptions):IServerChannelTContext{ consthandler=serviceas{[key:string]:unknown} constdisableMarshalling=optionsoptions.disableMarshalling constmapEventNameToEvent=newMapstring,Eventunknown() for(constkeyinhandler){ if(propertyIsEvent(key)){ mapEventNameToEvent.set(key,EventType.buffer(handler[key]asEventunknown,true,undefined,disposables)) } } returnnewclassimplementsIServerChannel{ listen(_:unknown,event:string,arg:any):Event{ consteventImpl=mapEventNameToEvent.get(event) if(eventImpl){ returneventImplasEvent } consttarget=handler[event] if(typeoftarget==='function'){ if(propertyIsDynamicEvent(event)){ returntarget.call(handler,arg) } if(propertyIsEvent(event)){ mapEventNameToEvent.set(event,EventType.buffer(handler[event]asEventunknown,true,undefined,disposables)) returnmapEventNameToEvent.get(event)asEvent } } thrownewError(`Eventnotfound:${event}`) } call(_:unknown,command:string,args?:any[]):Promiseany{ consttarget=handler[command] if(typeoftarget==='function'){ //Reviveunlessmarshallingdisabled if(!disableMarshallingArray.isArray(args)){ for(leti=0;iargs.length;i++){ args[i]=revive(args[i]) } } letres=target.apply(handler,args) if(!(resinstanceofPromise)){ res=Promise.resolve(res) } returnres } thrownewError(`Methodnotfound:${command}`) } }() } exportfunctiontoServiceTextendsobject(channel:IChannel,options?:ICreateProxyServiceOptions):T{ returnnewProxy({},{ get(_target:T,propKey:PropertyKey){ if(typeofpropKey==='string'){ if(options?.properties?.has(propKey)){ returnoptions.properties.get(propKey) } returnasyncfunction(...args:any[]){ constresult=awaitchannel.call(propKey,args) returnresult } } thrownewError(`Propertynotfound:${String(propKey)}`) }, })asT } 可以看到fromService是将类转换为Channel,而toService是将Channel转换为有类型提示的Service 总结vscode通过这样抽象了一套Channel机制进行通信,便于代码的管理和跨平台。源码还删减了很多关于副作用处理以及事件机制的代码,感兴趣的可以拉取vscode源码查看 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2020-04-11_参赛3年,斩获6金3银2铜:Kaggle Grandmaster亲授实战经验 下一篇:2020-09-17_15年!NumPy论文终出炉,还登上了Nature

TAG标签:

19
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价