这里只讲一些主要内容,完整代码请到我的github里下载:https://github.com/LiuFeng1011/UnityNetWork/tree/master/Assets/Code/Net
SocketHelper类
主要的通信类,socket的管理放在这里
下面说一下一些主要的方法
1.连接服务器,这个都写了比较详细的注释,一看就会明白
/// <summary>
/// 连接服务器
/// </summary>
/// <returns>The connect.</returns>
/// <param name="serverIp">Server ip.</param>
/// <param name="serverPort">Server port.</param>
/// <param name="connectCallback">Connect callback.</param>
/// <param name="connectFailedCallback">Connect failed callback.</param>
public void Connect(string serverIp,int serverPort,ConnectCallback connectCallback,ConnectCallback connectFailedCallback){
connectDelegate = connectCallback;
connectFailedDelegate = connectFailedCallback;
//采用TCP方式连接
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//服务器IP地址
IPAddress address = IPAddress.Parse(serverIp);
//服务器端口
IPEndPoint endpoint = new IPEndPoint(address,serverPort);
//异步连接,连接成功调用connectCallback方法
IAsyncResult result = socket.BeginConnect(endpoint, new AsyncCallback(ConnectedCallback), socket);
//这里做一个超时的监测,当连接超过5秒还没成功表示超时
bool success = result.AsyncWaitHandle.WaitOne(5000, true);
if (!success)
{
//超时
Closed();
if(connectFailedDelegate != null){
connectFailedDelegate();
}
}
else
{
//与socket建立连接成功,开启线程接受服务端数据。
isStopReceive = false;
Thread thread = new Thread(new ThreadStart(ReceiveSorket));
thread.IsBackground = true;
thread.Start();
}
}
2.发送数据
首先从发送队列中取出要发送的数据,然后对数据进行序列化,转为2进制数据,然后在数据最前端加上数据的长度,以便于服务器进行粘包的处理。
private void Send(){
if(socket == null){
return;
}
if (!socket.Connected)
{
Closed();
return;
}
try
{
Request req = sendDataQueue.Dequeue();
DataStream bufferWriter = new DataStream(true);
req.Serialize(bufferWriter);
byte[] msg = bufferWriter.ToByteArray();
byte[] buffer = new byte[msg.Length + 4];
DataStream writer = new DataStream(buffer, true);
writer.WriteInt32((uint)msg.Length);//增加数据长度
writer.WriteRaw(msg);
byte[] data = writer.ToByteArray();
IAsyncResult asyncSend = socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendCallback), socket);
bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true);
if (!success)
{
Closed();
}
}
catch (Exception e)
{
Debug.Log("send error : " + e.ToString());
}
}
3.接收数据
接收数据是一个独立的线程,连接服务器成功后开启的。如果接收到数据,会把数据放进DataHolder进行处理,然后根据DataHolder判断是否接收到完整数据,以解决粘包等问题。
这里由于接收到数据时并没有在主线程中,所以我们先把数据放入接收队列,在主线程的update方法中,对数据进行解析以及分发工作。
private void ReceiveSorket(){
mDataHolder.Reset();
while(!isStopReceive){
if (!socket.Connected)
{
//与服务器断开连接跳出循环
Debug.Log("Failed to clientSocket server.");
socket.Close();
break;
}
try
{
//接受数据保存至bytes当中
byte[] bytes = new byte[4096];
//Receive方法中会一直等待服务端回发消息
//如果没有回发会一直在这里等着。
int i = socket.Receive(bytes);
if (i <= 0)
{
socket.Close();
break;
}
mDataHolder.PushData(bytes, i);
while(mDataHolder.IsFinished()){
dataQueue.Enqueue(mDataHolder.mRecvData);
mDataHolder.RemoveFromHead();
}
}
catch (Exception e)
{
Debug.Log("Failed to clientSocket error." + e);
socket.Close();
break;
}
}
}
这里处理接收和发送队列的数据
//接收到数据放入数据队列,按顺序取出
void Update(){
if(dataQueue.Count > 0){
Resp resp = ProtoManager.Instance.TryDeserialize(dataQueue.Dequeue());
}
if(sendDataQueue.Count > 0){
Send();
}
}
DataHolder
此类用来管理接收到的数据,并处理粘包和丢包的情况,主要就是根据数据长度对数据进行裁切。
通过此方法保存收到的服务器数据,放入数据缓存中
public void PushData(byte[] data, int length)
{
if (mRecvDataCache == null)
mRecvDataCache = new byte[length];
if (this.Count + length > this.Capacity)//current capacity is not enough, enlarge the cache
{
byte[] newArr = new byte[this.Count + length];
mRecvDataCache.CopyTo(newArr, 0);
mRecvDataCache = newArr;
}
Array.Copy(data, 0, mRecvDataCache, mTail + 1, length);
mTail += length;
}
此方法判断接收到的数据是否完整,这里需要服务器在数据头4位装入数据长度,如果接收到完整数据,则将数据复制出一份供外部获取。
public bool IsFinished()
{
if (this.Count == 0)
{
//skip if no data is currently in the cache
return false;
}
if (this.Count >= 4)
{
DataStream reader = new DataStream(mRecvDataCache, true);
packLen = (int)reader.ReadInt32();
if (packLen > 0)
{
if (this.Count - 4 >= packLen)
{
mRecvData = new byte[packLen];
Array.Copy(mRecvDataCache, 4, mRecvData, 0, packLen);
return true;
}
return false;
}
return false;
}
return false;
}
如果接收到了完整的数据,则调用此方法将完整数据从数据缓存移除
public void RemoveFromHead()
{
int countToRemove = packLen + 4;
if (countToRemove > 0 && this.Count - countToRemove > 0)
{
Array.Copy(mRecvDataCache, countToRemove, mRecvDataCache, 0, this.Count - countToRemove);
}
mTail -= countToRemove;
}
ProtoManager
用来管理所有协议,进行消息的解析以及分发。
所有Resp需要在这里注册,以便接收到数据时进行解析
public void AddProtocol<T>(int protocol) where T: Resp, new()
{
if (mProtocolMapping.ContainsKey(protocol))
{
mProtocolMapping.Remove(protocol);
}
mProtocolMapping.Add(protocol,
(stream) => {
T data = new T();
data.Deserialize(stream);
return data;
});
}
这里注册相应协议的代理,在接收到服务器数据后,会调用这里注册过的相应代理
/// <summary>
/// 添加代理,在接受到服务器数据时会下发数据
/// </summary>
/// <param name="protocol">Protocol.</param>
/// <param name="d">D.</param>
public void AddRespDelegate(int protocol,responseDelegate d){
List<responseDelegate> dels ;
if (mDelegateMapping.ContainsKey(protocol))
{
dels = mDelegateMapping[protocol];
for(int i = 0 ; i < dels.Count ; i ++){
if(dels[i] == d){
return;
}
}
}else{
dels = new List<responseDelegate>();
mDelegateMapping.Add(protocol,dels);
}
dels.Add(d);
}
接收到服务器数据后,服务器会调用此方法对数据进行解析并调用相应的代理方法
public Resp TryDeserialize(byte[] buffer)
{
DataStream stream = new DataStream(buffer, true);
int protocol = stream.ReadSInt32();
Resp ret = null;
if (mProtocolMapping.ContainsKey(protocol))
{
ret = mProtocolMapping[protocol](stream);
if(ret != null){
if(mDelegateMapping.ContainsKey(protocol)){
List<responseDelegate> dels = mDelegateMapping[protocol];
for(int i = 0 ; i < dels.Count ; i ++){
dels[i](ret);
}
}
}
}else{
Debug.Log("no register protocol : " + protocol +"!please reg to RegisterResp.");
}
return ret;
}
DataStream
此类进行发送以及接收的数据流的处理,包含了数据大小端的设置以及数据流的读取和写入方法。
Request
基础的请求消息,所有要发送的请求消息继承此类
子类必须实现协议获取的方法,返回此请求所对应的协议
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
序列化方法
public virtual void Serialize(DataStream writer)
{
writer.WriteSInt32(GetProtocol());
writer.WriteByte(0);
}
Resp
基础的返回消息,所有服务器返回的消息继承此类
子类必须实现协议获取的方法,返回此请求所对应的协议
public virtual int GetProtocol(){
Debug.LogError("can't get Protocol");
return 0;
}
反序列化方法
public virtual void Deserialize(DataStream reader)
{
}
主要内容就这些,有哪些不明白的地方可以留言,有哪些不足或者可以改进的地方还请多多指教
之后我会更新一片实际应用的文章,以及服务器端的相关内容。