Beispiel #1
0
        private void OnConnectintOutTimeout(Action timeoutAction)
        {
            lock (deviceLocker)
            {
                //如果中间已经连接上了,又到了这里,就不要执行了。
                if (State == DeviceState.Connected || State == DeviceState.Error)
                {
                    return;
                }
                State = DeviceState.Error;
                ConnectingOutTimer?.Stop();
                ConnectingOutSocketTimer?.Stop();

                if (timeoutAction == null)
                {
                    //如果用户没有指定超时的行为,调用Device的默认的超时事件。
                    ConnectTimeouted?.Invoke(this);
                }
                else
                {
                    //如果有自定义超时,调用自定义超时。
                    timeoutAction();
                }
            }
        }
Beispiel #2
0
 private void TryAutoAccept(IChannel channel)
 {
     if (AppModel.Instance.LocalDevice.ID.CompareTo(ID) > 0)
     {
         ConnectingOutTimer?.Stop();
         //使用对方创建的channel.我自己创建的channel不要了。
         IsRequester = false; //需要设置为false,以后如果万一连接断开,总是由对方发起请求。
         SwitchChannel(channel);
         Accept();
     }
     else
     {
         //等待对方accept即可。如果两个人同时accept,就不知道该用哪个channel了。
     }
 }
 private void TryAutoAccept(IChannel channel)
 {
     // ReSharper disable once StringCompareToIsCultureSpecific
     if (SuperDriveCore.LocalDevice.Id.CompareTo(Id) > 0)
     {
         ConnectingOutTimer?.Stop();
         //使用对方创建的channel.我自己创建的channel不要了。
         IsRequester = false;                 //需要设置为false,以后如果万一连接断开,总是由对方发起请求。
         SwitchChannel(channel);
         Accept();
     }
     else
     {
         //等待对方accept即可。如果两个人同时accept,就不知道该用哪个channel了。
     }
 }
Beispiel #4
0
        /// <summary>
        /// 在限定的时间内,每隔两秒钟,不停的尝试链接,直到连接成功。如果超时,调用timeoutAction. 在给定时间内,不限次数的连接。
        ///
        /// 注意!! 调用Connect之前,如果Device的状态已经是ConnectingOut,这个函数会直接返回。如果想要强制重连,需要先将Device状态设置成Idle.
        /// </summary>
        /// <param name="timeoutMilliSeconds">允许尝试连接的时间(包括等待对方应答的时间)。</param>
        /// <param name="socketTimeoutMilliSeconds">建立socket最大等待时间。默认值=timeoutMilliSeconds。如果在这个事件范围内未成功建立连接,则报错</param>
        public void Connect(double timeoutMilliSeconds = 10000d, Action timeoutAction = null, double?socketTimeoutMilliSeconds = null)
        {
            if (State == DeviceState.ConnectingOut)
            {
                return;                                      //不要重复的发连接消息。一个连接失败后,状态会改变。但在尝试期间,不得重复。
            }
#if DEBUG
            //在测试情况下输入IP地址,或者通过扫描二维码,得到了这个设备,去主动连接这个设备时,底层的设备列表中并没有发现这个设备。
            if (ID == null)
            {
                ID = IPAddress;
            }
#endif
            Preconditions.Check(timeoutMilliSeconds > 0, "允许连接尝试时间必须大于0");
            Preconditions.Check(ID != null);

            //如果对方也在尝试连接我。
            //TODO BUG? 这个地方和安全冲突。如果对方要求安全密码在连接我,我同时点击它,就不会要求输入密码了,再另一方会认证失败。这样倒也能接受。
            if (State == DeviceState.ConnectingIn)
            {
                //TODO 这样做是否合适?
                Accept();
                return;
            }

            State         = DeviceState.ConnectingOut;
            socketCreated = false;
            double _socketTimeoutMilliSeconds = socketTimeoutMilliSeconds ?? timeoutMilliSeconds;
            ConnectingOutSocketTimer?.Stop();
            if (_socketTimeoutMilliSeconds != timeoutMilliSeconds)
            {
                ConnectingOutSocketTimer = Util.DoLater(() =>
                {
                    OnConnectintOutTimeout(timeoutAction);
                }, _socketTimeoutMilliSeconds);
            }

            //一旦启动连接后,启动一个计时器。如果中间连接成功,则清除这个计时器。
            ConnectingOutTimer?.Stop(); //如果以前曾经尝试连接,忽略以前的结果
            ConnectingOutTimer = Util.DoLater(() =>
            {
                OnConnectintOutTimeout(timeoutAction);
            }, timeoutMilliSeconds);

            IsRequester = true;
            //超时时间与上面的计时器时间相同,所以不在设置超时行为。
            AppModel.Instance.ChannelManager.CreateChannel(
                this
                , (channel) =>
            {
                SwitchChannel(channel);
                //TODO 为什么要延时发送?对方执行ChannelCreated, 并为Channel设置PacketReceivedEvent好像需要执行时间
                //如果在这期间发送了Connect消息,会被对方忽略。
                //TODO 重构 如果这个问题真的存在,可以让对方准备好之后回一个消息,这边收到消息之后再发送connect消息
                Util.RunLater(
                    () =>
                {
                    //如果是主动连接别人,发送一个连接消息。不要调用Device的Post函数。
                    //如果这里给别人发了链接消息,但是对方一直没理你,还是会导致超时。
                    SendConnectMessage();
                }
                    , 100d);
            }
                , null
                , timeoutMilliSeconds
                , (int)RETRY_INTERVAL
                );
        }
Beispiel #5
0
        //device本身不会直接收到connect消息,一个socket都是处理完connect消息,才和Device建立关联。
        internal void OnConnectMessageReceived(IChannel channel, ConnectMessage cm)
        {
            var sessionCode = cm.SessionCode;

            //对方发了一个SessionCode,表示以前可能连接过?
            if (!string.IsNullOrEmpty(sessionCode))
            {
                if (Crypto != null)
                {
                    sessionCode = Crypto.Decrypt(sessionCode);
                }

                //确实连接过。而且还没过期。自动同意这个设备。
                if (sessionCode == SessionCode)
                {
                    ConnectingOutTimer?.Stop();
                    //原来的channel应该是坏掉了,不然对方为什么要发Connect消息呢?所以换成对方创建的channel.
                    IsRequester = false;
                    SwitchChannel(channel);
                    //先换channel才能成功发送Accept消息。
                    Accept();
                    return;
                }
            }

            //如果没有SessionCode或者SessionCode不等于当前SessionCode.
            //启动全新连接流程。
            if (!string.IsNullOrEmpty(cm.ChallengeString))
            {
                //对方要求加密
                cm.ExtractToDevice(this);
                //其实验证还是可以在B这一端完成。因为A可以用它自己的密码加密它的密码。如果B输入的密码和解出来的密码一致,就认为认证成功,
                //并发送Accept消息给A, 最终还是要由A确认是否进入可通讯状态。所以改成那个方案意义不太大。
                //还是要有这么个字段,因为ChallengeRequest这个字段,在ConnectingIn的事件处理中,在Accept的时候要用。
                ChallengeRequest = cm.ChallengeString;
                IsRequester      = false;
                SwitchChannel(channel);
                bool shouldAskUserInput = true;

                //对方是否知道我的ConnectCode?
                if (!string.IsNullOrEmpty(cm.ConnectCode))
                {
                    //对方发来的ConnectCode是加密的
                    //我看一下是不是用我的密码加密的?我能判断是因为EncedConnectCode的原值就是ConnectCode(原始内容其实并不一定是ConnectCode,现在用了,只是为了方便)。
                    var ld = AppModel.Instance.LocalDevice;

                    string connectCode = null;
                    using (var sp = new SecurePassword(ld.ConnectCode))
                        using (var crypto = Env.Instance.SecurityManager.CreateCrypto(sp))
                        {
                            connectCode = crypto.Decrypt(cm.ConnectCode);
                        }

                    if (connectCode != null &&
                        connectCode == ld.ConnectCode)
                    {
                        if (Env.Instance.Config.PairedDevice != null &&
                            Env.Instance.Config.PairedDevice.ID != ID)
                        {
                            Reject(RejectMessage.ALLOW_ONLY_ONE_PAIR_DEVICE);
                        }
                        //对方知道ChannelCode,并正确的用这个ChannelCode给它自己的ID加了密。
                        ChannelCode = new SecurePassword(connectCode);
                        Accept();
                        Env.Instance.Config.PairedDevice = this;
                        Env.Instance.Config.Save();
                        shouldAskUserInput = false;
                    }
                    else
                    {
                        Reject(RejectMessage.CONNECT_CODE_INCORRECT);
                        return;
                    }
                }

                if (shouldAskUserInput)
                {
                    State = DeviceState.ConnectingIn;
                }
                return;
            }
            else
            {
                //对方不要求加密。
                this.ChallengeRequest = null;
                if (State == DeviceState.ConnectingOut)
                {
                    //如果是我去连接别人了,别人同时,或者稍后,给我发了ConnectMessage。
                    cm.ExtractToDevice(this);
                    TryAutoAccept(channel);
                }
                else if (State == DeviceState.ConnectingIn)
                {
                    //如果刚刚已经连接进来,并等待批准,为什么还要再连接?
                }
                else
                {
                    //可能是通过广播搜到的,也可能是别人拍了我的二维码主动连过来的,现在开始发起实际连接了,从前没有成功连接过。
                    cm.ExtractToDevice(this);//如果没有发现别人,别人直接连过来了,这个动作做了两次。没办法。本来在这里做最合适。但那里要汇报deviceDiscovered。
                    IsRequester = false;
                    SwitchChannel(channel);

                    var app    = AppModel.Instance;
                    var config = Env.Instance.Config;
                    //如果这个设备在信任设备列表中。
                    if (!string.IsNullOrEmpty(cm.ConnectCode) && cm.ConnectCode == app.LocalDevice.ConnectCode)
                    {
                        //如果这个设备所代表的对方发送了一个ConnectCode,而且这个ConnectCode与本机的ConnectCode一致,说明对方知道我的口令,自动同意连接。而且添加为信任设备
                        //TODO bug 这样还不够,应该从AppModel里面去取Device,然后再add或者replace,让所有的地方都只保留一个Device的引用。

                        //不过Device这个类应该是保证了它一定是AppModel里面的。因为在Connect之前已经检查了。
                        config.TrustDevices.AddOrReplace(this);
                        config.Save();
                    }

                    //如果上面成功添加了可信设备,这里就会自动接受了。
                    if (config.TrustDevices.FirstOrDefault(d => d.ID == this.ID) != null)
                    {
                        Accept();
                    }
                    else
                    {
                        //这会触发ConnectingIn事件,用户可以决定是否同意这个连接。
                        State = DeviceState.ConnectingIn;
                    }
                }
            }
        }
        //device本身不会直接收到connect消息,一个socket都是处理完connect消息,才和Device建立关联。
        internal void OnConnectMessageReceived(IChannel channel, ConnectMessage cm)
        {
            var sessionCode = cm.SessionCode;

            //对方发了一个SessionCode,表示以前可能连接过?
            if (!string.IsNullOrEmpty(sessionCode))
            {
                //确实连接过。而且还没过期。自动同意这个设备。
                if (sessionCode == SessionCode)
                {
                    ConnectingOutTimer?.Stop();
                    //原来的channel应该是坏掉了,不然对方为什么要发Connect消息呢?所以换成对方创建的channel.
                    IsRequester = false;
                    SwitchChannel(channel);
                    //先换channel才能成功发送Accept消息。
                    Accept();
                    return;
                }
            }
            else
            {
                //对方不要求加密。
                this.ChallengeRequest = null;
                if (State == DeviceState.ConnectingOut)
                {
                    //如果是我去连接别人了,别人同时,或者稍后,给我发了ConnectMessage。
                    cm.ExtractToDevice(this);
                    TryAutoAccept(channel);
                }
                else if (State == DeviceState.ConnectingIn)
                {
                    //如果刚刚已经连接进来,并等待批准,为什么还要再连接?
                }
                else
                {
                    //可能是通过广播搜到的,也可能是别人拍了我的二维码主动连过来的,现在开始发起实际连接了,从前没有成功连接过。
                    cm.ExtractToDevice(this);//如果没有发现别人,别人直接连过来了,这个动作做了两次。没办法。本来在这里做最合适。但那里要汇报deviceDiscovered。
                    IsRequester = false;
                    SwitchChannel(channel);

                    var app    = AppModel.Instance;
                    var config = Env.Instance.Config;
                    //如果这个设备在信任设备列表中。
                    if (!string.IsNullOrEmpty(cm.ConnectCode) && cm.ConnectCode == app.LocalDevice.ConnectCode)
                    {
                        //如果这个设备所代表的对方发送了一个ConnectCode,而且这个ConnectCode与本机的ConnectCode一致,说明对方知道我的口令,自动同意连接。而且添加为信任设备
                        //TODO bug 这样还不够,应该从AppModel里面去取Device,然后再add或者replace,让所有的地方都只保留一个Device的引用。

                        //不过Device这个类应该是保证了它一定是AppModel里面的。因为在Connect之前已经检查了。
                        config.TrustDevices.AddOrReplace(this);
                        config.Save();
                    }

                    //如果上面成功添加了可信设备,这里就会自动接受了。
                    if (config.TrustDevices.FirstOrDefault(d => d.ID == this.ID) != null)
                    {
                        Accept();
                    }
                    else
                    {
                        //这会触发ConnectingIn事件,用户可以决定是否同意这个连接。
                        State = DeviceState.ConnectingIn;
                    }
                }
            }
        }