7

Socket.IO 小坑总结

 8 months ago
source link: https://liqiang.io/post/small-tips-for-socketio-ff69bef2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Socket.IO 小坑总结

@POST· 2023-09-22 08:00 · 23 min read

最近在接手一个 Web 推送的网关,主要功能其实就是通过 Socket.IO 协议接收前端的连接,然后推送消息给对应的前端,但是,在调试的时候,遇到了一个不能成功连接的问题,这里做一个总结。

本文使用的 golang lib 为:https://github.com/googollee/go-socket.io
node lib 为:https://github.com/socketio/socket.io

具体的问题就是,当我用 Postman (是的,没错,Postman 开始支持 Websocket/Socket.IO 的功能了)连接我们的 Gateway 时,发现处于一直 Connecting 的状态,如图:

图 1:Socket.IO 处于 Connecting 状态
ce5db7040282.png

然后同事发现,如果我去掉 URL 中的 query param 就可以成功连接了:

图 2:去除 query param 后成功连接
6d2745c8a909.png

发现去除 query param 可以正常连接之后,我就感觉不应该是我的问题,但是,出于严谨,还是得一步一步地来,首先需要排除是我的业务代码有问题,于是我就用对应版本的 Golang Socket.IO 库写了一个最简单的 Hello World 程序,然后发现能复现问题,ok 这个就排除了我的业务代码的问题。

然后我看了一下,目前项目使用的版本还比较旧(1.0.1),然后就升级到最新版本(1.7.0),发现接口都变了(这个值得吐槽一下啊,同个大版本你居然变接口?),但是没关系,就修改一下 Hello World 的代码,然后再用 Postman 试一遍,发现不能复现,也就是说最新的版本是正常的。

处于对同个大版本修改接口的不信任,我尝试用官方原生的代码来尝试一下,看下标准的协议是如何实现的(Socket.IO 官方没有明确的相关文档说明),于是我就用了 Node Socket.IO 的 2.5.0 版本,发现同样的也是无法复现,也就是正常连接,那么最终可以认定是项目中使用的这个版本有 bug。

既然知道是使用的版本代码有问题,那么就直接跟踪一下代码,发现问题出在 Connect 的数据包处理阶段,Server 端看上去是正常处理了,但是,返回给 Postman 之后,Postman 却不认为正常,从而忽略了这个 Connect 的响应包,从而导致连接的状态一直处于 Connecting 中。

从代码中,可以看到问题代码在这里



  1. [[email protected]]# cat parser.go
  2. if next[0] == '/' {
  3. path, err := reader.ReadBytes(',')
  4. if err != nil && err != io.EOF {
  5. return err
  6. }
  7. pathLen := len(path)
  8. if pathLen == 0 {
  9. return fmt.Errorf("invalid packet")
  10. }
  11. if err == nil {
  12. path = path[:pathLen-1]
  13. }
  14. v.NSP = string(path)
  15. if err == io.EOF {
  16. return nil
  17. }
  18. }

Golang 的这个实现在解析 Connect 数据包的时候,Namespace 直接就使用请求的 Path,注意,这里的 Path 是原始未处理的,所以它是带 query param 的,也就是说它将 query param 也作为 namespace 的一部分;但是对于标准的 Node 实现,它的 Namespace 却是解析后的 URL Path,并且去除了 query param 的,源码为这里



  1. [[email protected]]# cat lib/client.js
  2. Client.prototype.ondecoded = function(packet) {
  3. if (parser.CONNECT == packet.type) {
  4. this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query);
  5. } else {
  6. var socket = this.nsps[packet.nsp];
  7. if (socket) {
  8. process.nextTick(function() {
  9. socket.onpacket(packet);
  10. });
  11. } else {
  12. debug('no socket for namespace %s', packet.nsp);
  13. }
  14. }
  15. };

可以发现这里解析 Namespace 的方式为:url.parse(packet.nsp).pathname,不包含 Query Param。所以 Golang 和 Node 的差异就在这里了。

首先我的第一个疑问就是我们的前端是可以正常连接带 Query Param 的,如果后端有这个问题的话,那么前端是不是也是可以处理这个问题?还是得从代码中寻找原因:源码,然后事实就是 Client 段的代码也是有特殊处理这段逻辑的。

那么 Postman 为什么不能正确处理?这个因为 Postman 的源码没有开源,从文档中也没有看到它使用的是开源的 Library 还是自己实现的,但是从问题上来看,应该也是不能正确地处理 Namespace 的问题。所以我创建了一个 ticket 来跟踪后续。

  • 关键词:Socket.IO; Golang; Namespace; Query Param; Postman; Connecting; 无法连接

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK