• 大发11选5官网

  • 大发11选5官网

  • 大发11选5官网

  • 大发11选5官网

大发11选5官网

作者︰  發布日期︰2020-02-26 02:50:55
Tag標(biao)簽︰撲克  小游戲(xi)  日記  
  • 怎麼辦,這很尷尬,為啥呢,因為kbe的某些原因讓我(wo)放pai)聳shi)用(yong)它所以(yi)本打算繼(ji)續更新的,說一下原因︰

    在(zai)DAY1中我(wo)希望kbe能夠開(kai)啟一個http服務,並讓php端做一個web請(qing)求將消(xiao)息(xi)傳遞給(gei)對應(ying)的用(yong)戶,可是這個http服務我(wo)是qie)雌鵠戳耍 ?song)消(xiao)息(xi)的函數也寫出來(花了不少時間(jian),kbe的注(zhu)釋和文檔都不多(duo),特(te)別是kbe把BaseHttpServer這個python庫另(ling)外弄(long)了個名(ming)字,用(yong)http.server import as 才導入(ru)成(cheng)功)尷尬的就是http服務和發消(xiao)息(xi)的函數怎麼也放不到一起︰

    1.一旦某個class不繼(ji)承自KBEngine.Base,那(na)麼ci)ta)就無法訪問(wen)KBEngine的幾fu)hu)所有(you)靜態(tai)函數、屬性,就無法獲取到對應(ying)用(yong)戶的mailbox完成(cheng)消(xiao)息(xi)發送(song)

    2.一旦繼(ji)承KBEgine.Base,你就做不了HTTP 服務,因為你的handler必須繼(ji)承baseHandler,你繼(ji)承不了,且即使(shi)你繼(ji)承到baseHandler去(qu)訪問(wen)KBEgine.Base的mailbox之類的又回到剛的死(si)邏輯之中

    3.系統庫的HTTP服務會阻塞進程,這個文檔還(huai)是qie)戳耍 還(huai)媧dai)框架太麻煩chang) 業(ye)魘蘊 環獎悖 矣鋟ㄌ 煜? hellip;………雖然我(wo)想說一萬個且,只能說de)魑wo)無能啊(a)…………

    當然論壇和官(guan)網(wang)當中也有(you)人反cong) 嗨頻奈wen)題,例如(ru)第三方接口(kou)訪問(wen)KBE的成(cheng)員/屬性問(wen)題,不huai)雌鵠春孟癲 揮you)現成(cheng)的解決方案,最後的最後。。。我(wo)放pai)/p>

     

    然後呢~~我(wo)自己老(lao)老(lao)實(shi)實(shi)寫了一個消(xiao)息(xi)服務器(基于socket ,with WPF .net 4.5+)以(yi)及消(xiao)息(xi)協議

    消(xiao)息(xi)協議采(cai)用(yong)http://msgpack.org/ 基本上支援所有(you)的語言,因此(ci)實(shi)際上我(wo)這個消(xiao)息(xi)服務器可以(yi)服務任何類型的客戶端,不huai)guan)你啥平台(tai)啥語言

     

    1.0版協議(還(huai)沒名(ming)字呢)規定:

    1.BasePack代(dai)表發送(song)的包(bao),BaseAckPack代(dai)表回執包(bao),BaseAckPack繼(ji)承自BasePack

    2.每個Pack長度為1024字節(jie),且ye)~4字節(jie)轉換成(cheng)int代(dai)表pack類型, BasePack及其子類從(cong)1.2.3...10... BaseAckPack 及其子類從(cong)1001,1002,1003...1010...(有(you)考慮負(fu)數,其實(shi)應(ying)該也ok)

    為啥這樣做? 這里(li)很奇特(te),你在(zai)把這1024個字節(jie)用(yong)msgpack轉成(cheng)對象之前,你並不知道這個pack是哪個對象,你不能統一按某一個特(te)定的對象去(qu)轉,比如(ru)LoginPack比BasePack只多(duo)了2個屬性,你在(zai)不知道它是一個LoginPack還(huai)是一個BasePack之前,你無法拆開(kai)他(ta),你按任何wo)恢擲床鸝kai)都有(you)可能出錯(屬性多(duo)了或少了,熟悉iOS 的KVC的應(ying)該很清(qing)楚),所以(yi)必須先把前面4個字節(jie)騰出來,可選的,第5~8個字節(jie)放長度(mespack可以(yi)長度大于內容拆開(kai)沒問(wen)題),讀8個字節(jie)之後再讀剩下的1016(當然不一定每個包(bao)一定得(de)是1024,可以(yi)更大,畢竟我(wo)目前夠用(yong)了)個字節(jie)

     

    using System;//send包(bao)namespace Packs{ //基礎包(bao) public class BasePack<T> { public int packType; public int fromId; public int toId; public int messageId; //將基本包(bao)轉bytes public byte[] PackToBytes() {  var encode = MsgPack.Serialization.MessagePackSerializer.Get<T>();  byte[] packContent = encode.PackSingleObject(this);  byte[] type = BasePack.intToBytes(this.packType);  byte[] len = BasePack.intToBytes(packContent.Length);  int lenth = packContent.Length;  byte[] dest = new byte[1024];  //第一個int空間(jian)︰類型  Buffer.BlockCopy(type, 0, dest, 0, type.Length);  //第二個int空間(jian)︰長度  Buffer.BlockCopy(len, 0, dest, type.Length, len.Length);   //剩余空間(jian)︰包(bao)內容  Buffer.BlockCopy(packContent, 0, dest, type.Length+len.Length, packContent.Length);  Console.WriteLine("打包(bao)pack,類型:" + this.packType + "長度:" + packContent.Length);  return dest; } //將bytes轉回基本包(bao) public static T BytesToPack(byte[] bytes) {  var encode = MsgPack.Serialization.MessagePackSerializer.Get<T>();  return encode.UnpackSingleObject(bytes); } } public class BasePack:BasePack<BasePack> { public const int LOGIN_PACK = 1; public const int REGISTER_PACK = 2; public const int PING_PACK = 3; public const int PONG_PACK = 4; public const int TEXT_PACK = 5; public const int SYSTEM_PUSH_PACK = 6; public const int LOGIN_ACK = 1001; public const int REGISTER_ACK = 1002; public const int PING_ACK = 1003; public const int PONG_ACK = 1004; public const int TEXT_ACK = 1005; public const int SYSTEM_PUSH_ACK = 1006; public const int CONNECTED_ACK = 1007;  /** * 將int數值(zhi)轉換為佔四個字節(jie)的byte數組,本方法適shi)yong)于(低位在(zai)前,高(gao)位在(zai)後)的順序。 * @param value *  要(yao)轉換的int值(zhi) * @return byte數組 */ public static byte[] intToBytes(int value) {  byte[] byte_src = new byte[4];  byte_src[3] = (byte)((value & 0xFF000000) >> 24);  byte_src[2] = (byte)((value & 0x00FF0000) >> 16);  byte_src[1] = (byte)((value & 0x0000FF00) >> 8);  byte_src[0] = (byte)((value & 0x000000FF));  return byte_src; } /**  * byte數組中取int數值(zhi),本方法適shi)yong)于(低位在(zai)前,高(gao)位在(zai)後)的順序。 *  * @param ary  *  byte數組  * @param offset  *  從(cong)數組的第offset位開(kai)始  * @return int數值(zhi)  */ public static int bytesToInt(byte[] ary, int offset) {  int value;  value = (int)((ary[offset] & 0xFF)   ((ary[offset + 1] << 8) & 0xFF00)   ((ary[offset + 2] << 16) & 0xFF0000)   ((ary[offset + 3] << 24) & 0xFF000000));  return value; } } //1.登錄包(bao) public class LoginPack: BasePack<LoginPack> { public string username; public string password; public LoginPack() {  this.packType = BasePack.LOGIN_PACK; } } //5.文字包(bao) public class TextPack:BasePack<TextPack> { public string content; public string toUser; public string fromUser; public TextPack() {  this.packType = BasePack.TEXT_PACK; } } //6.系統推送(song)包(bao) public class SystemPushPack: BasePack<SystemPushPack> { public string content; public string toUser;  public SystemPushPack() {  this.packType = BasePack.SYSTEM_PUSH_PACK; } } }


     

     

    3.server端Accept之後立即發送(song)ConnectPack,客戶端收到後發送(song)ConnectAckPack完成(cheng)連接

     

     private void OnAccept() { while (this.isServing) {  //異步Accept 回調ConnEnd  //serverSocket.BeginAccept(new System.AsyncCallback(this.ConnEnd), null);  //同(tong)步Accept  Socket clientSocket = serverSocket.Accept();  ReceiveObject obj = new ReceiveObject();  obj.acceptClient = clientSocket;  clients.Add(obj);  Thread receiveThread = new Thread(OnReceive);  receiveThread.Start(obj);  cThreads.Add(clientSocket.RemoteEndPoint.ToString(), receiveThread);  Console.WriteLine("新的客戶端連接:" + clientSocket.RemoteEndPoint.ToString());  BaseACKPack pack = new BaseACKPack();  pack.packType = BasePack.CONNECTED_ACK;  clientSocket.Send(pack.PackToBytes()); } }


     

     

    4.客戶端發送(song)LoginPack(由(you)于php已經(jing)校驗了用(yong)戶名(ming)和密碼(ma)並且生成(cheng)了token,所以(yi)loginPack實(shi)際上我(wo)沒有(you)寫校驗密碼(ma)的邏輯,單(dan)純(chun)的綁(bang)定用(yong)戶名(ming),用(yong)來接收消(xiao)息(xi)),server端拆開(kai)pack將用(yong)戶名(ming)綁(bang)定到客戶端對象中,這個對象的內容如(ru)下︰

     

     

    using System.Net.Sockets;public class ReceiveObject{ public Socket acceptClient; public byte[] buffer = new byte[1024]; public string userId; public string userName; public int roomId; public ReceiveObject() { }}

     

     

    整(zheng)個處理函數︰

     

     private void OnReceive(object obj) { while (this.isServing) {  ReceiveObject e = obj as ReceiveObject;  Socket c = e.acceptClient;  e.buffer = new byte[1024];  //判斷包(bao)類型,固(gu)定包(bao)在(zai)包(bao)之前  int type = c.Receive(e.buffer, 0, sizeof(Int32), SocketFlags.None);  if (type == 0)  {  Console.WriteLine("客戶端斷開(kai)連接:" + c.RemoteEndPoint.ToString());  //clients.RemoveAll((ReceiveObject obj) => { return obj.acceptClient == 0 ? true : false; });  clients.Remove(e);  cThreads.Remove(c.RemoteEndPoint.ToString());  Thread.CurrentThread.Abort();  //斷開(kai)連接  c.Shutdown(SocketShutdown.Both);  c.Close();  break;  }  type = BasePack.bytesToInt(e.buffer, 0);  //獲得(de)包(bao)大小,固(gu)定第2個int  int len = c.Receive(e.buffer, 0, sizeof(Int32), SocketFlags.None);  len = BasePack.bytesToInt(e.buffer, 0);  int receiveNumber = c.Receive(e.buffer, 0, 1024 - sizeof(Int32) * 2, SocketFlags.None);  switch (type)  {  case BasePack.LOGIN_PACK:   {   LoginPack lPack = LoginPack.BytesToPack(e.buffer);   Console.WriteLine("收到登錄請(qing)求,用(yong)戶名(ming):" + lPack.username + "密碼(ma):" + lPack.password);   e.userName = lPack.username;   //發送(song)登錄ACK   LoginACKPack loginACK = new LoginACKPack();   //loginACK.success = true;   c.Send(loginACK.PackToBytes());   }   break;  case BasePack.TEXT_PACK:   {   //處理消(xiao)息(xi)包(bao)   TextPack pack = TextPack.BytesToPack(e.buffer);   //處理basePack   Console.WriteLine("發送(song)給(gei)" + pack.toUser + "的消(xiao)息(xi):" + pack.content);   //從(cong)clients組找(zhao)用(yong)戶   List<ReceiveObject> list = clients.FindAll((ReceiveObject o) => { return o.userName == pack.toUser ? true : false; });   foreach (ReceiveObject target in list)   {    target.acceptClient.Send(pack.PackToBytes());   }   }   break;  case BasePack.TEXT_ACK:   {   //處理消(xiao)息(xi)回執   TextACKPack pack = TextACKPack.BytesToPack(e.buffer);   //刪(shan)除對應(ying)的消(xiao)息(xi)   pusher.DeleteMessageById(pack.messageId);   }   break;  case BasePack.SYSTEM_PUSH_ACK:   {   SystemPushACKPack pack = SystemPushACKPack.BytesToPack(e.buffer);   //刪(shan)除對應(ying)的消(xiao)息(xi)   pusher.DeleteMessageById(pack.messageId);   }   break;  default:   //處理未知包(bao)   {   }   break;  } } }

    發送(song)消(xiao)息(xi)函數,目前寫了2個case 原因︰php端的推送(song)類型很多(duo),我(wo)直(zhi)ben)有(you)叢zai)pushPack的content內部,客戶端用(yong)json解析開(kai)就行了,然後做了一個單(dan)聊的文本消(xiao)息(xi)發送(song),按群(qun)組推還(huai)沒來得(de)及做︰

     

     

     public void SendMsg(string from, string to, string body, int type, int messageId) { //從(cong)clients組找(zhao)用(yong)戶 ReceiveObject target = clients.FindLast((ReceiveObject o) => { return o.userName == to ? true : false; }); if (target == null) return; //推送(song)一條消(xiao)息(xi)至客戶端 //收到回執後才能修改sent狀態(tai)為1 Console.WriteLine("推送(song)消(xiao)息(xi)給(gei):" + to + "類型:" + type + "內容:" + body + "id:" + messageId); switch (type) {  //推送(song)文字消(xiao)息(xi)  case BasePack.TEXT_PACK:  {   TextPack txtPack = new TextPack();   txtPack.fromUser = from;   txtPack.toUser = to;   txtPack.content = body;   txtPack.messageId = messageId;   target.acceptClient.Send(txtPack.PackToBytes());  }  break;  //系統消(xiao)息(xi)  case BasePack.SYSTEM_PUSH_PACK:  {   SystemPushPack txtPack = new SystemPushPack();   txtPack.toUser = to;   txtPack.content = body;   txtPack.messageId = messageId;   target.acceptClient.Send(txtPack.PackToBytes());  }  break;  default:  {  }  break; } }}

     

     

    然後就是消(xiao)息(xi)隊列和php《-》c#間(jian)的調用(yong)問(wen)題

     

    1.嚴格按照p2p模型和pubSub模型的消(xiao)息(xi)隊列,即︰

    p2p模型︰ 如(ru)果消(xiao)息(xi)接受者的username在(zai)clients數組中,立即發送(song)標(biao),否(fu)則存入(ru)數據(ju)庫作為離(li)線消(xiao)息(xi),待該用(yong)戶登錄時再從(cong)數據(ju)庫取出該用(yong)戶的離(li)線消(xiao)息(xi)至內存中繼(ji)續發送(song),直(zhi)到收到相(xiang)應(ying)類型的ack或baseAck(客戶端的協議比服務器端低),從(cong)數據(ju)庫中徹底移除;

    pubSub 模型︰不huai)guan)消(xiao)息(xi)接受在(zai)clients數組中有(you)多(duo)少個(相(xiang)同(tong)的roomId標(biao)記),0到理論上限個,立即發送(song)且不需要(yao)回執且立即從(cong)內存中移除且不存入(ru)數據(ju)庫

     

    2.由(you)于php和c#程序是2個不同(tong)的進程,所以(yi)涉及到進程間(jian)通信(xin),如(ru)果這2個程序運行在(zai)同(tong)一台(tai)電腦(nao)上,可行的辦法有(you)︰共享內存、本地socket、管(guan)道等等??但是實(shi)際情(qing)況可能我(wo)們更希望web程序和消(xiao)息(xi)程序可以(yi)不在(zai)同(tong)一台(tai)電腦(nao),因此(ci)其他(ta)的方法︰共享同(tong)一個數據(ju)庫連接、http輪詢

    具(ju)體可以(yi)根據(ju)情(qing)況選擇,我(wo)這里(li)兩種都有(you)寫。

    且我(wo)ye)鈉諭hp每插入(ru)一條消(xiao)息(xi),c#馬(ma)上推送(song)出去(qu),那(na)麼c#做數據(ju)庫輪詢或者http輪詢其實(shi)都還(huai)好,我(wo)只用(yong)了一個線程做輪詢。

     

    最後今天寫下游戲(xi)端吧(ba)︰

    終于可以(yi)推各種包(bao)了,開(kai)始游戲(xi)包(bao)、出牌(pai)包(bao)、勝利包(bao) DAY1已經(jing)描述,目前在(zai)做的︰ 客戶端牌(pai)型校驗以(yi)及每一局中的每一輪何wen)迸卸 /p>

    這個游戲(xi)規則就是標(biao)準的跑(pao)得(de)快,也就是拿到黑(hei)桃(tao)3的玩家ye)諞瘓值(zhi)諞宦窒瘸讎pai),這里(li)還(huai)沒做,可以(yi)在(zai)所有(you)玩家收到開(kai)始游戲(xi)包(bao)之後做一個簡單(dan)的校驗。

    過牌(pai)直(zhi)ben)擁饔yong)出牌(pai)接口(kou),傳一個空的字符(fu)串即chun)桑 殼盎huai)沒有(you)主動過牌(pai)和結束每一輪zhi)穆嘸  雋私 懇瘓值(zhi)穆嘸  磁卸ㄊ?fu)。

    最後是幾個測試截圖,玩家id 45 和玩家 id 50玩了一局︰

     

About IT165 -廣(guang)告服務 -隱私聲明 -版權申明 -免責條款 -網(wang)站地圖 -網(wang)友投稿 -聯系方式
本站內容來自于互聯網(wang),僅供用(yong)于網(wang)絡技術學習,學習中請(qing)遵循相(xiang)關法律(lv)法規
大发11选5官网 | 下一页