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);
} }
}