找回密码
 注册帐号

扫一扫,访问微社区

开果 一个简单 小型的C#Socket网络通信库的制作(服务器客户端互通)(下)

27
回复
1105
查看
打印 上一主题 下一主题
[ 复制链接 ]

14

主题

23

帖子

184

积分

Rank: 9Rank: 9Rank: 9

UID
327626
好友
5
蛮牛币
133
威望
0
注册时间
2019-7-19
在线时间
133 小时
最后登录
2019-9-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熟悉之中
963/1000
排名
2706
昨日变化

7

主题

45

帖子

963

积分

Rank: 5Rank: 5

UID
252015
好友
0
蛮牛币
1604
威望
0
注册时间
2017-11-1
在线时间
373 小时
最后登录
2019-9-20
沙发
2019-7-30 11:57:54 只看该作者
学习666666666666666
回复 支持 反对

使用道具 举报

7日久生情
2232/5000
排名
4093
昨日变化

0

主题

1491

帖子

2232

积分

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

UID
254705
好友
1
蛮牛币
2017
威望
0
注册时间
2017-11-16
在线时间
379 小时
最后登录
2019-9-20
板凳
2019-7-30 16:25:49 只看该作者
6666666666666666666666666666
回复 支持 反对

使用道具 举报

4四处流浪
482/500
排名
10819
昨日变化

1

主题

65

帖子

482

积分

Rank: 4

UID
241225
好友
0
蛮牛币
141
威望
0
注册时间
2017-9-4
在线时间
321 小时
最后登录
2019-9-21
地板
2019-7-31 09:38:07 只看该作者
正好想了解下服务器
回复 支持 反对

使用道具 举报

7日久生情
4093/5000
排名
142
昨日变化

0

主题

378

帖子

4093

积分

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

UID
2484
好友
2
蛮牛币
4222
威望
0
注册时间
2013-8-23
在线时间
1377 小时
最后登录
2019-9-20
5#
2019-7-31 11:04:06 只看该作者
66666666666666666666学习了
回复 支持 反对

使用道具 举报

5熟悉之中
660/1000
排名
3655
昨日变化

2

主题

25

帖子

660

积分

Rank: 5Rank: 5

UID
57809
好友
0
蛮牛币
1268
威望
0
注册时间
2014-11-26
在线时间
223 小时
最后登录
2019-9-20
6#
2019-7-31 14:14:51 只看该作者
66666666666666666
回复 支持 反对

使用道具 举报

排名
48139
昨日变化

2

主题

19

帖子

74

积分

Rank: 2Rank: 2

UID
216657
好友
0
蛮牛币
160
威望
0
注册时间
2017-4-7
在线时间
49 小时
最后登录
2019-9-20
7#
2019-7-31 19:16:13 只看该作者
bcjdksbvsdkjibvb
回复 支持 反对

使用道具 举报

7日久生情
2401/5000
排名
479
昨日变化

2

主题

104

帖子

2401

积分

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

UID
69739
好友
0
蛮牛币
3115
威望
0
注册时间
2015-1-20
在线时间
753 小时
最后登录
2019-9-17
8#
2019-8-1 09:02:33 只看该作者
学习学习
回复

使用道具 举报

7日久生情
4330/5000
排名
86
昨日变化

1

主题

455

帖子

4330

积分

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

UID
28000
好友
0
蛮牛币
3949
威望
0
注册时间
2014-6-4
在线时间
1228 小时
最后登录
2019-9-20
9#
2019-8-1 10:20:34 只看该作者
谢谢~~~~~~~~~~~~~
回复

使用道具 举报

5熟悉之中
541/1000
排名
10224
昨日变化

0

主题

51

帖子

541

积分

Rank: 5Rank: 5

UID
299336
好友
0
蛮牛币
255
威望
0
注册时间
2018-10-9
在线时间
384 小时
最后登录
2019-9-20
10#
2019-8-2 15:41:18 只看该作者
6666666666
回复

使用道具 举报

2初来乍到
100/150
排名
64937
昨日变化

0

主题

30

帖子

100

积分

Rank: 2Rank: 2

UID
259926
好友
0
蛮牛币
241
威望
0
注册时间
2017-12-16
在线时间
70 小时
最后登录
2019-9-21
11#
2019-8-5 17:06:47 只看该作者
谢谢!!!!!!!!!!!!
回复

使用道具 举报

7日久生情
2210/5000
排名
596
昨日变化

1

主题

274

帖子

2210

积分

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

UID
1438
好友
4
蛮牛币
6900
威望
0
注册时间
2013-8-2
在线时间
499 小时
最后登录
2019-9-19
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熟悉之中
896/1000
排名
10707
昨日变化

0

主题

623

帖子

896

积分

Rank: 5Rank: 5

UID
301976
好友
1
蛮牛币
1342
威望
0
注册时间
2018-10-31
在线时间
175 小时
最后登录
2019-9-20
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 只看该作者
感谢分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则