? ? ? ??在C/S架構(gòu)中,通常是使用 UserID 作為唯一標(biāo)志來(lái)標(biāo)記每一個(gè)用戶的,也就是說(shuō),對(duì)于一個(gè)指定的UserID,只能有一個(gè)客戶端在線。
? ? ? ?
如果我們開(kāi)發(fā)的系統(tǒng)要支持同帳號(hào)多設(shè)備同時(shí)登錄的場(chǎng)景,即需要像微信一樣,在PC端登錄的同時(shí),也可以使用同一個(gè)帳號(hào)登錄移動(dòng)端(iOS或Android),那么,如何才能做到了?
? ? ? ?
解決方案的原理是比較簡(jiǎn)單的:既然C/S系統(tǒng)要求UserID作為用戶標(biāo)記必須是唯一的,那么我們就引入一個(gè)稱為“LoginID”的概念,對(duì)于同一個(gè)用戶,在不同類型的設(shè)備上就使用不同的LoginID,但是這些LoginID都指向同一個(gè)真正的UserID。
一. LoginID 與 UserID
1. 不需要支持同帳號(hào)多設(shè)備同時(shí)登錄的簡(jiǎn)單場(chǎng)景
??????? 在之前
不支持同帳號(hào)多設(shè)備同時(shí)登錄的場(chǎng)景中(簡(jiǎn)稱“單設(shè)備登錄”場(chǎng)景),登錄用的帳號(hào)就是真正的UserID,也就是說(shuō)底層框架中各個(gè)API(各個(gè)方法以及事件)的參數(shù)涉及到的用戶帳號(hào)都是真正的UserID。比如,一個(gè)帳號(hào)abc001,該帳號(hào)是存在于數(shù)據(jù)庫(kù)的用戶表中的;使用abc001登錄到服務(wù)器,在整個(gè)的運(yùn)作過(guò)程中,服務(wù)端正是使用abc001來(lái)標(biāo)記對(duì)應(yīng)的客戶端實(shí)例。在該場(chǎng)景中,不會(huì)存在多個(gè)運(yùn)行的客戶端實(shí)例都對(duì)應(yīng)帳號(hào)abc001的情況。如果有個(gè)客戶端已經(jīng)使用abc001登錄,然后再用該帳號(hào)在其它地方登錄,默認(rèn)的機(jī)制是會(huì)把之前登錄的那個(gè)客戶端擠掉線。
2. 需要支持同帳號(hào)多設(shè)備同時(shí)登錄的復(fù)雜場(chǎng)景
?????? 如果現(xiàn)在我們要
支持同帳號(hào)多設(shè)備同時(shí)登錄的場(chǎng)景(簡(jiǎn)稱“多端登錄”場(chǎng)景),那么,服務(wù)端在整個(gè)的運(yùn)作過(guò)程中,就不能使用abc001來(lái)標(biāo)記對(duì)應(yīng)的客戶端實(shí)例了,因?yàn)榇嬖诙鄠€(gè)客戶端實(shí)例都對(duì)應(yīng)同一個(gè)abc001帳號(hào)的情況。于是,我們使用LoginID來(lái)區(qū)分這種情況下不同的客戶端實(shí)例。
?????? 常用的方法是,
在真正的UserID前加上兩個(gè)字符的前綴以構(gòu)成LoginID。?比如,對(duì)于abc001這個(gè)帳號(hào),在使用iOS設(shè)備登錄時(shí),我們選擇使用前綴“1#”,這樣iOS設(shè)備使用的LoginID就是
1#abc001;同理,Android設(shè)備就使用2#abc001。
??????? 該兩個(gè)字符的前綴的含義是這樣的:
(1)第二個(gè)字符“#”,是一個(gè)標(biāo)志(token),表示該ID是一個(gè)LoginID。
(2)第一個(gè)字符,表示設(shè)備的類型。比如“0”表示.NET設(shè)備(PC),“1”表示iOS設(shè)備,“2”表示Android設(shè)備,等等。
???????當(dāng)使用LoginID后,服務(wù)端在整個(gè)的運(yùn)作過(guò)程中就不再是使用真正的UserID來(lái)標(biāo)記客戶端實(shí)例了,而是使用LoginID --
也就是說(shuō),框架中各個(gè)API(各個(gè)方法以及事件)的參數(shù)涉及到的用戶帳號(hào)都是LoginID了。?????
???
二. MultiDeviceHelper 類
? ? ? ? 我寫了一個(gè)MultiDeviceHelper類,用于為多設(shè)備同時(shí)登錄提供支持。特別是,提供了與LoginID的構(gòu)造和解析相關(guān)的API。
??????? 在MultiDeviceHelper的靜態(tài)構(gòu)造函數(shù)中,規(guī)定了每種設(shè)備的前綴,如下所示:
static MultiDeviceHelper() { #region 如果在當(dāng)前的應(yīng)用中,不存在某種類型的設(shè)備,則注釋掉下面對(duì)應(yīng)的語(yǔ)句即可。
MultiDeviceHelper.LoginIDPrefixMapping.Add(ClientType.IOS, "1#");
MultiDeviceHelper.LoginIDPrefixMapping.Add(ClientType.Android, "2#");
MultiDeviceHelper.LoginIDPrefixMapping.Add(ClientType.DotNet, "3#"); #endregion
}
???????? 然后,?MultiDeviceHelper提供了多個(gè)靜態(tài)方法以完成真正UserID、設(shè)備類型與LoginID之間的轉(zhuǎn)換:
???????
?
三. 登錄和登錄驗(yàn)證
????? ?客戶端在登錄時(shí)會(huì)調(diào)用IRapidPassiveEngine的Initialize方法:
???????LogonResponse?Initialize(string?userID,?string?logonPassword,?string?
serverIP,?int?serverPort,?ICustomizeHandler?customizeHandler);?
?????? 該方法的第一個(gè)參數(shù)就需要傳入LoginID,比如 1#abc001。
???????在服務(wù)端會(huì)回調(diào)IBasicHandler接口的VerifyUser方法來(lái)進(jìn)行帳號(hào)密碼驗(yàn)證:
????????bool?VerifyUser(string?systemToken,?string?userID,?string?password,
out?string?failureCause);????????
?????? 此時(shí)要注意的是,VerifyUser方法傳入的userID參數(shù)實(shí)際上是LoginID,即
1#abc001。我們需要通過(guò)調(diào)用MultiDeviceHelper的ParseLoginID方法來(lái)獲取真正的UserID,該方法會(huì)返回
abc001,并且out參數(shù)指明設(shè)備類型為iOS。
?
四. 處理消息及其它
? ? ? ?服務(wù)端是通過(guò)回調(diào)ICustomizeHandler接口的HandleInformation方法來(lái)處理接收到的消息的:
?????? void HandleInformation(string?sourceUserID,?int?informationType,?byte[]
info);?
??????
同上面一樣,此處的sourceUserID參數(shù)實(shí)際上也是LoginID,所以,也需要調(diào)用MultiDeviceHelper的ParseLoginID方法來(lái)將其轉(zhuǎn)換成真正的UserID。
??????
同理,在多設(shè)備登錄場(chǎng)景中,框架中各個(gè)API(各個(gè)方法以及事件)的userID參數(shù)實(shí)際上都是LoginID,在處理時(shí)都需要做類似的處理,這里就不一一列舉了。
?
五. 多設(shè)備聊天消息同步
??????
在解決了多設(shè)備同時(shí)登錄的問(wèn)題后,還有一個(gè)常見(jiàn)的需求:類似QQ的PC和手機(jī)端同時(shí)在線時(shí),別人給我發(fā)一條消息,手機(jī)端和PC端都能接收到。這樣的功能是怎么實(shí)現(xiàn)的了?
?????? 在單設(shè)備登錄場(chǎng)景中,我們通常是在客戶端調(diào)用ICustomizeOutter接口的下列Send方法來(lái)發(fā)送聊天消息的:
?????? void Send(string?targetUserID,?int?informationType,?byte[] info);?
?????? 該方法的第一個(gè)參數(shù)是接收者的UserID,表示直接將聊天消息發(fā)送給對(duì)方(可能是經(jīng)過(guò)服務(wù)器中轉(zhuǎn),或者是經(jīng)P2P通道直接傳送)。
?????? 但是,在多設(shè)備登錄場(chǎng)景中,不能再直接發(fā)送了,而是必須要經(jīng)過(guò)服務(wù)器中轉(zhuǎn),通過(guò)調(diào)用下面的Send方法:
?????? void Send(int?informationType,?byte[] info);?
??????
該Send方法將消息直接發(fā)送給服務(wù)端,在info參數(shù)中包含要消息接收者的UserID。服務(wù)端在處理該消息時(shí),需要從info中將接收者UserID解析出來(lái),然后,調(diào)用MultiDeviceHelper
的 GetLoginIDList
方法來(lái)獲取各個(gè)設(shè)備類型對(duì)應(yīng)的LoginID,然后,服務(wù)端在把該消息發(fā)送給每一個(gè)LoginID。如此,手機(jī)端和PC端就都能收到這條聊天消息了。?
?
(注:最新版本的 ESFramework.MSide.dll 已經(jīng)內(nèi)置了對(duì)多端同時(shí)登錄的支持,也就是說(shuō),本文所闡述的原理已經(jīng)在ESFramework
<http://www.oraycn.com/ESFramework_detail.aspx>框架中進(jìn)行了實(shí)現(xiàn)。另外,OrayTalk
也增加了多端登錄的功能,可下載測(cè)試 <http://www.oraycn.com/OrayTalk_download.aspx>。)
?
熱門工具 換一換
