gin中使用Socket.io踩的坑

最近在使用golang来搭建后台系统,框架使用非常著名的gin框架,由于在项目中需要使用socket.io进行长连接管理,官方推荐的golang服务端为 go-socket.io ,于是按照官方的文档示例很简单的就搭建起来了后台服务。 但是使用client 进行连接的时候就出现了各种问题,本文记录一下解决方案。

版本不兼容问题

我按照官方的示例 go-socket.io/main.go at master · googollee/go-socket.io · GitHub 来搭建后台,启动也很正常,由于没有golang的客户端,一般情况下我们是使用web与后台进行长连接,但是我的项目需要使用python 进行长连接,于是我又简单的写了一个python 客户端连接脚本. python使用的也是socketio推荐的 python-socketio

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: utf-8 -*-
import socketio

sio = socketio.Client()

def showmsg(msg):
    print(msg, 55555)

@sio.event
def connect():
    print("I'm connected!", sio.sid, 2222)
    sio.emit("hello", "yyx", callback=showmsg)


@sio.event
def connect_error(data):
    print("The connection failed!", data)

@sio.event
def disconnect():
    print("I'm disconnected!")

sio.connect('http://127.0.0.1:8000/', transports=["websocket"])
sio.wait()

脚本也很简单,就是在连接成功以后,打印 “I’m connected!”, 然后发送一个hello 事件,发送 “yyx” 字符串,在失去连接的时候,打印 “I’m disconnected!”.

运行脚本以后,一开始都还挺正常的,都按照预期的流程进行打印。

可是没过一会儿,就会打印出 “I’m disconnected!”!

虽然socketio会自动连接,但是在正常的业务中,一般是除了一些网络问题会导致断开,我们并不希望它自动断开,一开始我以为是偶发的,于是又观察了一会,还是会自动断开,我判断这可能不是由于网络原因导致的,可能是程序有什么bug!

于是我开始找规律,发现断开的时间是有规律了,每隔1分钟就会断一次,我猜想这个断开时间应该由某个参数导致的,但是代码都是官方的示例代码,我看了一下并没有什么设置的地方,于是我开始查看go-socket.io 的源码 我在初始化socket server 使用的是 server := socketio.NewServer(nil) , 查看这个函数的原型, func NewServer(opts *engineio.Options) *Server, 这里是需要传一个*engineio.Options 的配置信息的,上面传了一个 nil 进去,engineio.Options是个结构体,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type Options struct {
	PingTimeout  time.Duration
	PingInterval time.Duration

	Transports         []transport.Transport
	SessionIDGenerator session.IDGenerator

	RequestChecker CheckerFunc
	ConnInitor     ConnInitorFunc
}

这里继续探索源码发现,如果传入的是nil,那么各自都结构体都有相应的默认值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (c *Options) getPingTimeout() time.Duration {
	if c != nil && c.PingTimeout != 0 {
		return c.PingTimeout
	}
	return time.Minute
}

func (c *Options) getPingInterval() time.Duration {
	if c != nil && c.PingInterval != 0 {
		return c.PingInterval
	}
	return time.Second * 20
}

我敏感的觉得,自动断开可能和这两个timeout有关系,因为之前观察的现象是差不多1分钟就会断开一次,这里的PingTimeout 默认值就是一分钟,于是我尝试将这个时间缩短一下来验证一下。

1
2
3
4
5
var socketconfig = &engineio.Options{
		PingTimeout:  5 * time.Second,
		PingInterval: 10 * time.Second,
	}
	server = socketio.NewServer(socketconfig)

这时,再运行服务端与客户端,则此时就会每隔5秒钟断一次了! 关于PintTimeout与pingInterval的作用参考这篇文章

https://blog.vini123.com/358

终于找到了这个参数,但是要怎么解决自动断开的问题呢? 我总不能将这个PingTimeout 设置一个非常大的值吧? 那么它还是会每隔那么长的时间就会断一次,还是没有从根本上解决问题。

我又开始观察访问的url, http://127.0.0.1:8000/socket.io/?transport=websocket&EIO=4&t=1658570826.4926221

这里有个参数,EIO=4, 我之前在使用python 写服务端的时候,依稀记得这个socketio对于版本号很敏感,不同的版本之间是不兼容的。

我想这个EIO应该是指的Engine.IO 的版本号,这里应该是4的意思,我又查看了我使用python-socketio的版本,是5.2.1, 根据上面的表格这个就说的通了,我就在想是不是go-socket.io 的版本不对呢? 可是我下载的是最新的呀? 我在它的github上也没有看找到它用的是哪个版本的Engine.IO,也许是我找的不够仔细,算了,我先降一下python-socketio的版本试一下吧,于是我按照上面表格中的对应关系,使用4.x的python-socketio, pip install "python-socketio[client]"==4.6.1, 找了一个4.x中最高的版本4.6.1,其它的代码都没有改,这次居然可以了,不再每隔一定时间就断开了!

问题就这样的被解决了,没想到原来是go-socket.io还在使用3版本的Engine,后来我在它的issue中也有一个很显示的issue, Support Socket.IO v4 ,原来很早就有人提到了这个问题,但是作者本人答复就是4的版本变化太大,目前没精力改。。。

在网上搜索,其实很多问题都是由于版本不兼容导致的,在socketio中使用Engine.IO的版本相互不兼容,这个一定要注意!

跨域的问题

由于项目中还是需要使用web端来和后端进行长连接的,有了上面的经验,这次我使用了老版本的问题socketio.js,但是上来就报跨域问题了,依然还是查看源码,还是在初始化socketio服务器的时候,传入的 *engineio.Options 配置信息中,是有关于跨域的配置的 Transports

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var socketconfig = &engineio.Options{
		PingTimeout:  7 * time.Second,
		PingInterval: 5 * time.Second,
		Transports: []transport.Transport{
			&polling.Transport{
				Client: &http.Client{
					Timeout: time.Minute,
				},
				CheckOrigin: func(r *http.Request) bool {
					return true
				},
			},
			&websocket.Transport{
				CheckOrigin: func(r *http.Request) bool {
					return true
				},
			},
		},
	}
	server = socketio.NewServer(socketconfig)

这里对polling与websocket的Transport的CheckOrigin都进行的设置,全都返回true,当然具体的项目中可以根据实际情况进行单独的设置。

总结

本次的这两个问题解决起来都不复杂,但是排查起来有点困难,也走了不少弯路,像排查兼容性的问题,我一开始就没有往go-socket.io 支持的版本不是4这方面想,总想着我都用最新的了应该是支持4的吧。 遇到问题当发现网上的资料太少的时候,最后还得靠看源码来发现问题。