Advanced
第 7 章 進階應用
7-1 訊息安全(Security)
訊息在網路上傳遞時,最好能夠加密,目前加解密演算法分為對稱式與非對稱式兩種,由於非對稱式演算法須耗去大量的 CPU 運算時間,為了符合遊戲快速訊息處理的需要,建議採用對稱式的加密演算法。
SGC 將訊息封包的加密解密交由開發者自行處理,這是因為對稱式加密與解密使用的金鑰是同一種,為了避免所有 SGC 遊戲都在同一個加解密演算法與金鑰之下運作,讓開發者保有自己的加解密方法,自然可以降低被破解的危險性。
以下範例程式,我們定義了一個全域物件 MessageKey 用來存放金鑰,配合 Security 物件內的加密解密函式,就可以在傳送與接受訊息時進行加密與解密:
//TripleDes.cs
using System;
using System.Security.Cryptography;
using System.IO;
using System.Text;
using GameCloud;
sealed internal class MessageKey
{
static MessageKey _context;
// 以下KEY值僅供參考,請自行以任意16個byte數字組成的陣列做為KEY
internal byte[] bKey = {0x13,0xcd,0x8a,0xc5,0x7a,0x9,0xab,0x4f,0xd6, 0x33, 0x59, 0x2b, 0x65, 0xe3, 0xf1, 0x4d};
// 以下IV值僅供參考,請自行以任意8個byte數字組成的陣列做為IV
internal byte[] bIV = {0x9a,0xdd, 0xef,0x1c,0x88,0x48,0xa0,0xf6};
static public MessageKey instance
{
get{
lock(typeof(MessageKey))
{
if (_context == null)
{
_context = new MessageKey();
}
}
return _context;
}
}
}
public class Security
{
public Security()
{
mCSP.Key = MessageKey.instance.bKey;
mCSP.IV = MessageKey.instance.bIV;
}
public string EncryptString(string Value)
{
ICryptoTransform ct;
MemoryStream ms;
CryptoStream cs;
byte[] byt;
mCSP.Mode = System.Security.Cryptography.CipherMode.ECB;
mCSP.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV);
byt = Encoding.UTF8.GetBytes(Value);
ms = new MemoryStream();
cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
cs.Write(byt, 0, byt.Length);
cs.FlushFinalBlock();
cs.Close();
return Convert.ToBase64String(ms.ToArray());
}
public string DecryptString(string Value)
{
ICryptoTransform ct;
MemoryStream ms;
CryptoStream cs;
byte[] byt;
mCSP.Mode = System.Security.Cryptography.CipherMode.ECB;
mCSP.Padding = System.Security.Cryptography.PaddingMode.PKCS7;
ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV);
byt = Convert.FromBase64String(Value);
ms = new MemoryStream();
cs = new CryptoStream(ms, ct, CryptoStreamMode.Write);
cs.Write(byt, 0, byt.Length);
cs.FlushFinalBlock();
cs.Close();
return Encoding.UTF8.GetString(ms.ToArray());
}
}
建立Security物件
程式一開始就可以建立 Security
物件,由於 Security
物件內的加密解密函式是用在訊息接收與傳送,所以此物件必須是訊息接收與傳送函式可以存取的物件。
以下範例是在DP 的 FormLoad 時建立 Security
物件:
//(DP) dp001.cs
Security so;
private void dp001_Load(object sender, EventArgs e)
{
so = new Security();
labelSystemStatus.Text = "尚未連線";
ag = new CloudGame(userid, passwd, gguid, gcert);
ag.onCompletion += OnCompletion;
ag.onStateChanged += OnStateChanged;
ag.onPrivateMessageIn += OnPrivateMessageIn;
labelSystemStatus.Text = "連線中 ";
ag.Launch();
}
有了 Sercurity 物件後,往後就可以使用此物件內的加密與解密函式進行加解密了。
傳輸與接收訊息
接收訊息是在OnPrivateMessageIn 事件函式中,只要在事件函式中,把收到的字串訊息進行解密即可,不過,為了確保訊息可以解密,如果傳進來的訊息有問題,導至解密發生Exception,我們只要將之視為無效訊息,不須理會。
以下範例是 OnPrivateMessageIn()
,我們只在最上面加了一行呼叫解密函式:
//(DP) dp001.cs
void OnPrivateMessageIn(string msg, int d, CloudGame game)
{
try
{
string m = so.DecryptString(msg);
string[] cmds = m.Split(':');
uint ClientPoid;
(以下省略)
}
catch (Exception)
{}
}
至於傳送訊息,我們可以自己定義 SecureSend()
函式,用來取代 Send()
函式,日後只要記得不要直接使用Send()
即可:
//(DP) dp001.cs
// 私人訊息
void SecurePrivacySend(string msg, uint poid)
{
ag.PrivacySend(so.EncryptString(msg), poid);
}
// 場景訊息
void SecureSeceneSend(CloudScene scene, string msg)
{
scene.Send(so.EncryptString(msg));
}
請注意,由於 DP 在傳送與接收訊息已經增加了加解密的功能,用戶端的程式同樣也要加上加解密的功能,而且所使用的 KEY 與 IV 值也必須和DP 所使用的完全一樣。
7-2 系統記錄檔(Log)
開發者可以依照需要,規劃各種不同的系統記錄檔,將系統運作過程中的重要資料記錄起來。基本上,系統記錄檔大約分為除錯追蹤要用的程式記錄檔,以及與遊戲運作有關的遊戲記錄檔。
- PC 執行檔
PC 執行檔程式可以直接將記錄直接寫入用戶端的磁碟檔,由於 PC 磁碟檔容量很大,比較沒有磁碟已滿的問題,所以實作系統記錄檔的限制較小。
- Web Based
Web Based 程式受限於瀏覽器的安全性設定,一般來說是無法直接寫入磁碟檔的,所以無法實作系統記錄檔。
- App
App 程式雖然可以將系統記錄寫入到手機(或平板)的記憶卡裡,不過由於記憶卡的容量有限,如果我們開發的 App 程式導致用戶記憶體不足,為了避免這種狀況發生,除非是程式研發階段, 否則應該避免將系統記錄寫入手機記憶卡。
- DP
DP 程式運行於獨立主機或是 VM,和 PC 執行檔一樣沒有磁碟容量的問題,因此可以盡量記錄各種資訊,以因應各種需要。
系統紀錄檔程式範例
以下範例DPLog.cs 可用於 DP 程式中:
using System;
using System.IO;
sealed internal class DPLogPath
{
static DPLogPath _context;
internal string LogBasePath = null;
static public DPLogPath instance
{
get
{
lock(typeof(DPLogPath))
{
if (_context == null)
{
_context = new DPLogPath();
}
}
return _context;
}
}
}
public class DBLog
{
public static void WriteLog(string subdir, int tag, string log)
{
string LogFileName;
try
{
if (DPLogPath.instance.LogBasePath == null) return;
if (subdir == null || subdir == "")
{
LogFileName = DPLogPath.instance.LogBasePath + "\\";
}
else
{
LogFileName = DPLogPath.instance.LogBasePath + "\\" + subdir + "\\";
}
LogFileName += DateTime.Now.Year.ToString("D4") +
DateTime.Now.Month.ToString("D2") +
DateTime.Now.Day.ToString("D2") + ".log";
string LogData = DateTime.Now.Year.ToString("D4") + "-" +
DateTime.Now.Month.ToString("D2") + "-" +
DateTime.Now.Day.ToString("D2") + " " +
DateTime.Now.Hour.ToString("D2") + ":" +
DateTime.Now.Minute.ToString("D2") + ":" +
DateTime.Now.Second.ToString("D2") + "," + "[" + tag.ToString("D8") + "]" + log;
lock(DPLogPath.instance)
{
using(StreamWriter sw = File.AppendText(LogFileName))
{
sw.WriteLine(LogData);
sw.Flush();
sw.Close();
}
}
}
catch (Exception e)
{}
}
}
程式一開始必須先指定記錄檔的路徑,而且只要指定一次即可。假設記錄檔位於「C:\DP-Log
」資料匣,程式碼如下:
DPLogPath.instance.LogBasePath = "c:\\DP-Log";
路徑指定之後,要寫入一筆記錄,程式碼如下:
DBLog.WriteLog(null,theTag,"System Startup!!");
其中字串
"System Startup!!
"
就是我們要寫入的記錄資料;另外,由於參數 subdir
為 null,所以記錄檔是在「C:\DP-Log
」中, 檔案則以年月日(YYYYMMDD.Log)
為檔名,YYYY
為目前的西元年,MM
為目前月份,DD
則是目前日期。
7-3 DP代管(Co-location)
由於DP 是一個獨立運作於遠端的程式,目前 SGC 尚未提供 VM 的服務,建議開發者可以向雲端 VM 業者租用,千萬不要以訂戶線(Subscriber Line)做為 DP 與 SGC 連線的線路,這是因為一般的訂戶線如ADSL、FTTB 等,都是共用頻寬,很容易受到其它使用者的影響,使得 DP 的品質與效能降低。未來如果 SGC 的 VM 服務上線,建議將 DP 在 SGC 的 VM 上運行,直接與 SGC 主機群連線,可增加效能與穩定度。 目前全世界提供 VM 服務的業者非常多,請選擇頻寬穩定,機房品質良好,又有口碑的業者,例如Microsoft Azure、Amazon 的 AWS 服務等。
7-4 申請玩家遊戲
由於 iOS 的應用程式上架規定中有個潛規定,如果應用程式中需要用到第三方的登入帳號,申請帳號的過程若由應用程式跳到 Web 頁面,這個 Web 網站只能是單純的會員系統,如果網站涉及金流付款服務,應用程式將無法上架。
為了避免這個問題,應用程式必須內建「申請玩家帳號」功能,開發者可以在應用程式中規劃一個申請帳號的表單,包含以下欄位:
- 登入帳號
- 遊戲密碼
- 確認密碼
- 電子郵件
然後呼叫CloudSystem.ApplyNewUser()
函式,定義如下:
static void ApplyNewUser(string gguid, byte[] gamecode, string userid, string passwd, string email, OnCallCompletion cb, object token)
(1) gguid
是遊戲的GGUID。
(2) gamecode
是遊戲驗證碼。
(3) userid
是所申請的玩家帳號,請注意帳號長度必須介於 3 到 10 個字元,而且只能是英數字, 符號只能是「-」和「.」。
(4) passwd
是帳號的密碼,長度限制 6 到 18 字元。
(5) email
是帳號的 e-mail 信箱,用來驗證身份。
cb
為完成API 後的callback 函式,其函式定義如下:
void cb(int code, object token)
token
是和 callback 函式傳遞相依資料的物件。
此外, 如果遊戲想要降低玩家登入遊戲的門檻, 甚至連申請帳號都不必時, 可透過CloudSystem.ApplyNewUser()
函式的另外一個 Overloading,此函式不必傳入帳號、密碼、e-mail 等申請者資料,而是由系統配發帳號,並傳回帳號的名稱與密碼,此帳號又可稱為玩家體驗帳號。
static void ApplyNewUser(string gguid, byte[] gamecode, OnCallCompletion cb, object token)
體驗帳號的帳號密碼傳回後,遊戲程式必須儲存在本機儲存體中,玩家在遊戲中的資料,如CloudItem、CloudScore 所儲存的資料也可下次登入遊戲時取出使用。由於體驗帳號無法在 SGC 官網取得登入網站的帳號密碼,因此體驗帳號必須升級成正式帳號,升級正式帳號可以呼叫 CloudSystem.UpgradeUser()
,函式定義如下:
static void UpgradeUser(CloudGame game, string userid,string passwd,string email, OnCallCompletion cb, object token)