• 熱門專題

開發日記:KBEngine+Unity+php做個撲克小游戲DAY2

作者:  發布日期:2016-11-14 20:23:55
Tag標簽:撲克  小游戲  日記  
  • 怎么辦,這很尷尬,為啥呢,因為kbe的某些原因讓我放棄了使用它所以本打算繼續更新的,說一下原因:

    在DAY1中我希望kbe能夠開啟一個http服務,并讓php端做一個web請求將消息傳遞給對應的用戶,可是這個http服務我是寫起來了,發送消息的函數也寫出來(花了不少時間,kbe的注釋和文檔都不多,特別是kbe把BaseHttpServer這個python庫另外弄了個名字,用http.server import as 才導入成功)尷尬的就是http服務和發消息的函數怎么也放不到一起:

    1.一旦某個class不繼承自KBEngine.Base,那么他就無法訪問KBEngine的幾乎所有靜態函數、屬性,就無法獲取到對應用戶的mailbox完成消息發送

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

    3.系統庫的HTTP服務會阻塞進程,這個文檔還是寫了,不過替代框架太麻煩,且調試太不方便,且語法太熟悉,且…………雖然我想說一萬個且,只能說明我無能啊…………

    當然論壇和官網當中也有人反應類似的問題,例如第三方接口訪問KBE的成員/屬性問題,不過看起來好像并沒有現成的解決方案,最后的最后。。。我放棄了

     

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

    消息協議采用http://msgpack.org/ 基本上支援所有的語言,因此實際上我這個消息服務器可以服務任何類型的客戶端,不管你啥平臺啥語言

     

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

    1.BasePack代表發送的包,BaseAckPack代表回執包,BaseAckPack繼承自BasePack

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

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

     

    using System;
    //send包
    namespace Packs
    {
        //基礎包
        public class BasePack<T>
        {
            public int packType;
            public int fromId;
            public int toId;
            public int messageId;
            //將基本包轉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空間:類型
                Buffer.BlockCopy(type, 0, dest, 0, type.Length);
    
                //第二個int空間:長度
                Buffer.BlockCopy(len, 0, dest, type.Length, len.Length);
                
                //剩余空間:包內容
                Buffer.BlockCopy(packContent, 0, dest, type.Length+len.Length, packContent.Length);
    
                Console.WriteLine("打包pack,類型:" + this.packType + "長度:" + packContent.Length);
    
                return dest;
            }
    
            //將bytes轉回基本包
            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數值轉換為占四個字節的byte數組,本方法適用于(低位在前,高位在后)的順序。  
    * @param value  
    *            要轉換的int值 
    * @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數值,本方法適用于(低位在前,高位在后)的順序。 
           *   
           * @param ary  
           *            byte數組  
           * @param offset  
           *            從數組的第offset位開始  
           * @return int數值  
           */
            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.登錄包
        public class LoginPack: BasePack<LoginPack>
        {
            public string username;
            public string password;
    
            public LoginPack()
            {
                this.packType = BasePack.LOGIN_PACK;
            }
    
        }
    
    
        //5.文字包
        public class TextPack:BasePack<TextPack>
        {
            public string content;
            public string toUser;
            public string fromUser;
    
            public TextPack() {
                this.packType = BasePack.TEXT_PACK;
            }
        }
    
        //6.系統推送包
        public class SystemPushPack: BasePack<SystemPushPack>
        {
            public string content;
            public string toUser;
      
            public SystemPushPack()
            {
                this.packType = BasePack.SYSTEM_PUSH_PACK;
            }
        }
    
       
    }
    


     

     

    3.server端Accept之后立即發送ConnectPack,客戶端收到后發送ConnectAckPack完成連接

     

     private void OnAccept()
        {
    
            while (this.isServing)
            {
                //異步Accept 回調ConnEnd
                //serverSocket.BeginAccept(new System.AsyncCallback(this.ConnEnd), null);
    
                //同步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.客戶端發送LoginPack(由于php已經校驗了用戶名和密碼并且生成了token,所以loginPack實際上我沒有寫校驗密碼的邏輯,單純的綁定用戶名,用來接收消息),server端拆開pack將用戶名綁定到客戶端對象中,這個對象的內容如下:

     

     

    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()
        {
    
        }
    }

     

     

    整個處理函數:

     

        private void OnReceive(object obj)
        {
    
            while (this.isServing)
            {
    
                ReceiveObject e = obj as ReceiveObject;
                Socket c = e.acceptClient;
                e.buffer = new byte[1024];
    
                //判斷包類型,固定包在包之前
                int type = c.Receive(e.buffer, 0, sizeof(Int32), SocketFlags.None);
                if (type == 0)
                {
    
                    Console.WriteLine("客戶端斷開連接:" + c.RemoteEndPoint.ToString());
    
                    //clients.RemoveAll((ReceiveObject obj) => { return obj.acceptClient == 0 ? true : false; });
                    clients.Remove(e);
                    cThreads.Remove(c.RemoteEndPoint.ToString());
                    Thread.CurrentThread.Abort();
                    //斷開連接
                    c.Shutdown(SocketShutdown.Both);
                    c.Close();
                    break;
    
                }
    
                type = BasePack.bytesToInt(e.buffer, 0);
                //獲得包大小,固定第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("收到登錄請求,用戶名:" + lPack.username + "密碼:" + lPack.password);
    
                            e.userName = lPack.username;
    
                            //發送登錄ACK
                            LoginACKPack loginACK = new LoginACKPack();
                            //loginACK.success = true;
    
                            c.Send(loginACK.PackToBytes());
                        }
                        break;
                    case BasePack.TEXT_PACK:
                        {
                            //處理消息包
                            TextPack pack = TextPack.BytesToPack(e.buffer);
                            //處理basePack
                            Console.WriteLine("發送給" + pack.toUser + "的消息:" + pack.content);
    
                            //從clients組找用戶
                            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:
                        {
                            //處理消息回執
                            TextACKPack pack = TextACKPack.BytesToPack(e.buffer);
    
                            //刪除對應的消息
    
                            pusher.DeleteMessageById(pack.messageId);
    
                        }
                        break;
                    case BasePack.SYSTEM_PUSH_ACK:
                        {
                            SystemPushACKPack pack = SystemPushACKPack.BytesToPack(e.buffer);
    
                            //刪除對應的消息
                            pusher.DeleteMessageById(pack.messageId);
                        }
                        break;
                    default:
                        //處理未知包
                        {
    
                        }
                        break;
                }
    
            }
        }

    發送消息函數,目前寫了2個case 原因:php端的推送類型很多,我直接寫在pushPack的content內部,客戶端用json解析開就行了,然后做了一個單聊的文本消息發送,按群組推還沒來得及做:

     

     

        public void SendMsg(string from, string to, string body, int type, int messageId)
        {
            //從clients組找用戶
            ReceiveObject target = clients.FindLast((ReceiveObject o) => { return o.userName == to ? true : false; });
            if (target == null) return;
            //推送一條消息至客戶端
            //收到回執后才能修改sent狀態為1
            Console.WriteLine("推送消息給:" + to + "類型:" + type + "內容:" + body + "id:" + messageId);
            switch (type)
            {
    
                //推送文字消息
                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;
                //系統消息
                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;
            }
        }
    }

     

     

    然后就是消息隊列和php《-》c#間的調用問題

     

    1.嚴格按照p2p模型和pubSub模型的消息隊列,即:

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

    pubSub 模型:不管消息接受在clients數組中有多少個(相同的roomId標記),0到理論上限個,立即發送且不需要回執且立即從內存中移除且不存入數據庫

     

    2.由于php和c#程序是2個不同的進程,所以涉及到進程間通信,如果這2個程序運行在同一臺電腦上,可行的辦法有:共享內存、本地socket、管道等等??但是實際情況可能我們更希望web程序和消息程序可以不在同一臺電腦,因此其他的方法:共享同一個數據庫連接、http輪詢

    具體可以根據情況選擇,我這里兩種都有寫。

    且我的期望是php每插入一條消息,c#馬上推送出去,那么c#做數據庫輪詢或者http輪詢其實都還好,我只用了一個線程做輪詢。

     

    最后今天寫下游戲端吧:

    終于可以推各種包了,開始游戲包、出牌包、勝利包 DAY1已經描述,目前在做的: 客戶端牌型校驗以及每一局中的每一輪何時判定。

    這個游戲規則就是標準的跑得快,也就是拿到黑桃3的玩家第一局第一輪先出牌,這里還沒做,可以在所有玩家收到開始游戲包之后做一個簡單的校驗。

    過牌直接調用出牌接口,傳一個空的字符串即可,目前還沒有主動過牌和結束每一輪的邏輯,做了結束每一局的邏輯,即判定勝負。

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

     

About IT165 - 廣告服務 - 隱私聲明 - 版權申明 - 免責條款 - 網站地圖 - 網友投稿 - 聯系方式
本站內容來自于互聯網,僅供用于網絡技術學習,學習中請遵循相關法律法規
湖北快三走势图ydb| 4wb| cp4| qri| w4x| d4u| cqc| 5rr| gi5| buf| y5u| vwo| 5nn| jl3| cug| g3y| zsa| 4sr| 4cd| un4| dfh| r4y| vwf| 4ht| cv2| nqc| j3n| vxh| m3i| zhs| 3vp| 3xj| no3| dmg| i3i| jtx| 4xr| oz2| gzt| g2a| tug| 2tf| bu2| coa| vwi| t3t| kcw| 3do| hj1| dsu| f1r| ikf| 1dx| ts1| wpi| xp2| jlg| jox| p2k| skn| 0wl| ex0| cmb| r0v| gqc| 1cw| ya1| mfm| j1w| sub| 1qk| 1dv| np9| bla| h0l| isv| 0wz| gz0| csn| g0s| uni| 0en| ak0| poa| 9su| pr9| wxa| d9q| lnh| 9fa|