Guacamole 协议由若干指令组成。每条指令是一个逗号分隔的列表,最后以分号终止,其中列表中的第一个元素是指令操作码,其后的元素是该指令的参数:
OPCODE,ARG1,ARG2,ARG3,……;
指令列表中的每个元素都是由一个正的十进制整数前缀和一个具体的元素值组成,其中前缀由一个英文句点( . )分隔。整数前缀表示具体的元素值的Unicode字符的数量,字符由UTF-8编码:
LENGTH.VALUE
若干条完整的指令组成一条消息,该消息从客户端发送到服务器,或者从服务器发送到客户端。客户端到服务器的指令通常是控制指令(用于建立连接或断开连接)和事件(鼠标事件和键盘事件)。服务器到客户端的指令通常是将客户端用作远程显示器的绘制指令(缓存,剪切,绘制图像)。
例如,将显示尺寸设置为1024*768的完整有效的指令是:
4.size,1.0,4.1024,3.768;
对于这个指令,会被服务器解析为四个元素:“size” ,作为size指令的操作码,“0”,是图像默认层的索引,“1024”,为所需的像素宽度,“768”,为所需的像素高度。
正因为Guacamole协议的设计方式,使得它可以流式传输协议,同时也可以很容易地被JavaScript解析。
JS确实原生支持类似于XML、JSON这样格式的信息,但是这类格式的信息都不能以流的形式传输。JS在解析这类格式的信息前必须接收到完整的XML或者JSON的包,而guacamole协议的信息,却可以一边接收一边解析。它的指令内每个元素的长度前缀使得解析器不用遍历每个字符就可以完成指令之间的跳转。
握手的过程是guacamole协议建立连接的过程。当客户端发送“select”指令时,握手阶段就开始了,select指令告诉服务器要加载哪个协议:
6.select,3.vnc;
收到“select”指令后,服务器会加载对应的客户端组件,并且回复一个“args”指令,这个指令指明了服务器端需要的参数列表
4.args,13.VERSION_1_1_0,8.hostname,4.port,8.password,13.swap-red-blue,9.read-only;
其中的协议版本用于协商客户端和服务器的不同版本之间的兼容性,从而允许双方协商最高支持的版本并启用或禁用与该版本关联的功能。不支持该指令的旧版本的Guacamole客户端将无提示地将其忽略为空连接参数。有效协议版本如下:
版本号 | 对应参数值 | 描述 |
---|---|---|
1.0.0 | VERSION_1_0_0 | 这是默认版本,适用于1.1.0之前的任何版本。协议的版本1.0.0不支持协议协商,并且要求握手指令以特定顺序传递,并且存在(即使为空)。 |
1.1.0 | VERSION_1_1_0 | 协议版本1.1.0引入了对协议版本协商,握手指令的任意顺序的支持,并支持在握手期间传递时区指令。 |
客户端接收到服务端可接受的参数列表后,需要回复给服务器,自己支持的音频(audio),视屏(video),图像(image),最佳屏幕尺寸(size)及时区(timezone),并在最后回复所有的服务器要求的参数的值(connect),即使是空,也要回复。任意要求没有满足,连接都将被关闭。客户端回复给服务端的消息如下:
4.size,4.1024,3.768,2.96;
5.audio,9.audio/ogg;
5.video;
5.image,9.image/png,10.image/jpeg;
8.timezone,16.America/New_York;
7.connect,13.VERSION_1_1_0,9.localhost,4.5900,0.,0.,0.;
如上,在客户端回复服务端的参数中,回复了三个0.给服务端,意味着客户端这三个参数为空,没有值,所以留空,长度为0,回复0.恰好。
在实际协议中,指令之间是紧挨着的不存在换行符,如果一条指令之后除了新指令的开始之外还有其他内容,则连接将关闭。
以下是握手过程中的指令的说明:
指令名称 | 描述 |
---|---|
audio | 客户端支持的音频编码解码器。在上面的例子中指定了audio/ogg作为支持的编码解码器 |
connect | 这是握手阶段的最后一个指令,它表明握手阶段已经结束,并且连接正常建立,可以继续进行。这条指令后续跟着服务器中args指令发送的连接参数的参数值。在上面的例子中,参数指定了在5900端口与localhost进行连接,后续三个参数值为空。 |
iamge | 客户端支持的图像格式,按首选项顺序。上例中的客户端同时支持PNG和JPEG。 |
timezone | 客户端的时区,采用IANA区域密钥格式。上例的时区是美国纽约 |
video | 客户端支持的视频编码解码器。上例的客户端是不支持任何视频编解码器。 |
客户端在握手中发送的指令的顺序是任意的,除了最后一条指令connect将结束握手并尝试开始连接。
客户端发送完这些指令后,服务器将尝试使用接收到的参数初始化连接,如果成功,则以“ready”指令进行响应。这条指令中包含新客户端连接的ID,并标记交互阶段的开始。这个ID是一个任意字符串,但是保证这个ID在所有活动链接以及受支持协议中是唯一的:
5.ready,37.$260d01da-779b-4ee9-afc1-c16bae885cc7;
当服务器发送ready后,真正的交互阶段就开始了。客户端和服务器端之间相互传递绘图和事件指令,直到关闭连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmgcnZVV-1610937685314)(/Users/wengcy/Library/Application Support/typora-user-images/image-20201230202507183.png)]
握手阶段完成后,如果通过“select”指令提供了ID而不是协议名称,则这个连接将被认为是活跃的并且是能被加入的。
6.select,37.$260d01da-779b-4ee9-afc1-c16bae885cc7;
加入现有连接的其余阶段与握手的阶段是相同的。与新连接一样,这次连接的其他参数由握手期间提供的参数值决定。
guacamole支持传输剪切板内容,音频内容,视频内容和图像数据,以及文件和任意的命名管道。
特殊语义的指令将会通过新分配的流传送。例如,用于播放媒体文件的“audio”或“video”指令。用于传输文件的“file”指令,用于在客户端和服务端传输任意数据的“pipe”指令。在某些情况下,将通过已命名的流传送的结构化集合对象的方式来显示指明流的能力范围和语义。
流一旦被创建,将通过“blob”指令一块一块地传送数据,通过“ack”来确认已收到的消息,流的结束通过一个“end”指令来标识。
命令解析参考官方文档与客户端源码,由一次真实的链接作为样例参考
在一次访问中遇到的,除了建立连接的指令外的指令
read请求:
4.size,1.0,4.1364,3.768;4.size,2.-1,2.11,2.16;3.img,1.3,2.12,2.-1,9.image/png,1.0,1.0;4.blob,1.3,232.iVBORw0KGgoAAAANSUhEUgAAAAsAAAAQCAYAAADAvYV+AAAABmJLR0QA/wD/AP+gvaeTAAAAYklEQVQokY2RQQ4AIQgDW+L/v9y9qCEsIJ4QZggoJAnDYwAwFQwASI4EO8FEMH95CRYTnfCDOyGFK6GEM6GFo7AqKI4sSSsCJH1X+roFkKdjueABX/On77lz2uGtr6pj9okfTeJQAYVaxnMAAAAASUVORK5CYII=;3.end,1.3;6.cursor,1.0,1.0,2.-1,1.0,1.0,2.11,2.16;4.size,2.-1,2.32,2.32;0.;
3.img,1.3,2.14,4.-885,9.image/png,1.0,1.0;4.blob,1.3,152.iVBORw0KGgoAAAANSUhEUgAAAI8AAACfAQMAAAD6exhLAAAABlBMVEUAAAA4bKCQgYqkAAAAJUlEQVRIie3IMQ0AAAgDsDnBv0ngxwFJezY1R0cppZRSSin1oBaBVZeq9v/dCwAAAABJRU5ErkJggg==;3.end,1.3;4.copy,4.-885,1.3,1.0,3.140,3.159,2.14,1.0,3.971,3.257;4.sync,11.14688328152;
4.rect,1.0,3.994,3.263,2.42,2.12;5.cfill,2.14,1.0,1.8,2.36,3.104,3.255;
5.error,18.Aborted. See logs.,3.520;7.dispose,3.-46;0.;
7.dispose,3.-28;7.dispose,3.-45;7.dispose,3.-37;7.dispose,2.-1;7.dispose,1.0;10.disconnect;
write请求:
3.ack,1.3,2.OK,1.0;
4.sync,11.14685868962;
3.nop;
5.mouse,3.702,2.16,1.0;
3.key,3.115,1.1;
说明:read请求中出现的命令为guacd向客户端发送的命令,write请求中出现的命令为客户端向guacd发送的命令。read请求,write请求均为客户端发出,指令分别出现在write请求的请求头的requestPayload部分和read请求的response部分。
设置指定图层的大小,拥有三个参数
layer:指定图层下标
width:指定图层宽度
height:指定图层高度
完整指令顺序:
size,layer,width,height;
样例:
4.size,1.0,4.1364,3.768;
作用:将图层0的大小设置为1364*768
分配一个新的流,并将其与图像更新的元数据相关联,包括图像类型,目标图层和目标坐标。图像的具体内容将由其后的Blob指令指定,因此img通常与Blob绑定使用。
img指令有六个参数
stream:要分配的流的索引
mask:绘制图像时要应用的通道蒙版(具体见最后通道蒙版表)
layer:图像生成的目标图层
mimetype:发送的图像的格式
x:目标图像左上角在目标图层中的X坐标
y:目标图像左上角在目标图层中的Y坐标
完整指令顺序:
img,stream,mask,layer,mimetype,x,y;
样例:
3.img,1.3,2.12,2.-1,9.image/png,1.0,1.0;
作用:通知客户端将要使用索引为3的流传输图像,绘图时使用12号通道蒙版,图像生成在-1号图层(负数图层在客户端中作为缓冲图册,存储各种缓冲图像),图像格式为
image/png,坐标为(0,0)
通过给定的流发送一组数据。数据长度任意,采用base64编码,该数据仅当通过指定流传输时才有意义。
blob指令有两个参数
stream:指定流的索引
data:要发送的base64编码数据
完整指令顺序:
blob,stream,data;
样例:
4.blob,1.3,232.iVBORw0KGgoAA…………(具体数据省略)
作用:通过索引为3的流传输数据
结束指定流,体现在客户端源码中为将指定索引流删除。
end指令有一个参数
stream:指定流的索引
完整指令顺序:
end,stream;
样例:
3.end,1.3;
作用:将索引为3的流关闭
将客户端的光标设置为具有指定热点的图层的指定矩形中的图像数据。有七个参数
X:光标热点的X坐标
Y:光标热点的Y坐标
srclayer:要复制图像的图层的索引
srcX:源图层内源矩形左上角的X坐标
srcY:源图层内源矩形左上角的Y坐标
srcWidth:源图层内源矩形的宽度
srcHeight:源图层内源矩形的高度
完整指令顺序:
cursor,X,Y,srcLayer,srcX,srcY,srcWidth,srcHeight;
样例:
6.cursor,1.0,1.0,2.-1,1.0,1.0,2.11,2.16;
作用:从-1图层(0,0)位置拷贝一个11*16的矩形作为光标图像生成在(0,0)位置
将图像数据从指定图层或缓冲区的指定矩形复制到另一个指定图层或缓冲区的其他位置。
copy指令有九个参数:
srclayer:源图层的索引
srcx:源图层内源矩形左上角的X坐标
srcy:源图层内源矩形左上角的Y坐标
srcwidth:源图层内源矩形的宽度
srcheight:源图层内源矩形的高度
mask:在目标图层上绘制图像数据时要应用的通道蒙版
dstlayer:要绘制图像的目标图层的索引
dstx:目标图层内目标图像的左上角的X坐标
dsty:目标图层内目标图像的左上角的Y坐标
完整指令顺序:
copy,srclayer,srcx,srcy,srcwidth,srcheight,mask,dstlayer,dstx,dsty;
样例:
4.copy,4.-885,1.3,1.0,3.140,3.159,2.14,1.0,3.971,3.257;
作用:
从索引为-885的图层中复制(3,0)位置的140*159大小的矩形到索引为0的图层的(971,257)位置,绘图时采用14号通道蒙版
服务器指示给定的时间戳是所有先前操作的当前时间戳。客户端必须响应收到的每个sync指令。
客户端和服务器均应偶尔发送sync指令以报告当前操作执行状态。如果客户端没有响应服务器的sync指令,服务器可能会停止发送更新,直到客户端赶上来。
sync指令只有一个参数:
timestamp:有效的服务器相对时间戳
完整指令顺序:
sync,timestamp;
样例:
4.sync,11.14688328152;
作用:指示当前时间戳为14688328152
将一个矩形路径添加到指定的图层
rect指令文档上有6个参数
mask:绘制图像数据时要应用的通道蒙版
layer:目标层
X:要绘制的矩形的左上角的X坐标
Y:要绘制的矩形的左上角的Y坐标
width:要绘制的矩形的宽度
height:要绘制的矩形的高度
客户端源码与网页请求数据中只有5个参数:
layer:目标层
X:要绘制的矩形的左上角的X坐标
Y:要绘制的矩形的左上角的Y坐标
width:要绘制的矩形的宽度
height:要绘制的矩形的高度
按照客户端源码与网页请求数据为准
指令完整顺序:
rect,layer,x,y,width,height;
样例:
4.rect,1.0,3.994,3.263,2.42,2.12;
作用:
在0号图层(994,263)位置绘制一个42*12的矩形
用指定的颜色填充当前路径。
cfill指令有六个参数
mask:在指定层中填充当前路径时要应用的通道蒙版
layer:要填充路径所在的图层
r:用于填充路径的颜色的红色分量
g:用于填充路径的颜色的绿色分量
b:用于填充路径的颜色的蓝色分量
a:用于填充路径的颜色的alpha分量(不透明度)
完整指令顺序:
cfill,mask,layer,r,g,b,a;
样例:
5.cfill,2.14,1.0,1.8,2.36,3.104,3.255;
作用:
使用14号蒙版填充0号图层中当前存在的路径,rgb颜色分量为(8,36,104),不透明度为255
通知客户端由于指定的错误即将关闭连接。服务器可以在任何阶段发送此消息。
error指令有两个参数
test:描述错误的消息
status:描述错误的guaca协议代码(具体见最后状态码表)
完整指令顺序:
error,test,status;
样例:
5.error,18.Aborted. See logs.,3.520;
作用:通知客户端发送520异常,具体原因查询日志。
删除指定的图层
dispose指令只有一个参数
layer:要删除的图层
完整指令顺序:
dispose, layer;
样例:
7.dispose,2.-1;
作用:
删除-1图层
通知客户端服务器即将关闭连接。该指令不带任何参数。
ack指令确认接收到的数据blob,并提供状态代码和消息,说明该blob关联的操作是成功还是失败。
ack指令有三个参数:
stream:接收相应blob的流的索引
message:提示消息
status:表示成功或失败的状态码
完整指令顺序:
ack,stream,message,status;
样例:
3.ack,1.3,2.OK,1.0;
作用:索引为3的流中的blob接收成功
客户端“ nop”指令不执行任何操作,没有任何参数,并且被Guacamole服务器忽略。它的主要用途是作为保持活动信号,并且在没有活动可确保套接字由于超时而没有关闭的情况下,可由Guacamole 客户端发送。
发送指定的鼠标移动或按钮按下或释放事件
mouse指令有三个参数
X:鼠标指针的当前X坐标。
Y:鼠标指针的当前Y坐标。
mask:按钮掩码,代表每个鼠标按钮的按下或释放状态
按钮掩码:
0-未按下鼠标
1-按下鼠标左键
2-按下鼠标中键
4-按下鼠标右键
8-鼠标滚轮向上
16-鼠标滚轮向下
完整指令顺序:
mouse,x,y,mask;
样例:
5.mouse,3.702,2.16,1.0;
作用:
鼠标移动到坐标(702,16)位置,未按下任何键
发送指定的按键按下或释放事件
keysym:按下或释放的键对应的X11值
pressed:如果未按下该键,则为0;如果按下该键,则为1。
完整指令顺序:
key,keysym,pressed;
样例:
3.key,3.115,1.1;
作用:
按下115对应的键(S键)
guacamole协议通过img指令建立一个流,用于以PNG,JPEG或者WebP的格式传送的图像数据。根据使用的格式的不同,通过这种方式传送的图像更新数据可能以RGB或者RGBA(A代表透明度)编码,如果通过libguac传送的话,还会被自动调色。
图像数据还可以被发送到任意指定的矩形、图层或者缓存。将图像数据发送到图层意味着立刻可见,将图像数据发送到缓存意味着可以在将来被重用。
图像数据可以在图层或者缓存之间拷贝,这在屏幕滚动的时候很有用(屏幕滚动的时候,更新的图像经常与之前的图像完全一样),在缓存某个部分的图像的时候,也很有用。
guacamole吸收了RDP和VNC中拷贝屏幕区域数据的概念,并将之进一步发展,将屏幕可见(图层)与屏幕不可见(缓存)的存储统一起来。使用copy指令可以拷贝一个矩形的图像数据,并且可以将其放置到其他任意图层包括缓存中。
guacamole里的每个绘制操作都会作用到一个具体的图层,每个图层都有一个唯一的编号来标识它自身。图层编号是负数的时候,这个图层是不可见的,可以用于存储或者缓存图像数据。此时,图层通过编码被引用,且等同于文档中所谓的缓存,当通过某个指令引用图层的时候,图层会被自动创建。
0号图层会被当作默认图层,调整这个图层大小的时候就会调整整个远端屏幕大小。其他图层创建时候的初始大小与默认图层的大小一致。缓存的初始大小为0x0,并且会自动调整大小来适配装入的内容。
非缓存图层可以在其他图层中被移动或者嵌套。通过这种方式,提供了一种简单的硬件加速合成图像的方式。如果你需要一个窗口浮现在另外的窗口之上,或者你想要移除一些对象,又或者你想要自动保存一些对象之下的图像数据,图层是实现这些需求较好的方式。如果一个图层嵌套在其他图层里,它的位置是相对于父图层的。当父图层被移动或者重排序(调整图层之间的顺序)的时候,子图层会随之移动以及重排序。如果子图层超出了父图层的边界,字图层将被裁切。
当客户端与guacd成功建立连接后,客户端将开始进行图像绘制,图像刷新主要以img指令配合blob指令为主。guacamole协议通过img指令建立一个流,随后blob指令沿流开始传输数据(base64格式),客户端收到数据解析后就将按照img指令指定的绘制方式(通道蒙版样式)将图形绘制在指定的图层上。在视频播放(存疑,实际与官方文档有出入)时,同理guacd不断向客户端沿流传输img指令+blob指令的组合,客户端收到指令后进行解析绘制,只要流中一直传输img指令+blob指令,视频就将播放下去。guaca采用局部刷新的方式,
copy与cfill指令也参与图像刷新,copy指令通常是从缓冲图层获取数据,常见于屏幕滚动, 光标变化时刷新;cfill指令则配合rect指令负责填充指定矩形。
Code | Name | Description |
---|---|---|
0 | SUCCESS | 操作成功。OK |
256 | UNSUPPORTED | 请求的操作不受支持 |
512 | SERVER_ERROR | 发生内部错误,无法执行操作 |
513 | SERVER_BUSY | 由于服务器繁忙,无法执行该操作 |
514 | UPSTREAM_TIMEOUT | 上游服务器没有响应。在大多数情况下,上游服务器是远程桌面服务器。 |
515 | UPSTREAM_ERROR | 上游服务器遇到错误。 |
516 | RESOURCE_NOT_FOUND | 找不到关联的资源,例如文件或流,因此操作失败。 |
517 | RESOURCE_CONFLICT | 资源已被使用或锁定,从而阻止了所请求的操作。 |
518 | RESOURCE_CLOSED | 请求的操作无法继续,因为相关的资源已关闭。 |
519 | UPSTREAM_NOT_FOUND |
上游服务器似乎不存在,或者无法通过网络访问。 |
520 | UPSTREAM_UNAVAILABLE | 上游服务器拒绝服务连接。 |
521 | SESSION_CONFLICT | 上游服务器内的会话已结束,因为它与另一个会话发生冲突。 |
522 | SESSION_TIMEOUT | 上游服务器内的会话已结束,因为它似乎处于非活动状态。 |
523 | SESSION_CLOSED | 上游服务器中的会话已被强制关闭。 |
768 | CLIENT_BAD_REQUEST | 请求的参数无效或无效。 |
769 | CLIENT_UNAUTHORIZED | 权限被拒绝,因为该用户尚未登录。 |
771 | CLIENT_FORBIDDEN | 权限被拒绝,登录将无法解决问题。 |
776 | CLIENT_TIMEOUT | 客户端花费的时间太长而无法反映 |
781 | CLIENT_OVERRUN | 客户端发送的数据超出了协议允许的范围。 |
783 | CLIENT_BAD_TYPE | 客户端发送了意外或非法类型的数据。 |
797 | CLIENT_TOO_MANY | 客户端已经使用了太多资源。在允许进一步的请求之前,必须释放现有资源。 |
Code | Name | Description |
---|---|---|
0x00 | CLear | 在目标图层中清空所有已经存在的图像数据。 |
0x01 | B in A | 在源图层不透明的地方绘制目标图层,清空所有的源图层透明以及目标图层透明的地方。 |
0x02 | B out A | 清除目标图层中源图层不透明的部分,而且不会绘制任何东西。可用于遮盖。 |
0x03 | B | 不做任何操作 |
0x04 | A in B | 在目标图层不透明的地方绘制源图层,清空所有的源图层透明以及目标图层透明的地方。 |
0x05 | A xnor B | 将目标图层与源图层不透明的地方相加起来。清空所有目标图层与源图层透明的地方。这与A+B相似,除了透明的地方要清空以外。 |
0x06 | A atop B | 在目标图层中填充源图层中不透明的部分。 |
0x07 | (A+B)atop B | 在目标图层不透明的地方绘制源图层,并保留目标图层余下的地方。 |
0x08 | A out B | 在目标图层透明的地方画源图层,清空所有的源图层不透明以及目标图层不透明的地方。 |
0x09 | B atop A | 在源图层不透明的地方填充目标图层。 |
0x0A | A xor B | 与逻辑上的xor操作一样。但是这是图像合成操作,不是位运算,实质含义是在目标图层的透明部分绘制源图层,在源图层的透明部分绘制目标图层。 |
0x0B | B over A | 与你期望的常规绘制相反。源图层出现在目标图层透明的地方。如同你将目标图层绘制在源图层上,而不是反过来。 |
0x0C | A | 填充源图层,忽略目标图层。 |
0x0D | (A+B)atop A | 在源图层不透明的地方绘制目标图层,并且复制源图层余下的地方。 |
0x0E | A over B | 最常见的合成操作,在目标图层绘制全部的源图层,除了源图层透明的地方。 |
0x0F | A + B | 将源图层与目标图层相加起来,并将结果填充到空白的画布上。 |