Socket框架
Socket通信使用同步时 若有多个客户端同时连接服务端 每次都开启一条线程来接收客户端的消息并返回结果 显然消耗过大 所有有必要进行优化 可以采用多线程(接收线程 发送线程 其他处理线程)和异步Socket 使用异步Socket时 封装CommonSocket 无论服务端或者客户端都可用的通用Socket; NetMsgObject类用来处理消息的解析 将一条完整的消息解析为消息头+消息尾 消息头用来辨别不同消息处理 封装返回NetMsgObject类型的数据便于消息处理 封装Client客户端(Init(实例Socket 绑定ip和端口)Connect(BeginConnect开始连接)SendMsg(假发送 只是把数据压到了发送队列中)UpdateSendMsg真正将数据发送BeginSend  RecvMsg(将接收到从服务端返回的消息压入接收队列中)UpdateRecvMsg真正将数据取出BeginReceive);封装Server服务端(Init(实例Socket 绑定ip和端口)Accept(开始接受客户端的连接BeginAccept)UpdateRecvFromClient(实时接收客户端数据)UpdateSendToClient(广播给所有客户端))

音游 服务端支持多人连接 根据用户操作播放不同音乐及闪光灯效 用户输入不互斥音效可组合 停止操作时灯灭音乐停止 客户端输入使用NetWorkStream的BeginWrite和BeginRead写入和读取 基于客户端的不同输入 服务端根据消息类型作出处理 执行相应操作(随机灯光的颜色 开启线程睡眠0.1s实现缓变的效果)获取AudioSource组件的clip属性加载音效 Play方法播放音效

黏包 当客户端发送的数据量小时将多条完整或不完整的消息合并 黏在一次发送给服务端 服务端收到了一整段数据(多条消息)所以在接收端定义NetMsgObject(定义int值记录消息长度(4+Head+Body) Head消息头为辨别消息的消息类型用short表示占2字节 Body消息尾)Receive接收到客户端发来黏包数据的总长度Length 首先判断Length是否小于6 如果小于6(消息长度+消息头)说明接收到残包 压入队列 等待下次接收到完数据处理;如果大于6则转为NetMsgObject取到消息长度len 如果len<Length总长度 则接收字节数组中从0到len是完整的一条消息可做处理 处理下条消息时从第一条消息长度的len-1开始解析
分包 当客户端发送的数据量大时将消息分割为几部分 多次发送给服务端 服务端不止接收一次

状态同步 相较于帧同步误差大 基于客户端的输入向服务端发送消息后客户端完成自走逻辑 服务端接收到消息后 每隔固定时间发送确认包(确认当前服务端上该角色具体正确的属性和位置);当服务端返回消息时 客户端有可能只接受不处理 客户端移动结束后 这时服务端会发送强制包到客户端 强制包中有服务端中存储该角色的属性和位置 通过对比数据 如果在设定的允许范围内 客户端不做改变;如果不一致则强制保持一致(平滑移动或强制位移)

阻塞与非阻塞(Blocking&Non-Blocking)
客户端与服务端为例 服务端等待客户端发来的一条新的消息 但是服务端关闭了客户端Socket 服务端却一直在等待客户端的消息  此时服务端线程阻塞 因为服务端的accept方法一直在等待新消息的到来 阻塞卡住时的两种情况 发送时(Send)比如你要按下某键才发送消息时 只有要发送的数据传到了Send函数中 才会继续向下执行;接收时(Recv) 接收一方将数据放到缓存区(byte[])中 只有收到数据放入缓存区后 才会向下执行;解决阻塞:同步+多线程(异步)
非阻塞:服务端仍在接收客户端的消息(RecvCallBack) 但无论发来新消息还是没发来(空消息)都立刻返回 不会一直将线程挂在接收函数的调用上 异步非阻塞解决了阻塞的问题 所以Socket(Client/Server结构)多用异步非阻塞
同步与异步(Sync&Async)
同步与异步和阻塞与非阻塞相互联系但又不等同 同步有可能导致阻塞 异步解决阻塞 同步异步主要针对client(同步是死等结果;异步是有结果了通知我)阻塞非阻塞主要针对Server(阻塞是被调用时没数据不返回;非阻塞是被调用不管如何立即返回)

///CLient
using System;
using System.Text;
using System.Net.Sockets;
using UnityEngine;namespace QSocket
{
    public delegate void QAsyncCallback(NetMsgObject obj);
    public class Client
    {
        public CommonSocket CSocket;
        public QAsyncCallback asyncCallbak;
        public void RegAsyncCallback(QAsyncCallback callback)
        {
            asyncCallbak += callback;
        }
        //初始化,最开始必须先执行该函数
        public void Init()
        {
            RegAsyncCallback(ProcessMsg.Process);
            CSocket = new CommonSocket();
            CSocket.Init();
        }
        public void Start(string ip, short port)
        {
            CSocket.Start(ip, port);
        }
        //当作为单个客户端的时候才会调用该函数
        public void Connet()
        {
            CSocket.socket.BeginConnect(CSocket.IP, CSocket.Port, AsyncConnetCallBack, CSocket);
        }
        
        //该函数当做为服务端接收函数ACCEPT接收的客户端的时候才会调用。
        public void RecvProcess(CommonSocket s)
        {
            //利用非阻塞的接收数据函数调用接口来接收数据
            //一旦收到数据则调用该接口对应的自己手写的回调函数AsyncRecvCallBack
            s.socket.BeginReceive(s.RecvBuf, 0, s.RecvBuf.Length, SocketFlags.None, AsyncRecvCallBack, s);
        }
                // 作为单个客户端时调用该函数在UPDATE或者FIXEDUPDATE
        public void UpdateRecvMsg()
        {
            while (CSocket.RecvQueue.Count > 0)
            {
                NetMsgObject o = CSocket.RecvQueue.Dequeue();
                asyncCallbak(o);
            }
        }
        public void UpdateSendMsg()
        {
            while (CSocket.SendQueue.Count > 0)
            {
                NetMsgObject o = CSocket.SendQueue.Dequeue();
                if (CSocket.RecvBuf.Length == o.len)
                {                }
                CSocket.socket.BeginSend(o.msgdata, 0, o.msgdata.Length, SocketFlags.None, AsyncSendCallBack, CSocket);
            }
        }
        //假发送,压数据进入发送队列
        public void SendMsg(MsgCode code, string strContent)
        {
            NetMsgObject o = new NetMsgObject();
            byte[] b =o.Package(BitConverter.GetBytes((short)code), Encoding.UTF8.GetBytes(strContent));
            o.len = b.Length + 4;
            o.msgcode = code;
            o.msgdata = b;
            CSocket.SendPush(o);
        }
        //当作为单个客户端并且调用了connet函数的时候才会调用该函数
        //作为服务端接收进来的客户端的时候是不调用该函数的
        public void AsyncConnetCallBack(IAsyncResult ar)
        {
            CommonSocket tmpCSocket = (CommonSocket)ar.AsyncState;
            tmpCSocket.socket.EndConnect(ar);
            tmpCSocket.socket.BeginReceive(tmpCSocket.RecvBuf, 0, tmpCSocket.RecvBuf.Length, SocketFlags.None, AsyncRecvCallBack, tmpCSocket);
            Debug.Log("成功连接到服务器");
        }
        //真正将消息发送出去
        //该函数一定要在继承mono的类里面在生命周期函数
        //FixedUpdate或者Update里面执行
            public void AsyncRecvCallBack(IAsyncResult ar)
            {
                CommonSocket tmpCSocket = (CommonSocket)ar.AsyncState;
                int len = tmpCSocket.socket.EndReceive(ar);
                if (len > 0)
                {
                    //通过接收到的字节数据
                    //创建一个新的netmsgobject对象
                NetMsgObject o = NetMsgObject.Create(tmpCSocket.RecvBuf);
                //将新创建的对象压入到队列中
                tmpCSocket.RecvQueue.Enqueue(o);
            }
            tmpCSocket.socket.BeginReceive(tmpCSocket.RecvBuf, 0, tmpCSocket.RecvBuf.Length, SocketFlags.None, AsyncRecvCallBack, tmpCSocket);
        }
        //Send回调函数
        public void AsyncSendCallBack(IAsyncResult ar)
        {
            CommonSocket tmpCSocket = (CommonSocket)ar.AsyncState;
            int len = tmpCSocket.socket.EndSend(ar);
            if (len > 0)
            {
                Debug.Log("成功发送" + len + "个字节到服务端");
            }
        }
    }
}//客户端服务端通用Socket
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;namespace QSocket
{
    //通用Socket 称之为端(不分服务端还是客户端)
    public class CommonSocket
    {
        public Socket socket;
        public string IP;
        public short Port;
        public IPEndPoint IEP;
        public byte[] SendBuf;
        public byte[] RecvBuf;
        public Queue<NetMsgObject> RecvQueue;
        public Queue<NetMsgObject> SendQueue;
        public const int MaxBufferSize = 4096;        public void Init()
        {
            IP = "127.0.0.1";
            Port = 8888;
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            SendBuf = new byte[MaxBufferSize];
            RecvBuf = new byte[MaxBufferSize];
            RecvQueue = new Queue<NetMsgObject>();
            SendQueue = new Queue<NetMsgObject>();
            Start();
        }
        public void SetSocket(Socket s)
        {
            socket = s;
        }
        //开始一个IP的绑定
        public void Start()
        {
            IEP = new IPEndPoint(IPAddress.Parse(IP), Port);
        }
        //重载
        public void Start(string ip, short port)
        {
            SetIP(ip, port);
            IEP = new IPEndPoint(IPAddress.Parse(IP), Port);
        }
        //设置绑定IP
        public void SetIP(string ip, short port)
        {
            IP = ip;
            Port = port;
        }
        public void SendPush(NetMsgObject o)
        {
            SendQueue.Enqueue(o);
        }
        public void RecvPush(NetMsgObject o)
        {
            RecvQueue.Enqueue(o);
        }
    }
} ///消息类
using System;
namespace QSocket
{
    public class NetMsgObject
    {
        public MsgCode msgcode;
        public byte[] msgdata;
        public int len; 
        //通用取头
        public static short GetHead(byte[] b)
        {
            byte[] a = new byte[2];
            a[0] = b[0];
            a[1] = b[1];
            short ms = BitConverter.ToInt16(a, 0);
            return ms;
        }
        public static byte[] GetBody(byte[] b)
        {
            byte[] a = new byte[b.Length - 2];
            Array.Copy(b, 2, a, 0, b.Length - 2);
            return a;
        }        public static NetMsgObject Create(byte[] b)
        {
            NetMsgObject nmo = new NetMsgObject();
            nmo.msgcode = (MsgCode)GetHead(b);
            nmo.msgdata = GetBody(b);
            nmo.len = GetHead(b) + GetBody(b).Length + 4;
            return nmo;
        }
        public byte[] Package(byte[] code, byte[] data)
        {
            byte[] head = code;
            byte[] body = data;
            byte[] ret = new byte[head.Length + body.Length];
            //复制head到ret从0的位置开始复制head.length个字节
            Array.Copy(head, 0, ret, 0, head.Length);
            //复制body到ret从head.length的位置开始复制body.length个字节
            Array.Copy(body, 0, ret, head.Length, body.Length);
            return ret;
        }    }
}///Server
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;namespace QSocket
{
    public class Server
    {
        public CommonSocket CSocket;
        public const int MaxClientCount = 999;
        public Dictionary<string, Client> customClinttList;        public void Init()
        {
            customClinttList = new Dictionary<string, Client>();
            CSocket = new CommonSocket();
            CSocket.Init();
            CSocket.Start("127.0.0.1", 8888);
            CSocket.socket.Bind(CSocket.IEP);
            CSocket.socket.Listen(MaxClientCount);
            Accept();
        }
        public void SetIP(string ip, short port)
        {
            CSocket.SetIP(ip, port);
        }
        public void Start(string ip, short port)
        {
            CSocket.Start(ip, port);
        }
        public void Bind()
        {
            CSocket.socket.Bind(CSocket.IEP);
        }
        public void Listen()
        {
            CSocket.socket.Listen(MaxClientCount);
        }
        public void StartServer()
        {
            CSocket.Init();
            Debug.Log("初始化完毕");
            CSocket.socket.Bind(CSocket.IEP);
            Debug.Log("绑定完毕");
            CSocket.socket.Listen(MaxClientCount);
            Debug.Log("开始监听");
            Accept();
        }
        public void Accept()
        {
            Debug.Log("开始接收客户端");
            CSocket.socket.BeginAccept(AsyncAcceptCallback, CSocket);
            Debug.Log("等待客户端连接中...");
        }
        public void AsyncAcceptCallback(IAsyncResult ar)
        {
            //利用传进来的socket做endaccept
            CommonSocket s = (CommonSocket)ar.AsyncState;
            //接收连接进来的客户端
            Socket c = s.socket.EndAccept(ar);
            //创建一个新的客户端
            Client client = new Client();
            //初始化
            client.Init();
            //对该客户端对象进行赋值
            client.CSocket.IEP = (IPEndPoint)c.RemoteEndPoint;
            client.CSocket.socket = c;
            client.RecvProcess(client.CSocket);
            //将当前的创建好的客户端加入到客户端队列中
            AddNewClient(client);
            CSocket.socket.BeginAccept(AsyncAcceptCallback, CSocket);
        }
        //压队列
        public void PushMsgToAllClient()
        {
            if (customClinttList.Count > 0)
            {
                foreach (var item in customClinttList.Values)
                {
                    item.SendMsg(MsgCode.Reg, "0");
                }
            }
        }
        public void UpdateSendToClient()
        {
            if (customClinttList.Count > 0)
            {
                foreach (var item in customClinttList.Values)
                {
                    item.UpdateSendMsg();
                }
            }
        }
        public void UpdateRecvFromClient()
        {
            if (customClinttList.Count > 0)
            {
                foreach (var item in customClinttList.Values)
                {
                    item.UpdateRecvMsg();
                }
            }
        }
        public void AddNewClient(Client c)
        {
            if (customClinttList.ContainsKey(c.CSocket.socket.RemoteEndPoint.ToString()))
                customClinttList[c.CSocket.socket.RemoteEndPoint.ToString()] = c;
            else
                customClinttList.Add(c.CSocket.socket.RemoteEndPoint.ToString(), c);
        }    }
}