Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ssl websocket 在微信小程序手机端连续发送数据不能立即接收 #5584

Open
xmzhy opened this issue Nov 23, 2024 · 10 comments
Open

Comments

@xmzhy
Copy link

xmzhy commented Nov 23, 2024

  1. What did you do? If possible, provide a simple script for reproducing the error.

发生问题的场景是:在微信小程序手机端使用mqtt.js连接swoole websocket服务器,发送连接报文时卡住,卡在MQTT那个位置,等到前端关闭连接,才会一起收到数据。通过Nginx代理swoole可以正常接收。已排除手机端问题,排除SSL证书问题,尝试过不同的Swoole版本问题依旧。用电脑端的其他工具测试均正常,问题目前只出在微信手机端。
test.php内容如下:

<?php
/**
 * test.php --
 */

$server = new Swoole\WebSocket\Server("0.0.0.0", 50000, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$server->set(array(
    'open_websocket_protocol' => true,
    'open_websocket_close_frame' => true,
    'websocket_subprotocol' => 'mqtt',
    'ssl_protocols' => SWOOLE_SSL_TLSv1_2 | SWOOLE_SSL_TLSv1_3 | SWOOLE_SSL_TLSv1_1,
    'ssl_ciphers' => 'HIGH:!aNULL:!MD5',
    'ssl_cert_file' => '/cert/ssl.pem',
    'ssl_key_file' => '/cert/ssl.key',
));
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
    echo "server: handshake success with fd{$request->fd}\n";
});

$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    $b =bin2hex($frame->data);
    echo "receive from {$frame->fd}:{$frame->data},{$b},opcode:{$frame->opcode},fin:{$frame->finish}\n";
});

$server->on('close', function ($server, $fd) {
    echo "client {$fd} closed\n";
});

$server->start();

以下内容是在微信小程序手机端模拟 mqtt.js 的连接报文连续发送数据,代码是微信小程序页面的js文件

let socket = null;
Page({
  data: {},
  onLoad() {
    // 开始连接 WebSocket
    this.connectWebSocket();

    // 设置一个定时器,为了模拟测试,每5秒后关闭连接并重新连接
    setInterval(() => {
      if (socket) {
        socket.close({
          success: () => {
            console.log("WebSocket connection closed by timer");
            setTimeout(this.connectWebSocket, 2000); // 重新连接 WebSocket
          },
        });
      }
    }, 5000);
  },

  connectWebSocket() {
    socket = wx.connectSocket({
      url: "wss://your_address:50000", // 替换为你的 WebSocket 服务器地址
      protocols: ["mqtt"],
    });

    // 监听 WebSocket 打开事件
    socket.onOpen(() => {
      console.log("WebSocket connection opened");
      this.startSendingData();
    });

    // 监听 WebSocket 关闭事件
    socket.onClose(() => {
      console.log("WebSocket connection closed");
    });
  },

  // 通过 WebSocket 发送这些数据
  startSendingData() {
    // 模拟mqtt.js构建MQTT CONNECT报文的各个部分
    const header = new Uint8Array([0x10]);
    const remainingLength = new Uint8Array([0x1d]);
    const protocol = new Uint8Array([0x00, 0x04]);
    const protocolName = this.encodeStringToUint8Array("MQTT");
    const protocolLevel = new Uint8Array([0x04]);
    const connectFlags = new Uint8Array([0x00]);
    const keepAlive = new Uint8Array([0x00, 0x3c]);
    const clientId = this.encodeStringToUint8Array("mqtt_" + Math.random().toString(16).slice(3));

    // 将各个部分合并成一个数组
    const mqttConnectPacket = [header, remainingLength, protocol, protocolName, protocolLevel, connectFlags, keepAlive, clientId];

    let index = 0;

    function sendNextBuffer() {
      if (index < mqttConnectPacket.length) {
        socket.send({
          data: mqttConnectPacket[index].buffer,
          success: () => {
            console.log(index + " Data sent successfully");
            index++;
            setTimeout(sendNextBuffer, 0);
          },
          fail: (err) => {
            console.error("Failed to send data", err);
          },
        });
      } else {
        console.log("All data sent");
      }
    }
    sendNextBuffer();
  },

  encodeStringToUint8Array(str) {
    const utf8 = [];
    for (let i = 0; i < str.length; i++) {
      let charcode = str.charCodeAt(i);
      if (charcode < 0x80) utf8.push(charcode);
      else if (charcode < 0x800) {
        utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f));
      } else if (charcode < 0xd800 || charcode >= 0xe000) {
        utf8.push(
          0xe0 | (charcode >> 12),
          0x80 | ((charcode >> 6) & 0x3f),
          0x80 | (charcode & 0x3f)
        );
      } else {
        i++;
        charcode =
          0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
        utf8.push(
          0xf0 | (charcode >> 18),
          0x80 | ((charcode >> 12) & 0x3f),
          0x80 | ((charcode >> 6) & 0x3f),
          0x80 | (charcode & 0x3f)
        );
      }
    }
    return new Uint8Array(utf8);
  },
});
  1. What did you expect to see?

正常应该是立即收到数据打印
server: handshake success with fd1
receive from 1:,10,opcode:2,fin:1
receive from 1:,1d,opcode:2,fin:1
receive from 1:,0004,opcode:2,fin:1
receive from 1:MQTT,4d515454,opcode:2,fin:1
receive from 1:,04,opcode:2,fin:1
receive from 1:,00,opcode:2,fin:1
receive from 1:<,003c,opcode:2,fin:1
receive from 1:,0011,opcode:2,fin:1
receive from 1:mqtt_1f8b549f69e1,6d7174745f316638623534396636396531,opcode:2,fin:1

  1. What did you see instead?

立即收到打印的内容
server: handshake success with fd1
receive from 1:,10,opcode:2,fin:1
receive from 1:,1d,opcode:2,fin:1
receive from 1:,0004,opcode:2,fin:1
receive from 1:MQTT,4d515454,opcode:2,fin:1
到这里卡住

等前端关闭连接才打印
receive from 1:,04,opcode:2,fin:1
receive from 1:,00,opcode:2,fin:1
receive from 1:<,003c,opcode:2,fin:1
receive from 1:,0011,opcode:2,fin:1
receive from 1:mqtt_1f8b549f69e1,6d7174745f316638623534396636396531,opcode:2,fin:1
receive from 1:,,opcode:8,fin:1
client 1 closed

  1. What version of Swoole are you using (show your php --ri swoole)?

Swoole => enabled
Author => Swoole Team [email protected]
Version => 5.1.5
Built => Nov 15 2024 11:14:07
coroutine => enabled with boost asm context
epoll => enabled
eventfd => enabled
signalfd => enabled
cpu_affinity => enabled
spinlock => enabled
rwlock => enabled
openssl => OpenSSL 1.1.1k FIPS 25 Mar 2021
dtls => enabled
http2 => enabled
json => enabled
curl-native => enabled
pcre => enabled
zlib => 1.2.11
brotli => E16777222/D16777222
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled
async_redis => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.enable_library => On => On
swoole.enable_fiber_mock => Off => Off
swoole.enable_preemptive_scheduler => Off => Off
swoole.display_errors => On => On
swoole.use_shortname => On => On
swoole.unixsock_buffer_size => 8388608 => 8388608

  1. What is your machine environment used (show your uname -a & php -v & gcc -v) ?

Linux localhost.localdomain 4.19.91-25.1.an8.x86_64 #1 SMP Wed Dec 22 16:48:46 CST 2021 x86_64 x86_64 x86_64 GNU/Linux
PHP 8.2.25 (cli) (built: Nov 9 2024 21:58:33) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.25, Copyright (c) Zend Technologies

@NathanFreeman
Copy link
Member

NathanFreeman commented Nov 23, 2024

这个看起来像是微信端发送了一部分数据,然后等待websocket服务器发送响应过来才会继续发送下一部分的数据。或者是服务端这边还在等微信端发送完整的数据过来。
我不是很了解mqtt, @sy-records 你能帮忙分析一下这个issue吗

@sy-records
Copy link
Member

抓个包看看,换一下你的网卡和端口号

tcpdump -i eth0 port 1883 -w mqtt.pcap

复现以后把 mqtt.pcap 提上来

@xmzhy
Copy link
Author

xmzhy commented Nov 24, 2024

抓个包看看,换一下你的网卡和端口号

tcpdump -i eth0 port 1883 -w mqtt.pcap

复现以后把 mqtt.pcap 提上来

mqtt.pcap.zip

@sy-records
Copy link
Member

你这个少 connect 包,就去发布订阅了

@xmzhy
Copy link
Author

xmzhy commented Nov 24, 2024

已经注释掉了订阅相关
mqtt.pcap.zip

@sy-records
Copy link
Member

你已经实现了 connect ack,这个代码不是复现代码吧?

@xmzhy
Copy link
Author

xmzhy commented Nov 24, 2024

mqtt.js 发送的连接包会分成多次websocket包发送, connect ack在这里显示的不准,connect ack应该是服务器发出,但是这里的方向反了。Src: 192.168.1.239是手机,Dst: 192.168.100.51是服务器。手机端发送连接超过5秒没有回应就会关闭连接,3秒后重新连接,从网卡收到数据时间来看手机发送是没有问题的。抓包用的是正常的服务器和手机端的mqtt.js。为了排除干扰方便测试,上面提供的测试代码也能模拟出问题情况。目前卡在receive from 1:MQTT,4d515454,这里。经过初步测试这里只要是3到7个字节就会卡住,试过2个或者8个字节(或以上)就不卡,或者前面少发一次websocket数据也不会卡。不用ssl也不卡,没有地方报错,唯一报错就是服务器收到完整连接报文时要回connect ack,但这个时候连接是已经关闭了。

@NathanFreeman
Copy link
Member

没有用ssl会有这个问题吗

@sy-records
Copy link
Member

我早上没 ssl 测试,没问题。
得搞个证书试试

@xmzhy
Copy link
Author

xmzhy commented Nov 24, 2024

没有ssl没问题,用Nginx代理也正常。有证书的情况下,电脑端微信开发者工具上,或者其他的mqtt工具都没问题。就手机上有问题。安卓、iPhone都一样

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants