一套完整的unity的socket网络通信模块

这里只讲一些主要内容,完整代码请到我的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)
	{
	}

主要内容就这些,有哪些不明白的地方可以留言,有哪些不足或者可以改进的地方还请多多指教 大笑

之后我会更新一片实际应用的文章,以及服务器端的相关内容。




  • 26
    点赞
  • 201
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值