• 【从零开始游戏开发】Unity 前后端网络通信该如何搭建?注释解答 | 全面总结 |建议收藏


    你知道的越多,你不知道的越多 🇨🇳🇨🇳🇨🇳
    点赞再看,养成习惯,别忘了一键三连哦 👍👍👍
    文章持续更新中 📝📝📝



    通信该如何搭建?

    服务器端:

    1. 入口类(Program):
    static void Main(string[] args)
    {
        //1. 构造网络服务类: 
        NetServer net = new NetServer();
        //2. 调用初始化方法: 
        net.Init();
        //3. 调用开始方法:
        net.Start();
        //4. 死循环处理指令的输入
        Run();
        //5. 当循环跳出关闭服务器 调用停止服务方法
        net.Stop();
    }
    
    public static void Run()
    {
        bool run = true;
        while (run)
        {
            //1. 处理输入的指令 输入 Exit 指令会关闭服务器
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    2. 网络服务器类(Net Server):
    public void Init()
    {
        //1. 绑定一个IP与端口号:
        IPEndPoint ipPoint = new IPEndPoint(IP, Port);
        //2. 构造一个监听类:
        ServerListener = new TcpSocketListener(ipPoint);
        //3. 给监听类绑定一个Socket连接事件:
        ServerListener.SocketConnected += OnSocketConnected;
    }
    
    public void Start()
    {
        //1. 调用监听类开始方法:
        Log.Warning("Starting Listener...");
        ServerListener.Start();
        
        //2. 调用消息分发器类开始函数
        MessageDistributer<NetConnection<NetSession>>.Instance.Start(8);
        Log.Warning("NetService Started");
    }
    
    public void Stop()
    {
        //1. 调用监听类停止方法
        ServerListener.Stop();
    }
    
    //监听类监听到了对象连接,最后执行此方法
    private void OnSocketConnected(object sender, Socket e)
    {
        //1. 获取连接的IP端地址 
        IPEndPoint clientIP = (IPEndPoint)e.RemoteEndPoint;
        //2. 构造一个 连接模型
        SocketAsyncEventArgs args = new SocketAsyncEventArgs();
        //3. 构造一个 NetSession
        NetSession session = new NetSession();
        //4. 构造一个 连接类 NetConnection
         NetConnection<NetSession> connection = new NetConnection<NetSession>()
    }
    
    //传递给连接类的接收数据事件
    void DataReceived(NetConnection<NetSession> sender, DataEventArgs e)
    {
        //打印一条信息
        Log.WarningFormat("Client[{0}] DataReceived Len:{1}", e.RemoteEndPoint, e.Length);
        //1. 处理接收到的数据
        lock (sender.packageHandler)
        {
            // 调用消息处理类的 接收数据方法
            sender.packageHandler.ReceiveData(e.Data, 0, e.Data.Length);
        }
    }
    
    //传递给连接类的断开连接事件
    void Disconnected(NetConnection<NetSession> sender, SocketAsyncEventArgs e)
    {
        //打印一条信息
        Log.WarningFormat("Client[{0}] Disconnected", e.RemoteEndPoint);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    3. 监听类(TCP Socket Listener)
    private Socket m_ListenSocket;		//监听Socket
    private SocketAsyncEventArgs args;  //连接的一种模型
    
    //构造函数
    public TcpSocketListener(IPEndPoint endPoint)
    {
        //1. 实例化连接模型
        args = new SocketAsyncEventArgs();
        //2. 加上监听事件,到监听到有对象连接会执行
        args.Completed += OnSocketAccepted; 
    }
    
    public void Start()
    {
        lock (this)	//加 Lock 是以为 此类会有多个客户端同时发起连接 
        {
            //1. 初始化Socket 绑定终端 然后 开始监听:
            m_ListenSocket.Listen(0);
            //2. 监听到有连接执行:
            BeginAccept(args);
        }        
    }
    
    public void Stop()
    {
    	lock (this)
        {
            if (listenerSocket == null)
                retutn;
            //1. 关闭监听套接字
            listenerSocket.Close();
            listenerSocket = null;
        }
    }
    
    private void BeginAccept(SocketAsyncEventArgs args)
    {
        //1. 制空 否则会报错 
        args.AcceptSocket = null;			
        //2. 得到监听到的对象
        m_ListenSocket.AcceptAsync(args); 
    }
    
    //监听事件
    private void OnSocketAccepted(object sender, SocketAsyncEventArgs e)
    {
        //1. 获取或设置异步套接字操作的结果。
        SocketError error = e.SocketError; 
        //容错处理
        if (e.SocketError == SocketError.OperationAborted) 
            retutn;
        //2. 连接成功
        if (e.SocketError == SocketError.Success)
        {
            //1. 获取到连接的套接字:
            Socket handler = e.AcceptSocket;
            //2. 执行连接事件:
            OnSocketConnected(handler);
        }
        lock (this)
        {
            BeginAccept(e);	//达到循环监听
        }
    }
    
    //连接事件
    private void OnSocketConnected(Socket client)
    {
        //1. 执行连接事件: 
        SocketConnected?.Invoke(this, client);
    }
    
    //定义连接事件 网络服务器类初始化时 将此事件进行了赋值
    public event EventHandler<Socket> SocketConnected;
    
    //析构函数 结束时
    ~TcpSocketListener()
    {
        Dispose(false);
    }
    
    //此类继承了 IDisposable 实现接口
    public void Dispose()
    {
        Dispose(true);
        //请求公共语言运行时不要调用指定对象的终结器。手动调用了Dispose释放资源,那么析构函数就是不必要的了,这里阻止GC调用析构函数
        GC.SuppressFinalize(this);
    }
    
    //处理结束函数 bool 参数,防止 析构函数清理资源一次 之后 Dispose 又一次清理
    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                Stop();
                if (args != null)
                    args.Dispose();
            }
            
            disposed = true;
        }       
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    4. 连接类(Net Connection <泛型类 T > )
    /* 1. 对此类的介绍
    每当监听类监听到有对象连接,执行了对应的事件,会构造一个当前类。
    当前为泛型类 泛型 T 为 NetSession 类,此类缓存了连接对象的数据。
    可以说是,每个连接的客户端都 对应了一个当前类,之后当前类会为客户端所服务。
    */
    public class NetConnection<T>
    {
        // 定义 接收数据的回调 (委托)
        public delegate void DataReceivedCallback(NetConnection<T> sender, DataEventArgs e);
        // 定义 断开连接的回调 (委托)
        public delegate void DisconnectedCallback(NetConnection<T> sender, SocketAsyncEventArgs e);
        
        internal class State
        {
            public DataReceivedCallback dataReceived;			//接收数据委托变量
            public DisconnectedCallback disconnectedCallback;	//断开连接委托变量
            public Socket socket;
        }
        
        // 异步套接字操作
        private SocketAsyncEventArgs eventArgs;
        
        // 消息处理类对象
        public PackageHandler<NetConnection<T>> packageHandler;
        
        // 存储玩家数据类的变量
        private T session;
        public T Session { get { return session; } }
        
        //构造函数
        public NetConnection(Socket socket, SocketAsyncEventArgs args, DataReceivedCallback dataReceived,DisconnectedCallback disconnectedCallback, T session)
        {
            lock (this)	// 因为类对 SocketAsyncEventArgs 此类进行操作时候 必须要加Lock 否则会出现报错
            {
                //构造一个 消息处理器
                this.packageHandler = new PackageHandler<NetConnection<T>>(this);
                State state = new State()
                {
                    //1. 持有对构造方传递过来的值
                    socket = socket,
                    dataReceived = dataReceived,
                    disconnectedCallback = disconnectedCallback
                }
                //1. 初始化
                eventArgs = new SocketAsyncEventArgs();
                //2. 对 eventArgs 进行赋值
                eventArgs.AcceptSocket = socket;			//连接的Socket
                eventArgs.Completed += ReceivedCompleted;	//添加接收数据事件
                eventArgs.UserToken = state;				//将 对象 缓存
                eventArgs.SetBuffer(new byte[64 * 1024],0, 64 * 1024);	//设置 接收字节缓冲区
                
                this.session = session;		//将类缓存
                //执行消息接收
                BeginReceive(eventArgs);	
            }
        }
        
        //接收消息方法
        private void BeginReceive(SocketAsyncEventArgs args)
        {
            lock (this)
            {
                //取得socket
                Socket socket = (args.UserToken as State).socket;
                //判断socket的连接状态
                if (socket.Connected)
                {
                    //开启异步请求,接收来自连接的数据
                    args.AcceptSocket.ReceiveAsync(args);
                    //当接收到了数据就会执行,此类构造时,赋值的接收数据事件
                }
            }
        }
        
    	//接收数据事件    
        private void ReceivedCompleted(Object sender, SocketAsyncEventArgs args)
        {
            // 判断接收到的字节数
    		if (args.BytesTransferred == 0)
            {
                CloseConnection(args); //Graceful disconnect
                return;
            }
            // 判断造成结果,是否成功
            if (args.SocketError != SocketError.Success)
            {
                CloseConnection(args); //NOT graceful disconnect
                return;
            }
            
            //1. 取得 对象  【这里又是对 UserToken 的应用】
            State state = args.UserToken as State;
            //2. 构建一个接收到的字节数长度的字节数组
            Byte[] data = new Byte[args.BytesTransferred];
            //3. 将 args 对象的字节数组内的数据 复制到 data
            Array.Copy(args.Buffer, args.Offset, data, 0, data.Length);
            //4. 执行接收到数据的事件
            OnDataReceived(data, args.RemoteEndPoint as IPEndPoint, state.dataReceived);
            //5. 达到循环接收
            BeginReceive(args);
        }
        
        //调用接收数据事件的方法  事件是网络服务器类 构造此类是 传递来的
        private void OnDataReceived(Byte[] data, IPEndPoint remoteEndPoint, DataReceivedCallback callback)
        {
            callback(this, new DataEventArgs() { RemoteEndPoint = remoteEndPoint, Data = data, Offset =0, Length = data.Length  });
        }
    	
        //断开连接的方法
        private void CloseConnection(SocketAsyncEventArgs args)
        {
            //1. 从对象中获取到 Socket的对象	【这里就体现出 UserToken 这个属性的作用,当然不止这些】
            State state = args.UserToken as State;
            Socket socket = state.socket;
            try
            {
                socket.Shutdown(SocketShutdown.Both);	//禁止掉 Socket 的接收和发送
            }catch { } // throws if client process has already closed
            //2. 关闭socket
            socket.Close();
            socket = null;
            //3. 去掉接收数据的事件,没有这步会出现报错
            args.Completed -= ReceivedCompleted; //MUST Remember This!
            //4. 执行断开连接的事件
            OnDisconnected(args, state.disconnectedCallback);
        }
        
        //断开连接的事件,第二个参数 在网络服务器类构造此类时传递的事件方法
        private void OnDisconnected(SocketAsyncEventArgs args, DisconnectedCallback callback)
        {
    		callback(this, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    5. 字节数组类(Data Event ARGs)
    public class DataEventArgs : EventArgs
    {
        public IPEndPoint RemoteEndPoint { get; set; }
        public Byte[] Data { get; set; }
        public Int32 Offset { get; set; }
        public Int32 Length { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    6. 消息包处理类(Package Handler)
    public class PackageHandler<T>
    {
        // 字节数据缓冲区
        private MemoryStream stream = new MemoryStream(64 * 1024);
        //字节读取索引
        private int readOffset = 0;
        
        //接收 数据方法,调用者:网络服务器类
        public void ReceiveData(byte[] data,int offset,int count)
        {
            //判断缓存区加上一定长度的数据会不会超过缓存的容量
            if(stream.Position + count > stream.Capacity)
            {
                throw new Exception("PackageHandler write buffer overflow");
            }
            //1. 将数据写入到 缓冲区
            stream.Write(data, offset, count);
            //2. 解析消息包方法
            ParsePackage();
        }
        
        //数据包解析方法
        bool ParsePackage()
        {
            /*这里为什么要加 4,这个判断很关键
            因为第一次接收 读取索引为0,加4可以判断,数据最基本的长度,如果这个都没有,那么这个包就没必要解析
            */
            if (readOffset + 4 < stream.Position)
            {
                //1. 获取包内容的大小
                int packageSize = BitConverter.ToInt32(stream.GetBuffer(), readOffset);
                //2. 这里的 读取索引readOffset,感觉加没加都一样,根据整个方法代码,这个变量在这的作用为0
                //但是这步判断很重要,包大小 加 4 如果小于等于成立,说明消息包是完整的,可解
                if (packageSize + readOffset + 4 <= stream.Position)
                {
                    //3. 调用通过字节提取消息类
                    Message.NetMessage message = UnpackMessage(stream.GetBuffer(), this.readOffset + 4, packageSize);
                    if (message == null)
                    {
                        throw new Exception("PackageHandler ParsePackage faild,invalid package");//容错处理
                    }
                    //4. 得到了 类,调用消息分发器传递该类,进行处理
                    MessageDistributer<T>.Instance.ReceiveMessage(this.sender, message);
                    //5. 将读取索引加上 整条完整数据长度
                    this.readOffset += (packageSize + 4);
                    //6. 返回这个类,是为了再次判断是否,有粘包,分包处理
                    return ParsePackage();
                }
            }
            
            //当消息接收处理过一次,读取索引必定大于0
            if (this.readOffset > 0)
            {
                //1. 这里是获取可能存在没有其他不完整的数据
                long size = stream.Position - this.readOffset;
                //2. 如果这个小于,说明缓冲区还有数据
                if (this.readOffset < stream.Position)
                {
                    Array.Copy(stream.GetBuffer(), this.readOffset, stream.GetBuffer(), 0, stream.Position - this.readOffset);
                }
                //3. Reset Stream
                this.readOffset = 0;
                stream.Position = size;
                stream.SetLength(size);
            }
            return true;//结束
        }
        
        //提取消息类
        public static Message.NetMessage UnpackMessage(byte[] packet,int offset,int length)
        {
            Message.NetMessage message = null;
            using (MemoryStream ms = new MemoryStream(packet, offset, length))
            {
                message = ProtoBuf.Serializer.Deserialize<Message.NetMessage>(ms);
            }
            return message;
        }
        
        
        // -------- 客户端专用构造方式 --------
        public class PackageHandler : PackageHandler<object>
        {
            public PackageHandler(object sender) : base(sender)
            {
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    7. 消息分发器类(MessageDistributer)单例类
    public class MessageDistributer<T> : Singleton<MessageDistributer<T>>
    {
        // 队列的消息类对象
        class MessageArgs
        {
            public T sender;
            public Message.NetMessage message;
        }
        // 消息类队列
        private Queue<MessageArgs> messageQueue = new Queue<MessageArgs>();
        
        // 自动重置事件,终止状态
        private AutoResetEvent threadEvent = new AutoResetEvent(true);
        
        // 线程执行状态
        private bool Running = false;
        
        // 开启的线程数量
        public int ThreadCount = 0;
        // 激活状态的线程数
        public int ActiveThreadCount = 0;
        
        // 处理消息包处理类传递的类  sender 是消息包处理类被构造时候,存放是 NetConnection 对象,
        public void ReceiveMessage(T sender ,Message.NetMessage message)
        {
            // 将消息添加到消息队列	MessageArgs
            this.messageQueue.Enqueue(new MessageArgs() { sender = sender, message = message });
            // 将事件状态设置为有信号,从而允许一个或多个等待线程继续执行。
            threadEvent.Set();
        }
        
        // 多线程模式消息处理开始函数,参数为 工作线程的数量
        public void Start(int ThreadNum)
        {
            this.ThreadCount = ThreadNum;
            // 进行限制
            if (this.ThreadCount < 1) this.ThreadCount = 1;
            if (this.ThreadCount > 1000) this.ThreadCount = 1000;
            // 激活线程执行状态
            Running = true;
            for (int i = 0; i < this.ThreadCount; i++)
            {
                // 调用 QueueUserWorkItem 方法以将方法排队以便在线程池线程上执行。 为此,可将方法传递给 WaitCallback 委托。 委托具有签名
                ThreadPool.QueueUserWorkItem(new WaitCallback(MessageDistribute));
            }
            while (ActiveThreadCount < this.ThreadCount)
            {
                // 工作状态的线程 小于 总线程,进入休眠	具体作用不得而知 =-=  可能只是为了减轻CPU压力哦~
                Thread.Sleep(100);
            }
        }
        
        // 消息处理线程
        private void MessageDistribute(Object stateInfo)
        {
            Log.Warning("MessageDistribute thread start");
            try
            {
                // 如果不使用 Increment 和,则在 Decrement 执行前两个步骤后,线程可以被抢占
                ActiveThreadCount = Interlocked.Increment(ref ActiveThreadCount);
                while (Running)
                {
                    if (this.messageQueue.Count  == 0)
                    {
                        // 消息队列没有消息时 阻止当前线程,直到当前 WaitHandle 收到信号。
                        threadEvent.WaitOne();
                        continue;
                    }
                    // 取出队列消息
                    MessageArgs package = this.messageQueue.Dequeue();
                    if (package.message.Request != null)
                    {
                        // 请求消息不为空,执行请求事件消息发送	用于客户端~
                        MessageDispatch<T>.Instance.Dispatch(package.sender, package.message.Request);
                    }
                    if (package.message.Response != null)
                    {
                        // 响应消息不为空,执行请求事件消息发送	用于服务端~
                        MessageDispatch<T>.Instance.Dispatch(package.sender, package.message.Response);
                    }
                }
                catch{}
                finally	//必会执行的代码块 0.0  Increment Decrement,就和有生就有死一个道理
                {
                    ActiveThreadCount = Interlocked.Decrement(ref ActiveThreadCount);
                    Log.Warning("MessageDistribute thread end");
                }
            }
        }
        
        // 停止多线程模式消息处理器
        public void Stop()
        {
            Running = false;
            this.messageQueue.Clear();
            while (ActiveThreadCount > 0)
            {
                threadEvent.Set();
            }
            Thread.Sleep(100);
        }
        
        // 定义的委托
        public delegate void MessageHandler<Tm>(T sender, Tm message);
        // 委托字典
        private Dictionary<string, System.Delegate> messageHandlers = new Dictionary<string, System.Delegate>();
        
        // 就一个false 变量
        public bool ThrowException = false;
        
        // 消息派发类调用此方法,此方法也是每条通信数据最后的一处理,会执行谁订阅的方法
        public void RaiseEvent<Tm>(T sender,Tm msg)
        {
            string key = msg.GetType().Name;
            if (messageHandlers.ContainsKey(key))
            {
                // 在委托字典通过key 取得对应的委托
                MessageHandler<Tm> handler = (MessageHandler<Tm>)messageHandlers[key];
                if (handler != null)
                {
                    try
                    {
                        handler(sender, msg);
                    }
                    catch (System.Exception ex)
                    {
                        Log.ErrorFormat("Message handler Fail");
                        // 也不知道这里要干嘛 =-=, 感觉没啥大作用
                        if (ThrowException)
                            throw ex;
                    }
                }
                else
                {
                    Log.Warning("No handler subscribed for {0}" + msg.ToString());
                }
            }
        }
        
        // 订阅事件
        public void Subscribe<Tm>(MessageHandler<Tm> messageHandler)
        {
            string type = typeof(Tm).Name;
            // 如果有相同的,则把之前的制空,重新赋值
            if (!messageHandlers.ContainsKey(type))
            {
                messageHandlers[type] = null;
            }
            messageHandlers[type] = (MessageHandler<Tm>)messageHandlers[type] + messageHandler;
        }
        
        // 有订阅 当然有注销
        public void Unsubscribe<Tm>(MessageHandler<Tm> messageHandler)
        {
            string type = typeof(Tm).Name;
            if (!messageHandlers.ContainsKey(type))
            {
                messageHandlers[type] = null;
            }
            messageHandlers[type] = (MessageHandler<Tm>)messageHandlers[type] - messageHandler;
        }
        
        
        //------- 下面区域是只有客户端才会用到的方法-------
        public void Clear()
        {
            // 清空接收消息存储的容器
            this.messageQueue.Clear();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    8. 消息派发器类(MessageDispatch)单例类
    // 在消息分发器类中,有订阅事件,此类只充当一个中介
    public class MessageDispatch<T> : Singleton<MessageDispatch<T>>
    {
        // 响应派发	RaiseEvent 消息分发器类中处理
        public void Dispatch(T sender, Message.NetMessageResponse message)
        {
            if (message.userRegister != null) { MessageDistributer<T>.Instance.RaiseEvent(sender, message.userRegister); }
        }
        
        // 请求派发
        public void Dispatch(T sender, SkillBridge.Message.NetMessageRequest message)
        {
            if (message.userRegister != null) { MessageDistributer<T>.Instance.RaiseEvent(sender,message.userRegister); }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    至此服务器端会用到的注释完毕

    客户端:

    网络类(NetClient)Mono单例类
    class NetClient : MonoSingleton<NetClient>
    {
        private IPEndPoint address;	 // 终端
        private Socket clientSocket;
        
        public bool running { get; set; }	// 网络服务器运行状态
        private bool connecting = false;	// 是否正在连接服务器
        
    	void Awake()
        {
            running = true;
        }
        
        // 初始化函数 率先绑定 终端
        public void Init(string serverIP, int port)
        {
            this.address = new IPEndPoint(IPAddress.Parse(serverIP), port);
        }
        
        // 连接服务器函数
        public void Connect(int times = DEF_TRY_CONNECT_TIMES)
        {
            if (this.connecting)
            {
                return;	//当前在连接中,直接跳出
            }
            
            if (this.clientSocket != null)
            {
                //socket这东西,先关掉,在使用,每次使用前,先关掉,为什么呢? 如果你不做断线重连就可以忽略
                this.clientSocket.Close();	
            }
            
            if (this.address == default(IPEndPoint))
            {
                throw new Exception("Please Init first.");
            }
            
            this.connecting = true; // 正在连接
            //真正执行连接
            this.DoConnect();
        }
    	
        // 开始连接
        void DoConnect()
        {
            Debug.Log("NetClient.DoConnect on " + this.address.ToString());
            try
            {
                if (this.clientSocket != null)
                {
                    this.clientSocket.Close();	//解释过
                }
                
                this.clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this.clientSocket.Blocking = true;	//开启阻塞
                
                Debug.Log(string.Format("Connect[{0}] to server {1}", this.retryTimes, this.address) + "\n");
                //得到一个异步连接的结果~
                IAsyncResult result = this.clientSocket.BeginConnect(this.address, null, null);
                bool success = result.AsyncWaitHandle.WaitOne(1000);//1000 是连接等待结果的时间
                if (success)
                {
                    // success 为真,说明连接这档事成了, EndConnect 接收结果后,clientSocket 就可以发送接收消息了
                    this.clientSocket.EndConnect(result);
                }
            }
            catch(SocketException ex)
            {
                // 连接被拒绝 为什么被拒绝,有点数的都知道被拉黑名单了 ( •̀ ω •́ )✧
                if(ex.SocketErrorCode == SocketError.ConnectionRefused)
                {
                    this.CloseConnection();
                }
                Debug.LogErrorFormat("DoConnect SocketException Fail");
            }
            catch (Exception e)
            {
                Debug.Log("DoConnect Exception:" + e.ToString() + "\n");
            }
            // 判断是否连接成功
            if (this.clientSocket.Connected)
            {
                this.clientSocket.Blocking = false;	//关掉阻塞
                //this.RaiseConnected(0, "Success"); 这条注释,是因为会执行一个事件,重连事件,属于功能性
            }
            else
            {
                // 进行重连的事情
            }
            this.connecting = false; //连接完毕
        }
      
        // 关闭连接 没有参数,因为参数只是打印出关闭的原因
        public void CloseConnection()
        {
            this.connecting = false;
            if (this.clientSocket != null)
            {
                this.clientSocket.Close();
            }
            
            //清空缓冲区
            MessageDistributer.Instance.Clear();	//因为这个类是公用的,断线就需要清理,此类最下面有客户端专用的方法区域
            this.sendQueue.Clear();	//清空发送消息队列
            
            this.receiveBuffer.Position = 0;	//接收消息的缓存区清0
            this.sendBuffer.Position = sendOffset = 0; //发送的缓冲区 和发送索引 清0
        }
        
        // 需要发送的消息队列
        private Queue<NetMessage> sendQueue = new Queue<NetMessage>();
        
        // 发送数据的缓冲区
        private MemoryStream sendBuffer = new MemoryStream();
        // 接收数据的缓冲区
        private MemoryStream receiveBuffer = new MemoryStream(64 * 1024);
        
        // 这个类的构造,看此类最下方
        public PackageHandler packageHandler = new PackageHandler(null);
        
        //发送消息
        public void SendMessage(NetMessage message)
        {
            if (!running)
            {
                return;
            }
            
            if (!this.Connected)
            {
                // 这里呢,就是你没了连接服务器 就调用此方法,这里会忽略掉你的请求,先连接服务器
                this.receiveBuffer.Position = 0;
                this.sendBuffer.Position = sendOffset = 0;
                this.Connect();
                Debug.Log("Connect Server before Send Message!");
                return;
            }
            // 将消息添加到队列
            sendQueue.Enqueue(message);
        }
        
        public void Update()
        {
            if (!running)
            {
                return; // 没有连接 跳出~
            }
            
            if (this.KeepConnect())
            {
                if (this.ProcessRecv())
                {
                    // 保存是连接状态
                    if (this.Connected)
                    {
                        this.ProcessSend();	//发送过程
                        MessageDistributer.Instance.Distribute();	//ProceeMessage(); 方法只有这一条代码 直接写了,处理派发~
                    }
                }
            }
        }
        
        // 发送过程
        bool ProcessSend()
        {
            bool ret = false;
            try
            {
                if (this.clientSocket.Blocking)
                {
                     Debug.Log("this.clientSocket.Blocking = true\n");
                }
                bool error = this.clientSocket.Poll(0, SelectMode.SelectError);
                if (error)
                {
                    Debug.Log("ProcessSend Poll SelectError\n");
                    this.CloseConnection();
                    return false;
                }
                // 检测是否可写,这个只要socket正常,就一定是真
                ret = this.clientSocket.Poll(0, SelectMode.SelectWrite);
                if (ret)
                {
                    // 第一次肯定不大于因为都是0,执行完后,这里就大于了
                    if (this.sendBuffer.Position > this.sendOffset)
                    {
                        // 得到大小
                        int bufsize = (int)(this.sendBuffer.Position - this.sendOffset);
                        // 发送
                        int n = this.clientSocket.Send(this.sendBuffer.GetBuffer(), this.sendOffset, bufsize, SocketFlags.None);
                        if (n <= 0)
                        {
                            this.CloseConnection();
                            return false;
                        }
                        this.sendOffset += n;
                        if (this.sendOffset >= this.sendBuffer.Position)
                        {
                            //清空一定要做 this.sendOffset += n; if (this.sendOffset >= this.sendBuffer.Position) 其实可以不要
                            this.sendOffset = 0;
                            this.sendBuffer.Position = 0;
                            this.sendQueue.Dequeue();
                        }
                    }
                    else
                    {
                        if (this.sendQueue.Count > 0)
                        {
                            // 上面说 可以不要 ,是因为 这里,每次只求一条
                            NetMessage message = this.sendQueue.Peek();
                            byte[] package = PackageHandler.PackMessage(message);
                            // 写入数据
                            this.sendBuffer.Write(package, 0, package.Length);
                        }
                    }
                }
            }
        }
        
        // 接收消息的过程方法
        bool ProcessRecv()
        {
            bool ret = false;
            try
            {
                if (this.clientSocket.Blocking)	//如果是阻塞的 就一直卡折,所以抛出个异常 提醒一下
                {
                    Debug.Log("this.clientSocket.Blocking = true\n");
                }
                bool error = this.clientSocket.Poll(0, SelectMode.SelectError);
                if (error)
                {
                    // 检测到错误 所以关闭
                    Debug.Log("ProcessRecv Poll SelectError\n");
                    this.CloseConnection();
                    return false;
                }
                
                ret = this.clientSocket.Poll(0, SelectMode.SelectRead);
                if (ret)
                {
                    // 检测到有可读的消息
                    int n = this.clientSocket.Receive(this.receiveBuffer.GetBuffer(), 0, this.receiveBuffer.Capacity, SocketFlags.None);
                    if (n <= 0)
                    {
                        //接收到的长度为0.所以关闭,有消息过来大小为零的只有 关闭请求/响应 =-=
                        this.CloseConnection();
                        return false;
                    }
                    
                    // 跟服务器端一样,接收数据的处理是一致的
                    this.packageHandler.ReceiveData(this.receiveBuffer.GetBuffer(), 0, n);
                }
            }
            catch (Exception e)
            {
                // 接收过程异常~
                Debug.Log("ProcessReceive exception:" + e.ToString() + "\n");
                this.CloseConnection();
                return false;
            }
            return true;
        }
        
        // 状态判断把,比较Update 一直在执行
        bool KeepConnect()
        {
            if (this.connecting)
    			return false;
            if (this.address == null)
                return false;
            if (this.Connected)
                return true;
            //if (this.retryTimes < this.retryTimesTotal)	this.Connect(); //这个还是断线重连的处理
            
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279

    🎁🌻🌼🌸 粉丝福利来喽 🎁🌻🌼🌸

    1. 免费领取海量资源 🎁
      简历自测评分表、Unity职级技能表、面试题库、入行学习路径等
    2. 《Unity游戏开发五天集训营 》50个名额 🎁
      我给大家争取到了 50个《游戏开发五天集训营 》名额,原价198,前50个免费
      扫码加入,暗号小听歌
      即可参加ARPG狼人战斗系统、饥荒生存类游戏开发、回合制RPG口袋妖怪游戏等游戏开发训练营
    3. 额外抽奖机会🎁
      参加游戏训练营、还有机会获得大厂老师在线面试指导、或者有机会获得价值1998元的《Unity极速入门与实战》课程
    🔻🔻🔻🔻 扫下方二维码,获取游戏开发福利,暗号小听歌 🔻🔻🔻🔻
  • 相关阅读:
    python-(6-3-3)爬虫---requests入门(对参数封装)
    centos7安装字体和中文字体
    脉冲神经网络原理及应用,脉冲神经网络的优缺点
    机器学习总结
    微服务架构必备技术栈:万变不离其宗的奥义!
    怎样录屏没有外界杂音?3个十分好用的方法,码住收藏!
    微信小程序的五种传值方式
    Factuality Challenges in the Era of Large Language Models
    JPA概述
    【中移芯昇】5. spi接口测试tf卡
  • 原文地址:https://blog.csdn.net/qq_21407523/article/details/127712630