概述
Vscode渲染进程与插件是运行在不同的进程中的,为了能访问插件的功能,两者之间必须建立通信通道。
Vscode使用的是socket进行进程间的通信,因为是在本地进程间通信,而且TCP/IP协议栈更复杂,为了提高效率,使用了文件协议的socket。Windows上采用命名管道,类unix系统上采用UNIX 域。
由于socket是面向字节流的,需要定义应用层协议,来使双方识别通信报文。
协议介绍
一般通信协议会规定以下两种结构:
- 报文的开始与结束标识,如果报文body出现了相同的标识是需要做转义的。
定义开始与结束标识,主要是为了容错。出现畸形报文后,通过开始与结束标识,后续可以继续识别正常的报文。 - 报文结构,即一帧报文各个字节代表的意义,一般报文分为报文头,与报文体。
报文头定义处理协议时需要的参数,比如报文体的长度,协议版本。以IP协议为例,IP协议的报文头中包含源、宿端地址,因为处理IP报文的时候需要这两个参数,用于报文的转发。
报文体就是有效数据。如果上面还有协议则为上层协议处理后的数据,如果上层没有协议则直接为程序中发送的数据。
定义好报文结构后,协议处理程序,就可以将一帧报文的字节数据,转成应用能够识别的数据。
Vscode设计的通信协议,只定义了报文结构,未使用开始与结束标识。
报文开始与结束标识一般出现在比较底层的协议层中,主要是为了容错,防止畸形报文,打乱了后面所有的报文的解析。vscode直接基于socket进行通信,而且是单机内通信,不需要做这种多余的防护。
报文结构
源码解读
//接受消息
_socket.on('data', (data: Buffer) => {
chunks.push(data);
totalLength += data.length;
while (totalLength > 0) {
//检查是否读过头部文件
if (state.readHead) {
//检查是否大于17个字符
if (totalLength >= Protocol._headerLen) {
const all = Buffer.concat(chunks);
//读第一个字节 判断是否是json
state.bodyIsJson = all.readInt8(0) === 1;
//读取消息体的长度
state.bodyLen = all.readInt32BE(1);
state.readHead = false;
const rest = all.slice(Protocol._headerLen);
totalLength = rest.length;
chunks = [rest];
} else {
break;
}
}
if (!state.readHead) {
// expecting body -> read bodyLen-bytes for
// the actual message or wait for more data
if (totalLength >= state.bodyLen) {
const all = Buffer.concat(chunks);
let message = all.toString('utf8', 0, state.bodyLen);
if (state.bodyIsJson) {
message = JSON.parse(message);
}
this._onMessage.fire(message);
const rest = all.slice(state.bodyLen);
totalLength = rest.length;
chunks = [rest];
state.bodyIsJson = false;
state.bodyLen = -1;
state.readHead = true;
} else {
break;
}
}
}
});
}
//发送数据
public send(message: any): void {
//构造消息头
const header = Buffer.alloc(Protocol._headerLen);
//构造消息体
if (typeof message !== 'string') {
message = JSON.stringify(message);
header.writeInt8(1, 0);
}
const data = Buffer.from(message);
header.writeInt32BE(data.length, 1);
//发送消息
this._writeSoon(header, data);
}