找回密碼
 注冊帳號

掃一掃,訪問微社區

開果 一個簡單 小型的C#Socket網絡通信庫的制作(服務器客戶端互通)(下)

22
回復
861
查看
打印 上一主題 下一主題
[ 復制鏈接 ]

6

主題

10

帖子

100

積分

Rank: 9Rank: 9Rank: 9

UID
327626
好友
1
蠻牛幣
65
威望
0
注冊時間
2019-7-19
在線時間
72 小時
最后登錄
2019-8-21

馬上注冊,結交更多好友,享用更多功能,讓你輕松玩轉社區。

您需要 登錄 才可以下載或查看,沒有帳號?注冊帳號

x
大家好我是匠人團隊的開果

接著上篇繼續講



1.KGNetSession socket的會話管理  就是管理接收/發送 數據消息的

這里流程是StartReciveData()開啟了連接異步監聽了回調ReciveHeadData(IAsyncResult)這個回調是用來判斷出包長然后進行實際數據消息包異步回調處理ReciveData(IAsyncResult)

這里分兩個異步接收主要是處理socket的粘包和分包情況,

大概的思維圖






粘包的話是當消息頻繁一起時候 socket有可能把他們打包一起發了過來,
分包 是當消息過長 socket會把他們分成兩或多個個包分幾次發過來 就會出現包不完整情況

為啥ReciveHeadData只接收 四字節嘞,因為 定義包長的數據就站byte四個字節 所以只接收這幾個  socket的話一次沒接收完他會把余下的繼續分包發的 所以接收完 得到這個包的長度就知道接下來要接收包數據要多長   

ReciveData(IAsyncResult)接收到之后 就進行了消息處理回調函數  OnReciveData(T)   然后進行新一輪監聽了,如果這時候 socket 出現粘包還沒接完 就會繼續分包發過來重復這一輪操作就好  就可以處理掉粘包的情況
連接關閉時候 會觸發OnDisRecive
如果是強行關閉的話會觸發一次監聽異步的回調 如果還是調用EndReceive就會報錯所以前面要加個if(mSocket.Available==0)判斷是否還有數據

[C#] 純文本查看 復制代碼
     /// <summary>
        /// 單個的網絡會話管理
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public abstract  class KGNetSession<T> where T:KGNetData
        {
            public Socket mSocket;
    
    
    
            public event Action<T>  OnReciveDataEvent;//接收到的數據委托事件
            public event Action OnDisReciveEvent;//關閉會話的事件
            public event Action OnStartReciveEvent;//首次開啟連接的事件
    
            public KGNetPacket mKGNetPacket=new KGNetPacket();
    
            /// <summary>
            /// 這里開始數據接收
            /// </summary>
            /// <param name="socket"></param>
            /// <param name="close"></param>
            public void StartReciveData(Socket socket,Action close=null)
            {
                try
                {
                   // 初始化賦值
                    mSocket = socket;
                    OnDisReciveEvent+= close;
    
                    //回調開啟連接事件
                    OnStartRecive();
                    //首先是接收頭4個字節確認包長
                    mKGNetPacket.PacketBuff = new byte[4];
                    mSocket.BeginReceive(mKGNetPacket.PacketBuff, 0, 4, SocketFlags.None, ReciveHeadData, null);
                }
                catch (Exception e)
                {
                    ("StartReciveDataError:" + e).KLog(LogLevel.Err);
    
                }
              
            }
    
            /// <summary>
            /// 這里是判斷接收標頭的就是長度
            /// </summary>
            /// <param name="ar"></param>
            protected void ReciveHeadData(IAsyncResult ar)
            {
              
                try
                {
    
                    //如果接收數據長度等于0就是斷開連接了 
                    //為啥要加這個勒 在斷開的時候 異步會回調一次 直接調用EndReceive 會報錯
                    if (mSocket.Available == 0)
                    {
                        Clear();
                        return;
                    }
                    
                    int len = mSocket.EndReceive(ar);
                    if (len > 0)
                    {
                        mKGNetPacket.HeadIndex += len;
                        //這里如果是小于4的就是湊不成 就是分包了 要繼續接收
                        if (mKGNetPacket.HeadIndex < mKGNetPacket.HeadLength)
                        {
    
                            mSocket.BeginReceive(mKGNetPacket.PacketBuff, mKGNetPacket.HeadIndex, mKGNetPacket.HeadLength - mKGNetPacket.HeadIndex, SocketFlags.None, ReciveHeadData, null);
                        }
                        //這里已經取出長度了
                        else
                        {
                            //賦值從那四個字節獲取的byte[]的長度
                            mKGNetPacket.SetPackLen();
                            //進行真正的數據接收處理
                            mSocket.BeginReceive(mKGNetPacket.PacketBuff, 0, mKGNetPacket.PacketBuffLength, SocketFlags.None, ReciveData, null);
                        }
                    }
                    else
                    {
                      
                        Clear();
                        
                    }
                }
                catch (Exception e)
                {
    
                    ("ReciveHeadDataError:" + e).KLog(LogLevel.Err);
                }
               
             
            }
    
            /// <summary>
            /// 這里是接收到包里面的數據異步處理
            /// </summary>
            /// <param name="ar"></param>
            protected void ReciveData(IAsyncResult ar)
            {
                try
                {
                    //結束接收獲取長度
                    int len = mSocket.EndReceive(ar);
                    if (len>0)
                    {
                        mKGNetPacket.PacketIndex += len;
    
                        //這里是如果接收到的包長和原本獲取到的長度小,就是分包了 需要再次進行接收剩下的
                        if (mKGNetPacket.PacketIndex < mKGNetPacket.PacketBuffLength)
                        {
    
                            mSocket.BeginReceive(mKGNetPacket.PacketBuff, mKGNetPacket.PacketIndex, mKGNetPacket.PacketBuffLength - mKGNetPacket.PacketIndex, SocketFlags.None, ReciveData, null);
                        }
                        //已經接完一組數據了
                        else
                        {
                            //這里就可以進行回調函數了
                            OnReciveData(mKGNetPacket.PacketBuff.DeSerialization<T>());
    
                            //開始新一輪的從上往下接收了
                            mKGNetPacket.PacketBuff = new byte[4];
                            mSocket.BeginReceive(mKGNetPacket.PacketBuff, 0, 4, SocketFlags.None, ReciveHeadData, null);
                        }
                    }
                    else
                    {
                        Clear();
                    }
    
                }
                catch (Exception e)
                {
                    ("ReciveDataError:" + e).KLog(LogLevel.Err);
                }
            
            }
    #region Send
    
            /// <summary>
            /// 發送消息
            /// </summary>
            /// <param name="data"></param>
            public void SendData(T data)
            {
                //這里轉回來 byte[]
                byte[] bytedata = data.PackNetData();
    
                //創建流準備異步寫入發送
                NetworkStream network = null;
    
                try
                {
                    //指定寫入的socket
                    network = new NetworkStream(mSocket);
    
                    //判斷是否可以支持寫入消息
                    if (network.CanWrite)
                    {
                        //開始異步寫入
                        network.BeginWrite(bytedata,0, bytedata.Length, SendDataAsync, network);
                    }
                }
                catch (Exception e)
                {
    
                    ("SendDataError:" + e).KLog(LogLevel.Err);
                }
    
            }
    
            /// <summary>
            /// 這里是異步寫入回調
            /// </summary>
            /// <param name="ar"></param>
            protected void SendDataAsync(IAsyncResult ar)
            {
                //拿到寫入時候的流
                NetworkStream network = (NetworkStream)ar.AsyncState;
                try
                {
                    //結束寫入 就是發送了  然后進行關閉流
                    network.EndWrite(ar);
                    network.Flush();
                    network.Close();
                  
                }
                catch (Exception e)
                {
                    ("SendDataAsyncError:" + e).KLog(LogLevel.Err);
                }
            }
            #endregion
        /// <summary>
        /// 網絡關閉
        /// </summary>
            protected void Clear()
            {
                OnDisRecive();
                mSocket.Close();
            }
    
    
            protected virtual void OnReciveData(T data)
            {
                OnReciveDataEvent?.Invoke(data);
                ("接收到了一條消息:"+data).KLog();
            }
    
            protected virtual void OnDisRecive()
            {
                OnDisReciveEvent?.Invoke();
                ("關閉了一個連接:").KLog();
            }
    
            protected virtual void OnStartRecive()
            {
                OnStartReciveEvent?.Invoke();
                ("開始了一個連接:").KLog();
            }
    
    
        }



2.KGBaseNet  KGSocketClient/KGSocketServe的父類

就提了一下共用部分




[C#] 純文本查看 復制代碼
      public abstract class KGBaseNet
        {
            public Socket mSocket;
    
            public KGBaseNet()
            {
                //這里就是new一個socket出來 指定地址類型 和套接字類型( 就是傳送數據類型),還有協議
                mSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            }
    
            //開啟建立
            public abstract void StartCreate(string ip,int port);
    
            //建立的回調
            public abstract void ConnectAsync(IAsyncResult ar);
    
            //打印的調用
            public void SetLog(Action<string, LogLevel> LogEvent,bool run=true)
            {
                LogEvent.SetLog(run);
            }
        }




3.KGSocketClient 建立客戶端的

好像沒啥好說的 看代碼吧

[C#] 純文本查看 復制代碼
       /// <summary>
        /// 建立客戶端的
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="R"></typeparam>
        public  class KGSocketClient<T, R> : KGBaseNet where T : KGNetSession<R>, new() where R : KGNetData
        {
            public T Client;
    
            public override void StartCreate(string ip, int port)
            {
    
                try
                {
                    Client = new T();
                    //異步連接服務器
                    mSocket.BeginConnect(IPAddress.Parse(ip), port, ConnectAsync, Client);
                    ("正在連接服務器").KLog();
                }
                catch (Exception e)
                {
                    ("StartCreateError:" + e).KLog(LogLevel.Err);
    
                }
               
    
            }
            public override void ConnectAsync(IAsyncResult ar)
            {
                try
                {
                    mSocket.EndConnect(ar);
                    //連接完成開始接收數據
                    Client.StartReciveData(mSocket,()=> { Client = null; });
                
                }
                catch (Exception e)
                {
                    ("ConnectAsyncError:" + e).KLog(LogLevel.Err);
                    
                }
             
            }
    
          
        }



4.KGSocketClient 建立服務器端的


[C#] 純文本查看 復制代碼
      public  class KGSocketServe<T, R> : KGBaseNet where T : KGNetSession<R>, new() where R : KGNetData
        {
            public List<T> SessionList=new List<T>();//儲存會話管理的
            public  int NetListen=10;//監聽數
    
    
    
            public override void StartCreate(string ip, int port)
            {
                try
                {
                    //綁定地址
                    mSocket.Bind(new IPEndPoint(IPAddress.Parse(ip),port));
                    //監聽數
                    mSocket.Listen(NetListen);
                    //進行異步監聽
                    mSocket.BeginAccept(ConnectAsync, null);
                    ("建立服務器........").KLog();
                }
                catch (Exception e)
                {
                    ("StartCreateError:" + e).KLog(LogLevel.Err);
    
                }
            }
    
            //異步回調
            public override void ConnectAsync(IAsyncResult ar)
            {
              
                try
                {
                    T Client = new T();
                    //這里結束接收 獲取剛剛連接的socket
                  Socket sk=  mSocket.EndAccept(ar);
    
                    //開始監聽  第二個是加入結束事件
                    Client.StartReciveData(sk,
                        ()=> 
                        {
                            SessionList.Remove(Client);
                        });
                    //添加進SessionList儲存
                    SessionList.Add(Client);
                    //開始新一輪接收連接
                    mSocket.BeginAccept(ConnectAsync, null);
                }
                catch (Exception e)
                {
                    ("ConnectAsyncError:" + e).KLog(LogLevel.Err);
    
                }
            }
        }




使用方法

這里大概說一下   下一篇會出個游戲例子



1.要繼承的
KGNetData 和KGNetSession都要新建一個類繼承才能用


2.網絡消息自定義類

都必須繼承KGNetData 然后打上可序列化標簽[Serializable]

[C#] 純文本查看 復制代碼
    [Serializable]
    public class NetData : KGNetData {
        public string dataname;
    }



3.創建客戶端/服務器端


[C#] 純文本查看 復制代碼
   //  KGSocketClient<KGNetSession<KGNetData>, KGNetData> kg =    new KGSocketClient<KGNetSession<NetData>, NetData>();
                KGSocketServe<KGNetSession<KGNetData>, KGNetData> kg=    new KGSocketServe<KGNetSession<NetData>,NetData>();
    
           //都是調用這個創建
                kg.StartCreate("127.0.0.1", 8897);


4.發送數據

必須繼承KGNetSession

[C#] 純文本查看 復制代碼
就是調用KGNetSession里面的SendData(T)

    kg.Client.SendData(new KGNetData { dataname = "123456" });



5.接收網絡消息

這里留了一個回調事件和回調函數OnReciveDataEvent/OnReciveData  

重寫OnReciveData  就好了  如果別的要加事件可以往OnReciveDataEvent加

     protected override void OnReciveData(T data)
            {
                OnReciveDataEvent?.Invoke(data);
                ("接收到了一條消息:"+data).KLog();
            }


6.打印數據的


[C#] 純文本查看 復制代碼
在KGBaseNet里面的   這里是給 在另外一些 Console.WriteLine打印不了留出來用的 

            public void SetLog(Action<string, LogLevel> LogEvent,bool run=true)
            {
                LogEvent.SetLog(run);
            }



好了 基本完了    下一篇弄個游戲示例去





工程地址
游客,如果您要查看本帖隱藏內容請回復





回復

使用道具 舉報

5熟悉之中
945/1000
排名
2706
昨日變化

7

主題

43

帖子

945

積分

Rank: 5Rank: 5

UID
252015
好友
0
蠻牛幣
1509
威望
0
注冊時間
2017-11-1
在線時間
357 小時
最后登錄
2019-8-20
沙發
2019-7-30 11:57:54 只看該作者
學習666666666666666
回復 支持 反對

使用道具 舉報

7日久生情
2115/5000
排名
4092
昨日變化

0

主題

1391

帖子

2115

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蠻牛幣
1915
威望
0
注冊時間
2017-11-16
在線時間
362 小時
最后登錄
2019-8-21
板凳
2019-7-30 16:25:49 只看該作者
6666666666666666666666666666
回復 支持 反對

使用道具 舉報

4四處流浪
461/500
排名
10818
昨日變化

1

主題

60

帖子

461

積分

Rank: 4

UID
241225
好友
0
蠻牛幣
134
威望
0
注冊時間
2017-9-4
在線時間
304 小時
最后登錄
2019-8-10
地板
2019-7-31 09:38:07 只看該作者
正好想了解下服務器
回復 支持 反對

使用道具 舉報

7日久生情
4073/5000
排名
142
昨日變化

0

主題

377

帖子

4073

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
2484
好友
2
蠻牛幣
4135
威望
0
注冊時間
2013-8-23
在線時間
1358 小時
最后登錄
2019-8-21
5#
2019-7-31 11:04:06 只看該作者
66666666666666666666學習了
回復 支持 反對

使用道具 舉報

5熟悉之中
653/1000
排名
3655
昨日變化

2

主題

24

帖子

653

積分

Rank: 5Rank: 5

UID
57809
好友
0
蠻牛幣
1251
威望
0
注冊時間
2014-11-26
在線時間
217 小時
最后登錄
2019-8-9
6#
2019-7-31 14:14:51 只看該作者
66666666666666666
回復 支持 反對

使用道具 舉報

排名
48135
昨日變化

2

主題

15

帖子

56

積分

Rank: 2Rank: 2

UID
216657
好友
0
蠻牛幣
107
威望
0
注冊時間
2017-4-7
在線時間
35 小時
最后登錄
2019-8-20
7#
2019-7-31 19:16:13 只看該作者
bcjdksbvsdkjibvb
回復 支持 反對

使用道具 舉報

7日久生情
2390/5000
排名
479
昨日變化

2

主題

97

帖子

2390

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
69739
好友
0
蠻牛幣
3056
威望
0
注冊時間
2015-1-20
在線時間
749 小時
最后登錄
2019-8-2
8#
2019-8-1 09:02:33 只看該作者
學習學習
回復

使用道具 舉報

7日久生情
4291/5000
排名
86
昨日變化

1

主題

439

帖子

4291

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
28000
好友
0
蠻牛幣
3983
威望
0
注冊時間
2014-6-4
在線時間
1205 小時
最后登錄
2019-8-21
9#
2019-8-1 10:20:34 只看該作者
謝謝~~~~~~~~~~~~~
回復

使用道具 舉報

4四處流浪
491/500
排名
10224
昨日變化

0

主題

49

帖子

491

積分

Rank: 4

UID
299336
好友
0
蠻牛幣
243
威望
0
注冊時間
2018-10-9
在線時間
336 小時
最后登錄
2019-8-21
10#
2019-8-2 15:41:18 只看該作者
6666666666
回復

使用道具 舉報

排名
64935
昨日變化

0

主題

30

帖子

81

積分

Rank: 2Rank: 2

UID
259926
好友
0
蠻牛幣
174
威望
0
注冊時間
2017-12-16
在線時間
49 小時
最后登錄
2019-8-21
11#
2019-8-5 17:06:47 只看該作者
謝謝!!!!!!!!!!!!
回復

使用道具 舉報

7日久生情
2200/5000
排名
596
昨日變化

1

主題

272

帖子

2200

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
1438
好友
4
蠻牛幣
6754
威望
0
注冊時間
2013-8-2
在線時間
491 小時
最后登錄
2019-8-21
12#
2019-8-6 09:43:18 只看該作者
趕緊回復學習一下,謝謝樓主分享
回復 支持 反對

使用道具 舉報

7日久生情
2542/5000
排名
588
昨日變化

1

主題

89

帖子

2542

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
32995
好友
0
蠻牛幣
3701
威望
0
注冊時間
2014-7-7
在線時間
1058 小時
最后登錄
2019-8-6
QQ
13#
2019-8-6 09:49:07 只看該作者
很好的學習資料,感謝分享
回復 支持 反對

使用道具 舉報

5熟悉之中
765/1000
排名
10706
昨日變化

0

主題

507

帖子

765

積分

Rank: 5Rank: 5

UID
301976
好友
1
蠻牛幣
1140
威望
0
注冊時間
2018-10-31
在線時間
160 小時
最后登錄
2019-8-21
14#
2019-8-6 09:59:59 只看該作者
Nice 。。。  值得學習,自己太菜
回復 支持 反對

使用道具 舉報

7日久生情
2542/5000
排名
588
昨日變化

1

主題

89

帖子

2542

積分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
32995
好友
0
蠻牛幣
3701
威望
0
注冊時間
2014-7-7
在線時間
1058 小時
最后登錄
2019-8-6
QQ
15#
2019-8-6 10:04:29 只看該作者
感謝分享
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 注冊帳號

本版積分規則

捕鱼王怎么进不去 免费麻将单机 转让黑龙江时时机器 擼一擼久久蝌蚪窩網站 火龙果分分彩计划软件手机版 蔡花宝典三肖六码 3肖6码 爱乐网 幸运pk10玩法技巧 时时彩技巧论坛 北京pk10全天稳定计划 时时彩计划后二下载 扑克牌三公玩法与技巧 球探网即时比分手机 时时彩技巧后一稳赢 网上押大小 三牛发手机登录平台 广东时时走势图百度百度贴吧