多端登录互踢

更新时间: 2024/04/17 15:00:35

云信 IM 支持多客户端同时登录,同时支持设置客户端之间的互斥关系。您可以通过自动和手动两种方式实现 IM 的多端登录互踢。

前提条件

已实现登录 IM

实现多端登录互踢

方式1:多端登录自动互踢

云信控制台首页应用管理选择应用,再进入产品功能 > IM 即时通讯 > 基础功能 > 多端登录模式,配置四种不同的 IM 多端登录互踢策略。

多端登录模式.png

只允许一端登录

同一账号仅允许在一台设备上登录。当该账号在另一台设备上成功登录时,新设备会将旧设备踢下线。

桌面端互踢、移动端互踢、桌面与移动端同时登录

  • 同类客户端之间互踢。
  • 桌面端之间互踢,移动端之间互体,桌面端和移动端可同时在线。
  • 同一账号最多允许在 1 台桌面端设备(PC、Web)和 1 台移动端设备(iOS、Android)上同时登录并收发消息。

以下情况下新设备会将旧设备踢下线:

  • 已经有一台桌面端设备(PC、Web)在线,在另一台桌面端设备(PC、Web)上成功登录。
  • 已经有一台移动端设备(iOS、Android)在线,在另一台移动端设备(iOS、Android)上成功登录。

以下情况下新设备和旧设备可以共存:

  • 已经有一台移动端设备(iOS、Android)在线,在另一台桌面端设备(PC、Web)上登录。
  • 已经有一台桌面端设备(PC、Web)在线,在另一台移动端设备(iOS、Android)上登录。

各端均可以同时登录在线

各端均可以同时登录在线,最多可支持 10 个设备同时在线,在设备数上限内,所有的新设备再次登录,均不会将在线的旧设备踢下线。

自定义多端登录

配置自定义多端登录步骤如下:

  1. 在云信控制台选择自定义多端登录配置后,单击子功能配置。云信提供 5 种内置终端类型,分别是 iOS、AOS(安卓)、Mac、PC、Web,内置终端类型之间,如有互踢要求,可在对应的行列中勾选互踢,保存后方可生效。

    自定义多端登录.png

    例如:需要将 iOS 与 AOS 之间实现互踢,则在表格中第二行第三列处勾选互踢。如下图所示:

    互踢.png

  2. (可选)如果内置的终端类型不足以覆盖所有终端,支持添加新的终端类型。点击下方添加按钮,选择自定义终端类型,并以整数数字命名新的终端类型名称,单击确定即可添加自定义的终端类型。

    自定义终端.png

  3. (可选)如需删除某一类终端,可以勾选左侧复选框,点击删除去掉该类型。

    删除端.png

  4. 配置完成后,点击保存

如果在初始化时配置了自定义客户端类型(customClientType),则登录时遵循自定义多端登录逻辑。如果没有设置该字段,则认为是预定义的客户端类型(PC、AOS、PC、IOS、WEB)。

强制登录

在控制台配置多端互踢策略后,若登录失败或并返回 417 错误码(禁止多端登录),再次登录时您可以通过设置 V2NIMLoginOption.forceModetrue 来实现强制登录,将其他在线端踢下线。

实现流程图如下:

多端登录处理.drawio.png

示例代码如下(以静态登录为例):

Android
javaV2NIMLoginOption option = new V2NIMLoginOption();
option.setForceMode(true);

NIMClient.getService(V2NIMLoginService.class).login("accountId", "token", option, new V2NIMSuccessCallback<Void>() {
    @Override
    public void onSuccess(Void unused) {
        // TODO
    }
}, new V2NIMFailureCallback() {
    @Override
    public void onFailure(V2NIMError error) {
        int code = error.getCode();
        String desc = error.getDesc();
        // TODO
    }
});
iOS
objective-c- (id<V2NIMLoginService>)getV2LoginService
{
    return [[NIMSDK sharedSDK] v2LoginService];
}

- (void)login
{
    NSString *accountId = @"accountId";
    NSString *token = @"token";
    id<V2NIMLoginService> service = [self getV2LoginService];
    V2NIMLoginOption *option = [[V2NIMLoginOption alloc] init];
    option.forcemode = YES;
    [service login:accountId token:token
            option:nil
              success:^{
        NSLog(@"login succ");
    }
              failure:^(V2NIMError * _Nonnull error) {
        NSLog(@"login fail: error = %@", error);
    }];
}
macOS/Windows
C++V2NIMLoginOption option;
option.forceMode = true;

loginService.login(
    "accountId",
    "token",
    option,
    []() {
        // login succeeded
    },
    [](V2NIMError error) {
        // login failed, handle error
    });
Web/uni-app/小程序
typescripttry {
  await nim1.V2NIMLoginService.login("ACCOUNT_ID", "TOKEN", {
    "forceMode": true
  })  
} catch (err) {
  // TODO failed, check code
  // console.log(err.code)
}
Harmony
typescripttry {
  await nim.loginService.login("ACCOUNT_ID", "TOKEN", {
    forceMode: true
  } as V2NIMLoginOption)
} catch (err) {
  // TODO failed, check code
  // console.log(err.code)
}

方式2:多端手动互踢

调用 kickOffline 方法主动将当前登录的其他客户端踢下线,API 调用时序如下:

uml diagram

踢方操作

步骤1:注册多端登录监听

Android/iOS/Windows/macOS

调用 addLoginListener 方法注册登录状态监听器,监听其他在线客户端的登录状态变化(onLoginClientChanged)。本地端未登录时,如有其他端使用相同的 IM 账号登录或注销,本地端会收到通知;本地端登录成功后,当有其他端登录或者注销时,本地端也会收到通知。

Android
javaV2NIMLoginListener yourLoginClientChangedListener = new V2NIMLoginListener() {
    @Override
    public void onLoginStatus(V2NIMLoginStatus status) {

    }

    @Override
    public void onLoginFailed(V2NIMError error) {

    }

    @Override
    public void onKickedOffline(V2NIMKickedOfflineDetail detail) {

    }

    @Override
    public void onLoginClientChanged(V2NIMLoginClientChange change, List<V2NIMLoginClient> clients) {
        switch (change) {
            case V2NIM_LOGIN_CLIENT_CHANGE_LIST:
                // TODO
                break;
            case V2NIM_LOGIN_CLIENT_CHANGE_LOGIN:
                // TODO
                break;
            case V2NIM_LOGIN_CLIENT_CHANGE_LOGOUT:
                // TODO
                break;
        }
    }
};
NIMClient.getService(V2NIMLoginService.class).addLoginListener(yourLoginClientChangedListener);
iOS
objective-c@interface YourLoginClientChangedListener : NSObject <V2NIMLoginListener>

@end

@implementation YourLoginClientChangedListener

- (void)onLoginClientChanged:(V2NIMLoginClientChange)change
                     clients:(NSArray<V2NIMLoginClient *> *)clients
{
    switch (change) {
        case V2NIM_LOGIN_CLIENT_CHANGE_LIST:
            NSLog(@"other logined clients: %@", clients);
            break;
        case V2NIM_LOGIN_CLIENT_CHANGE_LOGIN:
            NSLog(@"other clients: %@ login", clients);
            break;
        case V2NIM_LOGIN_CLIENT_CHANGE_LOGOUT:
            NSLog(@"other clients: %@ logout", clients);
            break;
        default:
            NSLog(@"unknown change = %ld", change);
    }
}

@end

- (void)listenLoginClientChanged
{
    [[NIMSDK sharedSDK].v2LoginService addLoginListener:[[YourLoginClientChangedListener alloc] init]];
}
macOS/Windows
C++auto& instance = v2::V2NIMInstance::get();
auto& loginService = instance.getLoginService();
V2NIMLoginListener listener;
// listener.onLoginStatus
// listener.onLoginFailed
// listener.onKickedOffline
listener.onLoginClientChanged = [](V2NIMLoginClientChange change, nstd::vector<V2NIMLoginClient> clients) {
    // handle login client change
};
loginService.addLoginListener(listener);

Web/uni-app/小程序/Harmony

调用 on("EventName") 方法注册登录相关监听器,包括登录状态变化、登录失败、登录终端被踢、登录终端信息变更。

Web/uni-app/小程序
typescript//登录状态变化回调
nim.V2NIMLoginService.on("onLoginStatus", theListnerFn)
//登录失败回调
nim.V2NIMLoginService.on("onLoginFailed", theListnerFn)
//登录终端被其他端踢下线回调
nim.V2NIMLoginService.on("onKickedOffline", theListnerFn)
//登录终端登录信息变更回调
nim.V2NIMLoginService.on("onLoginClientChanged", theListnerFn)
Harmony
typescript//登录状态变化回调
nim.loginService.on("onLoginStatus", (status: V2NIMLoginStatus) => {})
//登录失败回调
nim.loginService.on("onLoginFailed", (error: V2NIMError) => {})
//登录终端被其他端踢下线回调
nim.loginService.on("onKickedOffline", (detail: V2NIMKickedOfflineDetail) => {})
//登录终端登录信息变更回调
nim.loginService.on("onLoginClientChanged", (change: V2NIMLoginClientChange, clients: V2NIMLoginClient[]) => {})

步骤2:将其他客户端踢下线

本地端调用 kickOffline 方法主动将使用相同 IM 账号登录的其他设备端踢下线。

Android
javaList<V2NIMLoginClient> loginClients = NIMClient.getService(V2NIMLoginService.class).getLoginClients();
if (loginClients.size() > 0) {
    // example: pick first
    V2NIMLoginClient client = loginClients.get(0);
    NIMClient.getService(V2NIMLoginService.class).kickOffline(client, new V2NIMSuccessCallback<Void>() {
        @Override
        public void onSuccess(Void unused) {
            // TODO
        }
    },
    new V2NIMFailureCallback() {
        @Override
        public void onFailure(V2NIMError error) {
            int code = error.getCode();
            String desc = error.getDesc();
            // TODO
        }
    });
}
iOS
objective-c- (void)kickOffline
{
    NSArray<V2NIMLoginClient *> *clients = [[NIMSDK sharedSDK].v2LoginService getLoginClients];
    V2NIMLoginClient *client = nil;
    // pick client to kick
    // put your code here
    // pick first
    client = [clients firstObject];
    
    [[NIMSDK sharedSDK].v2LoginService kickOffline:client success:^{
        NSLog(@"kick offline succ");
        } failure:^(V2NIMError * _Nonnull error) {
            NSLog(@"kick offline fail: error = %@", error);
        }];
}
macOS/Windows
cpploginService.kickOffline(client, []() {
    // kick client succeeded
}, [](V2NIMError error) {
    // kick client failed, handle error
});
Web/uni-app/小程序
typescriptconst loginClients = nim.V2NIMLoginService.getLoginClients()
try {
  if (loginClients && loginClients.length > 0) {
    const loginClient = await nim.V2NIMLoginService.kickOffline(loginClients[0])
    // todo, success
  }
} catch (err) {
  // TODO failed, check code
  // console.log(err.code)
}
Harmony
typescriptconst loginClients = nim.loginService.getLoginClients()
try {
  if (loginClients && loginClients.length > 0) {
    const loginClient = await nim.loginService.kickOffline(loginClients[0])
    // todo, success
  }
} catch (err) {
  // TODO failed, check code
  // console.log(err.code)
}

被踢方操作

  1. 被踢的客户端可在登录 IM 前,调用 addLoginListener 监听本地登录状态变化,收到被踢下线回调(onKickedOffline)后,建议登出并切换到登录界面。

    Android
    javaV2NIMLoginListener yourKickedOfflineListener = new V2NIMLoginListener() {
        @Override
        public void onLoginStatus(V2NIMLoginStatus status) {
    
        }
    
        @Override
        public void onLoginFailed(V2NIMError error) {
    
        }
    
        @Override
        public void onKickedOffline(V2NIMKickedOfflineDetail detail) {
            V2NIMKickedOfflineReason reason = detail.getReason();
            // TODO
        }
    
        @Override
        public void onLoginClientChanged(V2NIMLoginClientChange change, List<V2NIMLoginClient> clients) {
    
        }
    };
    NIMClient.getService(V2NIMLoginService.class).addLoginListener(yourKickedOfflineListener);
    
    iOS
    objective-c@interface YourKickedOfflineListener : NSObject <V2NIMLoginListener>
    
    @end
    
    @implementation YourKickedOfflineListener
    
    - (void)onKickedOffline:(V2NIMKickedOfflineDetail *)detail
    {
        NSLog(@"kicked detail = %@", detail);
    }
    
    @end
    
    - (void)listenKickedOffline
    {
        [[NIMSDK sharedSDK].v2LoginService addLoginListener:[[YourKickedOfflineListener alloc] init]];
    
    macOS/Windows
    C++V2NIMLoginListener listener;
    listener.onLoginStatus = [](V2NIMLoginStatus status) {
        // handle login status
    };
    listener.onLoginFailed = [](V2NIMError error) {
        // handle login error
    };
    listener.onKickedOffline = [](V2NIMKickedOfflineDetail detail) {
        // handle kicked offline detail
    };
    listener.onLoginClientChanged = [](V2NIMLoginClientChange change, nstd::vector<V2NIMLoginClient> clients) {
        // handle login client change
    };
    loginService.addLoginListener(listener);
    
    Web/uni-app/小程序
    TypeScriptnim.V2NIMLoginService.on("onKickedOffline", function (detail: V2NIMKickedOfflineDetail) {})
    

    :::

    Harmony
    typescriptnim.loginService.on("onLoginClientChanged", (change: V2NIMLoginClientChange, clients: V2NIMLoginClient[]) => {})
    
  2. 被踢下线后,被踢端还可以调用 getKickedOfflineDetail 方法获取被踢详情,包括被踢具体原因、将其踢下线的设备端的客户端类型等。

    Android
    java// get back detail anywhere
    // detail will be cleared after next call to login
    V2NIMKickedOfflineDetail kickedOfflineDetail = NIMClient.getService(V2NIMLoginService.class).getKickedOfflineDetail();
    
    iOS
    objective-c// get back detail anywhere
    // detail will be cleared after next call to login
    V2NIMKickedOfflineDetail *detail = [[NIMSDK sharedSDK].v2LoginService getKickedOfflineDetail];
    
    macOS/Windows
    cppauto kickedOfflineDetail = loginService.getKickedOfflineDetail();
    
    Web/uni-app/小程序
    typescriptconst detail = nim.V2NIMLoginService.getKickedOfflineDetail()
    

    :::

    Harmony
    typescriptconst detail = nim.loginService.getKickedOfflineDetail()
    

    如果当前状态不是被其他端主动踢下线,例如被服务端禁用并踢出 和自动登录监听到 417,则这两个方法的返回值无效。

此文档是否对你有帮助?
有帮助
去反馈
  • 前提条件
  • 实现多端登录互踢
  • 方式1:多端登录自动互踢
  • 只允许一端登录
  • 桌面端互踢、移动端互踢、桌面与移动端同时登录
  • 各端均可以同时登录在线
  • 自定义多端登录
  • 强制登录
  • 方式2:多端手动互踢
  • 踢方操作
  • 步骤1:注册多端登录监听
  • 步骤2:将其他客户端踢下线
  • 被踢方操作