如何巧妙地在基於 TCP Socket 的應用中實現用戶註冊功能?

  • 2019 年 10 月 23 日
  • 筆記

      通常,在基於TCP的應用中(比如我開源的GGTalk即時通信系統),當TCP連接建立之後,第一個請求就是登錄請求,只有登錄成功以後,服務器才會允許客戶端進行其它性質的業務請求。但是,註冊用戶這個功能比較特殊,因為在註冊之前,還不存在這個UserID,就更不可能用這個UserID來登錄了。

    所以,基於TCP的應用,用戶註冊功能一般是通過其它方式來實現的,比如,使用WebAPI,或者使用.NET Remoting等技術。 

      有沒有辦法可以不使用另外的技術而是直接基於當前的TCP連接來實現了?       

      經過我的摸索和實踐,找到了一個方法,可以達到這樣的效果,並且,即將推出的最新版本的GGTalk採用了這個方案。 這個方案,就是巧妙利用TCP應用的登錄功能,在其基礎上加上特殊的標記來表達註冊行為。

      我們先看看GGTalk中的登錄功能是如何做的。

一.現有的登錄機制            

1.客戶端

      客戶端通過調用IRapidPassiveEngine的Initialize方法來與服務器建立TCP連接,並發送登錄請求,以及獲取登錄結果。

        // 參數:          //   userID:當前登錄的用戶ID,由數字和字母組成,最大長度為10          //   logonPassword:用戶登陸密碼。          //   serverIP:服務器的IP地址。
// serverPort:服務器的端口。 // customizeHandler:自定義處理器,用於處理服務器或其它用戶發送過來的消息 LogonResponse Initialize(string userID, string logonPassword, string serverIP, int serverPort, ICustomizeHandler customizeHandler);

       參數含義非常清晰,其返回值LogonResponse的定義如下:

      

        LogonResponse的LogonResult屬性說明了登錄的結果,如果是Failed(登錄失敗),FailureCause屬性就指明了失敗的原因。

2.服務端

        服務端通過回調IBasicHandler的VerifyUser方法來驗證用戶的帳號信息。

      bool VerifyUser(string systemToken, string userID, string password, out string failureCause);

        返回的bool值表示驗證是否通過,如果返回false(驗證不通過,登錄失敗),則out參數failureCause指明失敗的原因,該參數會被返回到客戶端為LogonResponse的FailureCause屬性賦值。        

二.基本思路

        有了上面的基礎知識,我們就可以充分利用這個登錄機制來實現註冊功能了。

        我們在服務端和客戶端共同做出這樣的約定:

(1)當登錄的password參數以“#Reg:”開頭時,表示當前的行為就不是默認的登錄行為,而是註冊行為。

         因為password一般是MD5加密的,所以不可能以“#Reg:”開頭,所以,這樣就避免了將正常的登錄行為誤判為註冊行為。

(2)當password參數以“#Reg:”開頭,其接下來的內容即表示註冊所需的相關信息,各信息之間使用“;”分隔。

         比如,GGTalk使用的password的樣式如下所示:#Reg:[帳號];[密碼];[名稱];[簽名]。

         舉個例子,某個註冊的內容為:#Reg:10001;123456;david;加油!

(3)服務端發現當前的登錄作為註冊行為時,VerifyUser方法一定返回false。並且,註冊的結果通過out的failureCause返回給客戶端。

(4)客戶端以註冊行為來使用IRapidPassiveEngine的Initialize方法時,其返回值LogonResponse的LogonResult屬性肯定是LogonResult.Failed。

(5)客戶端根據返回值LogonResponse的FailureCause屬性值來判斷註冊是成功還是失敗。FailureCause的可能值是:Succeed ,Existed ,Error。

三.具體實現

 1.服務端

      服務端按如下方式實現IBasicHandler的VerifyUser方法:

 

        public bool VerifyUser(string systemToken, string userID, string password, out string failureCause)          {              if(password != null && password.StartsWith(RegistActionToken)) //表示是註冊              {                  //password內容示例: #Reg:10001;123456;david;加油!                  failureCause = this.Register(password);                  return false;              }              
//此處驗證帳號密碼...
return true; } private static string RegistActionToken = "#Reg:"; private string Register(string content) { try { //content內容示例: #Reg:10001;123456;david;加油! string[] parts = content.Substring(RegistActionToken.Length).Split(';'); string id = parts[0]; string pwd = parts[1]; string name = parts[2]; string signature = parts[3]; GGUser user = new GGUser(id, SecurityHelper.MD5String2(pwd), name, signature); RegisterResult res = this.Register(user); //將註冊的用戶資料存儲到DB return res.ToString(); //Succeed ,Existed ,Error } catch(Exception ee) { return "Error: 解析註冊字符串報錯!" + ee.Message; } }

       代碼比較簡單,就不再過多解釋了。

2.客戶端

      客戶端通過調用IRapidPassiveEngine的Initialize方法來完成註冊行為,代碼如下所示:

        public void Register(string id, string pwd, string name, string signature)          {              string content = string.Format("{0}{1};{2};{3};{4}", RegistActionToken, id, pwd, name, signature);              LogonResponse response = rapidPassiveEngine.Initialize("forRegister", content, serverIP, serverPort, null);              string result = response.FailureCause; //Succeed ,Existed ,Error              if (result == "Succeed")              {                  MessageBox.Show("註冊成功!");              }              else if (result == "Existed")              {                  MessageBox.Show("帳號已經存在!");              }              else              {                  MessageBox.Show("註冊過程中出現錯誤!");              }          }

      注意,如果註冊成功,客戶端也是還尚未登錄到服務器的。

      註冊成功之後,再使用成功註冊的帳號和密碼調用正常邏輯的IRapidPassiveEngine的Initialize方法來登錄到服務器。

四.結語

      如此一來,我們就完全可以使用現有的TCP機制來實現用戶的註冊,而且在多端(PC、安卓、iOS)都可以這樣做。這樣就避免了僅僅為了一個註冊功能而需要去發佈一個Web站點或發佈一個Remoting服務(且不說Remoting還沒法支持安卓和iOS端),太不划算了。

      順便說一下,GGTalk接下來會不斷推出新的版本,不斷增強功能,而且UI方面也將進一步美化,敬請期待。