socket.io-client-csharp icon indicating copy to clipboard operation
socket.io-client-csharp copied to clipboard

断线重连时报错未将对象引用设置到对象的实例。

Open beiguancyc opened this issue 3 years ago • 9 comments

不好意思,英文太烂,只能用中文提issue了。 最近在测试3.0时,发现有个问题,想向您请教。

服务端是node "socket.io": "^4.0.1", 我的代码使用如下

socketClient = new SocketIO(host, new SocketIOOptions
            {
                Query = new Dictionary<string, string>
                {
                    {
                        "token","token"
                    }
                }
            });           
            socketClient.ClientWebSocketProvider = () => new ClientWebSocketManaged(); 
            await socketClient.ConnectAsync();

重连的代码也是参考您的

socketClient.OnDisconnected += async (sender, e) =>
            {
                while (true)
                {
                    try
                    {
                        await socketClient.ConnectAsync();
                        break;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        await Task.Delay(1000);
                    }
                }
            };

我的测试方法是在连接上服务端以后,就禁用网卡,过一段时间再启用网卡,测试是否会自动重连上。 经过多次测试,发现 如果断开的时间短,可以实现预期的重连效果

如果断开的时间太长,则会在OnDisconnected的await socketClient.ConnectAsync();报异常 引发的异常:“System.NullReferenceException”(位于 mscorlib.dll 中) 未将对象引用设置到对象的实例。

同时也可能发生其他状况 1.有时候可能会出现一台主机,会在服务端有两个连接 2.有时候网络通了以后,重连的时候会触发OnConnected,但是马上会触发OnDisconnected,再也无法连接成功。 3.有时候网络断了,无法触发OnDisconnected

希望我的测试能对您有些帮助,希望socket.io-client-csharp能越来越好

beiguancyc avatar Sep 17 '21 09:09 beiguancyc

感谢你的反馈,但是你的代码流程有部分问题。

目前库默认支持重新连接,因此你不需要手动重新连接,尝试删除这些代码:

                while (true)
                {
                    try
                    {
                        await socketClient.ConnectAsync();
                        break;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        await Task.Delay(1000);
                    }
                }

我不确定你所遇到的问题是否都与这有关

doghappy avatar Sep 17 '21 09:09 doghappy

我按照您的办法,把上述代码去掉后,确实可以。 但是还有另外一个发现,如果是服务端调用了socket.disconnect, 那么客户端则不会自动重连。 如果是这种情况,有办法可以自动重连吗?

beiguancyc avatar Sep 17 '21 13:09 beiguancyc

可以看一下这个文档: https://socket.io/docs/v4/client-api/#event-disconnect

OnDisconnect 事件处理函数的参数是一个字符串的值,可以称为 reason,在上述代码中,变量名是 e

客户端主动断开连接时,reason 是 io client disconnect,服务端断开连接时,reason 是 io server disconnect

这两种断开时,库不会重新连接,因此官方文档中给出示例了,if (reason === "io server disconnect") 同样的,在使用SocketIOClient 时,你需要有类似的操作:

socketClient.OnDisconnected += async (sender, e) =>
{
    if (e == "io server disconnect")
        await socket.ConnectAsync();
};

doghappy avatar Sep 18 '21 02:09 doghappy

我在OnDisconnected测试了下重置Query, socketClient.OnDisconnected += async (sender, e) => { if (e == "io server disconnect") dict["date"] = DateTime.Now.ToString("HH:mm:ss"); socketClient.Options.Query = dict; await socket.ConnectAsync(); }; 我发现服务端socket.handshake.query获取到的日期始终是初次连上的日期,重连上的Query没有更新 不知道是bug还是刻意如此设计?

beiguancyc avatar Sep 18 '21 05:09 beiguancyc

属于 bug,目前你可以这样管理Query:

socketClient.OnDisconnected += async (sender, e) =>
{
    if (e == "io server disconnect")
    {
        var sio = sender as SocketIO;
        // 通过操作来处理,比较麻烦
        sio.Router.QueryParams
    }
};

下一次更新,将会修复这个问题。感谢你的反馈。

doghappy avatar Sep 18 '21 06:09 doghappy

辛苦!下次更新测试验证后我会close。

beiguancyc avatar Sep 18 '21 06:09 beiguancyc

socketClient.OnDisconnected += async (sender, e) =>
{
    if (e == "io server disconnect")
        dict["date"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm");
        socketClient.Options.Query = dict;
        await socket.ConnectAsync();
};

更新到3.03后,这段代码在断开后不会再自动重连,多次测试每次都可以复现。

测试方法:

1.客户端在header里放置连接时间,连接到服务端成功后,禁用网卡,十分钟后启用网卡。启用网卡后,会执行重连。到这里一切正常。

2.因为是测试断联,所以我在服务端设置收到的header里的时间如果超过十分钟,就disconnect。因此步骤1中的重新连接会被服务端disconnect

3.客户端执行OnDisconnected,理想状态应该是header携带最新时间再次连接服务端。 但是在3.0.3中,客户端不会再次重连。 在3.0.2中,大部分情况会重连,偶尔发生不会重连的情况,同时有一定几率抛出异常:无法访问已释放的对象。

beiguancyc avatar Oct 01 '21 01:10 beiguancyc

可以提供一个可复现的 github repo 吗?只需要上述描述的必要代码即可

doghappy avatar Oct 11 '21 01:10 doghappy

好的,我先提供我测试用的代码,处理完手上这点事再提交个repo 我的客户端代码如下,分别使用3.0.3和3.0.2测试过。

 public async Task StartConnect()
        {
            var socketClient = new SocketIO(url);
            //兼容win7
            socketClient.ClientWebSocketProvider = () => new ClientWebSocketManaged();

            var jsonSerializer = new SocketIOClient.Newtonsoft.Json.NewtonsoftJsonSerializer();
            jsonSerializer.OptionsProvider = () => new JsonSerializerSettings
            {
                ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver
                {
                    NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()
                }
            };
            socketClient.JsonSerializer = jsonSerializer;

            var dict = new Dictionary<string, string>();
            dict["date"] = now;
            socketClient.Options.Query = dict;

            await socketClient.ConnectAsync();             

            socketClient.OnDisconnected += async (sender, e) =>
            {
                if (e == "io server disconnect")
                {
                    try
                    {
                        dict["date"] = now;
                        socketClient.Options.Query = dict;
                        await socketClient.ConnectAsync();
                    }
                    catch (Exception)
                    {

                    }                    
                } 
            };           
        }

服务端代码用的是node

socketio.on("connection", async (socket: Socket) => {
  socket.on('disconnect', function (reason) {

  });
  let clientdate=socket.handshake.query["date"]
  if(){ //clientdate和服务端时间相差十分钟
    socket.disconnect(true)
  }  
});

beiguancyc avatar Oct 13 '21 03:10 beiguancyc