以下内容仅针对于编程爱好者,普通用户可忽略。

概述

Websocket是一种基于HTTP的实时通信协议。参考阮一峰的Websocket教程
Quicker 的 Websocket 服务提供了一个实时通信接口,方便使用者通过自己编写的HTML5网页或者小程序、APP等与Quicker通信,实现特定需求。

功能演示:TODO

Websocket客户端通过局域网直接连接的方式与Quicker通信,相对于推送服务连接方式,不需要经过服务器中转,网络延迟小(可实现类似于触摸板的鼠标控制),可用于传送较大量的数据(如传送文件),也可主动向客户端发送数据。只是websocket接口不能直接通过公网地址访问,使用这个协议也需要一定的专业知识。

设置

在设置窗口 -> 手机APP/WebSocket设置页面中,开启WebSocket服务,并根据需要修改默认端口和验证码。
image.png
参数:
启用Websocket服务: 是否开启服务。
端口: 服务端口号,为1-65535之间的数字。

  • 端口号不能其他网络服务冲突。
  • 您需要将Quicker加入到Windows防火墙白名单或者在防火墙设置中开启此端口的访问权限。

验证码: 建立Websocket连接后,经过验证码比对以后才可以进一步通信。可以避免在同一个网络中有多位Quicker用户时连错电脑。
启用安全连接(wss): 是否启用wss连接。类似于https相对于httpwss相对于ws是一种加密的安全连接方式。

如何建立安全连接

在浏览器中,以http方式访问的网页中只能建立非加密websocket连接(ws),以https方式访问的网页中只能建立加密的weboskcet连接(wss)。如果以 http 方式访问网页,一些 Javascript 脚本的权限会受到限制(如读取或写入剪贴板、访问摄像头等),为避免这些问题的出现,建议您使用 https 方式访问网页,同时以安全连接 wss 方式建立websocket连接。

使用安全连接方式时,您可以使用wss://转换后的本机ip地址.lan.quicker.cc:端口号/ws访问Quicker的Websocket服务(末尾的ws表示服务网址路径)。其中ip地址部分为您电脑的局域网ip地址中的点.替换为英文短横线-后的字符串。如电脑的局域网ip地址为192.168.1.56时,对应的websocket服务URI为:wss://192-168-1-56.lan.quicker.cc:668/ws

注:这里的域名只是ip地址的别名。就像不同的局域网会有相同的192.168.*.*的内网地址,您并不能通过这个域名访问到别人局域网里的ip,别人也不能通过这个域名访问到您的电脑。(域名解析是需要联网的,除非您在内网架设域名解析服务)

当不使用安全连接时,可以使用ws://ip地址:端口/ws的方式访问Quicker的Websocket服务,如ws://192.168.1.56:668/ws

内置的web服务
如果您编写了客户端网页,可以放置在我的文档\Quicker\_websocket\文件夹下,即可通过与websocket相同端口的网址访问(放置后需重启Quicker或websocket服务),如https://192-168-1-56.lan.quicker.cc:668/index.html

通信协议

通过文本格式向Quicker发送请求并获取响应。请求和响应都使用Json格式,消息参数与推送服务接近。

服务端和客户端:

  • 常开服务等待连接的是服务端,这里就是Quicker软件本体了。
  • 主动发起连接请求的网页、APP、小程序等。

通信过程:

  • 客户端发起Websocket连接。
  • 如果设置了验证码,客户端首先发送验证码消息。
    • 返回验证结果。
  • 如果未设置验证码,服务端(Quicker软件)直接发送验证成功消息。
  • 连接建立。
  • 双方根据需要发送消息和返回结果。

请求地址

非安全连接时:ws://电脑ip地址:设置的端口号/ws
安全连接时:wss://192-168-1-156.lan.quicker.cc/ws

客户 js 示例代码:

  1. let ip = txtIp.value;
  2. let port = txtPort.value;
  3. let _password = txtPassword.value;
  4. let uri = '';
  5. let protocol = '';
  6. // 根据网页是否是https连接,构造连接地址的组成部分
  7. if (isHttps) {
  8. protocol = 'wss';
  9. var ipstr = ip.replaceAll('.', '-'); // 将ip地址中的.替换为-
  10. uri = `${ipstr}.lan.quicker.cc:${port}`;
  11. } else {
  12. protocol = 'ws';
  13. uri = `${ip}:${port}`;
  14. }
  15. // 构造完整连接地址
  16. uri = `${protocol}://${uri}/ws`;
  17. // 建立连接
  18. try {
  19. socket = new WebSocket(uri);
  20. } catch (e) {
  21. console.log('connect to websocket failed.', e);
  22. showError(e.message);
  23. return;
  24. }
  25. setStateContent('连接中...');

同时,客户端开始监听websocket的各种事件:

  • open:连接建立
  • error:发生错误
  • close:连接断开
  • message:收到消息

消息结构

常规请求消息

  1. {
  2. messageType: 消息类型常量,
  3. serial: 消息编号,
  4. operation: '操作类型,如copy将data参数内容写入剪贴板',
  5. data: '数据,为文本,也可能为对象',
  6. action: '操作类型为action时指定执行的动作名称或id',
  7. extraData: '可选的额外数据,文本或对象',
  8. wait: 是否等待操作返回结果
  9. }

参数在不必要的时候可以省略。

常规响应消息

  1. {
  2. messageType: 消息类型常量,
  3. serial: 消息编号,
  4. replyTo: 响应的请求消息编号,
  5. isSuccess: 操作是否成功,
  6. message: 操作失败时的提示消息,
  7. data: 可选的返回数据(文本或对象),
  8. extraData: 可选的额外返回数据(文本或对象)
  9. }

消息类型常量
对应于消息中messageType参数的取值。

  • 2:命令请求消息,用于发送操作指令和内容。
  • 4:命令响应消息,返回指令操作结果。
  • 5:身份验证请求,客户端发送验证码。
  • 6:身份验证响应,返回密码是否正确。

身份验证

连接建立后,开始身份验证流程。
如果Quicker中未设置连接验证码(仅在家庭等安全环境中使用),则直接下发验证通过消息。

  1. // Quicker 发送给客户端的验证通过消息
  2. {
  3. messageType: 6,
  4. isSuccess: true, // isSuccess表示是否验证通过
  5. replyTo: 0
  6. }

如果设置了连接验证码,则需要客户端首先发送身份验证消息,服务端(Quicker软件)返回验证结果。
客户端示例代码(连接建立后主动发送验证请求消息):

  1. // 监听websocket连接事件,连接后如果设置了密码则发送请求消息
  2. socket.addEventListener('open', function(event) {
  3. setStateContent('<font color=green>已连接,待认证</font>');
  4. // 设置了密码,要发送密码消息
  5. if (_password) {
  6. sendMsg({
  7. messageType: 5, // 消息类型为5
  8. data: _password, // data中放置验证密码
  9. serial: getSerial()
  10. });
  11. }
  12. });

Quicker收到消息后对比验证码,返回结果。

  1. {
  2. messageType: 6, // 验证响应消息类型
  3. isSuccess: false, // isSuccess表示是否验证通过
  4. replyTo: 1,
  5. message: '验证码不正确'
  6. }

上行消息

这里指客户端发起请求,Quicker进行执行和响应的消息。
大部分与推送服务中支持的请求类型一致,只额外增加了传送文件支持。

请求消息

  1. {
  2. "messageType":2,
  3. "serial": 1000,
  4. "operation":"paste",
  5. "data":"Hello Quicker!Quicker真好玩!哈哈😄",
  6. "action":"动作名或ID",
  7. "wait":false
  8. }

参数说明

  • messageType:消息类型标识,请求消息为2.
  • serial:请求编号(不强制编号,可以直接写0)。
  • operation: 操作类型,请参考推送服务文档。除推送中的操作类型,另外支持sendfile(传送文件)、pasteimage(粘贴图片到当前窗口)操作,详见后续章节说明。
  • data:操作参数数据。
  • action:操作类型为action时,指定动作的id或名称。请参考推送请求消息格式。
  • wait:是否等待动作响应。

向Quicker传输文件

相对于推送服务,websocket直连不受带宽限制,可以用于传送文件(其他设备传送到Quicker)。具体协议如下:

  • 通过两条消息传送文件。
  • 第一条:文本消息,告知下一步要传送文件,以及文件的名称。
  • 第二条:二进制消息,发送文件内容。

文本消息格式:

  1. {
  2. "messageType":2,
  3. "serial": 1000,
  4. "operation":"sendfile", // sendfile 表示要传送文件
  5. "data":"文件名.png" // 文件名
  6. }

二进制消息:为文件内容字节数组。js参考代码:

  1. // 发送文件
  2. function sendFile() {
  3. var file = document.getElementById('theFile').files[0];
  4. if (!file) {
  5. console.log('没有文件。');
  6. return
  7. }
  8. if (file.size > 200000000) {
  9. alert('File should be smaller than 200MB')
  10. return
  11. }
  12. var filename = file.name;
  13. console.log('file name:', filename);
  14. // 发送第一条消息,传送文件名
  15. var msg = {
  16. messageType: 2,
  17. operation: 'sendfile',
  18. serial: msgSerial++,
  19. data: filename
  20. };
  21. socket.send(JSON.stringify(msg));
  22. // 发送第二条:二进制消息,传输文件数据
  23. var reader = new FileReader();
  24. var rawData = new ArrayBuffer();
  25. reader.loadend = function () {
  26. }
  27. reader.onload = function (e) {
  28. rawData = e.target.result;
  29. socket.send(rawData);
  30. console.log("文件已发送");
  31. }
  32. reader.readAsArrayBuffer(file);
  33. }

响应消息格式

  1. {
  2. "MessageType":4,
  3. "ReplyTo":0,
  4. "IsSuccess":true,
  5. "Data":"D:\\Docs\\Quicker\\_recv\\20220113_021127521_quicker.bin",
  6. "Message":"ok"
  7. }

参数:

  • MessageType,固定为4.
  • ReplyTo:响应的哪一条消息的Serial值。
  • IsSuccess:是否成功响应。
  • Data:返回的数据。
  • Message:错误消息。

下行消息

Quicker组合动作增加了 Websocket 模块,可用于向连接的客户端发送消息。
image.png

  • 向客户端发送文本消息:按规定的消息格式发送json文本内容。这里也可以通过表达式创建匿名对象,Quicker会自动序列化为文本。
    image.png
  • 向客户端发送文件(二进制方式):使用与“上行消息”中说明的两个消息一致的方式发送文件。(第一个消息发送文件名,第二个消息为二进制方式发送文件内容)。这种方式在iOS的浏览器中不支持。
  • 向客户端发送文件(Base64方式):将文件内容序列化为base64字符串后放入extData参数中一起发送。可支持iOS,只是传送的数据略大一些。

客户端消息处理参考代码:

  1. var msg = JSON.parse(data);
  2. // 密码验证结果消息,MSG_AUTH_RESP = 6
  3. if (msg.messageType == MSG_AUTH_RESP) {
  4. if (msg.isSuccess) {
  5. setStateContent('<font color=green>已连接</font>');
  6. document.getElementById('tests').classList.remove('d-none');
  7. } else {
  8. setStateContent('<font color=red>认证失败</font>');
  9. }
  10. } else if (msg.messageType === MSG_PUSH) { // MSG_PUSH = 2,表示常规命令消息
  11. if (msg.operation === 'sendfile') {
  12. if (!msg.extData) {
  13. // 消息中没有extData,表示是通过二进制方式发送的文件内容,
  14. // 需要等下一个消息接收二进制数据
  15. _nextFileName = msg.data;
  16. console.log('next file name:', _nextFileName);
  17. } else {
  18. // 有extData,它是文件内容的base64转换。
  19. let blob = base64toBlob(msg.extData);
  20. SaveBlobAs(blob, msg.data); // msg.data 为文件名。
  21. }
  22. }
  23. else if (msg.operation == 'copy') {
  24. // copy 命令,将data内容写入剪贴板。
  25. document.getElementById('txtData').value = msg.data;
  26. writeClipboard(msg.data);
  27. }
  28. }