网络开发——Unity中Socket的使用

Socket的使用

项目中如果需要快发开发Socket可以直接SuperSocket或者FastSocket开源框架,开发过程中只需要关注应用层方面的代码即可。当然为了更加自由方便的扩展自己的开发需求,还是有必要从零开始搭建一个自己的网络框架。
开发前的基本理论知识可以去百度搜索一下,这边就直接专注于使用和封装Socket。

public class NetWorkSocket : SingletonMono<NetWorkSocket>
{
	/// <summary>
    /// 客户端Socket
    /// </summary>
    private Socket m_Client;
    // private byte[] buffer = new byte[10240];
    /// <summary>
    /// 压缩数组的长度界限
    /// </summary>
    private const int m_CompressLen = 200;
    #region 发送消息变量
    /// <summary>
    /// 发送消息队列
    /// </summary>
    private Queue<byte[]> m_SendQueue = new Queue<byte[]>();

    /// <summary>
    /// 检查队列委托
    /// </summary>
    private Action m_CheckSendQueue;


    #endregion
    #region 接收数据所需变量
    /// <summary>
    /// 接受数据包字节缓存区
    /// </summary>
    private byte[] m_ReceiveBuffer = new byte[2048 * 10];
    /// <summary>
    /// 接收数据包的缓存数据流
    /// </summary>
    private MemoryStreamBuffer m_ReceiveMS = new MemoryStreamBuffer();
    /// <summary>
    /// 接受消息的队列
    /// </summary>
    private Queue<byte[]> m_ReceiveQueue = new Queue<byte[]>();

    private int m_ReceiveCount = 0;
    #endregion
    public Action OnConectOk;
	protected override void OnAwake()
    {
        base.OnAwake();
    }
    protected override void OnStart()
    {
        base.OnStart();
    }
    protected override void BeforeOnDestroy()
    {
        base.BeforeOnDestroy();
        DisConnected();
    }
    public void DisConnected()
    {
        if (m_Client != null && m_Client.Connected)
        {
            m_Client.Shutdown(SocketShutdown.Both);
            m_Client.Close();
        }
    }
}

使用继承Mono的单例模式,简单的处理一下NetWorkSocket 的生命周期。

数据发送

发送数据到服务器时把数据添加到消息队列中,并通过一个委托检测消息队列,如果队列长度大于0,数据出栈后发送数据后继续检测队列,完成循环发送数据。

public void SendMsg(byte[] data)
{
    --------------------------------------
    //string result = string.Empty;   //StringBuilder more batter 
    //for (int i = 0; i < data.Length; i++)
    //{
    //    result += data[i].ToString("X2") + "  ";
    //}
    //Debug.Log(result);
    //Debug.Log("发送data---------");
    //--------------------------------------
	//数据 加密
    byte[] sendBuffer = MakeData(data);   
    lock (m_SendQueue)
    {
        m_SendQueue.Enqueue(sendBuffer);
        m_CheckSendQueue.BeginInvoke(null, null);
    }
}
#endregion
#region Send真发送数据包的服务器
/// <summary>
/// 真发送数据包的服务器
/// </summary>
/// <param name="buffer"></param>
private void Send(byte[] buffer)
{
    m_Client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallBack, m_Client);
}

private void SendCallBack(IAsyncResult ar)
{
   m_Client.EndSend(ar);

   OnCheckSendQueueCallBack();
}
#endregion

/// <summary>
/// 检查队列委托的回调
/// </summary>
private void OnCheckSendQueueCallBack()
{
    //如果队列中有数据包 则发送数据包
    if (m_SendQueue.Count > 0)
    {
        Send(m_SendQueue.Dequeue());
    }
}

发送数据的数据是二进制数组,这个数据通过是采用Protobuf或者Json等常用网络格式序列化生成的,然后通过组合的方式将 数据包长度 + 数据包体 组成形成 数据包。中间也可以加入一些字段标识字段,例如发送消息ID,服务器根据ID响应,避免一些外挂发送假数据。当然 数据包体 也是需要加密的。

提供一个常用加密方式:

  1. 判断是否压缩数据
  2. 数据进行异或运行
  3. crc校验
  4. 压缩 flag 和 CRC 校验值 写入数据包

数据接收

数据接收过程中常出现数据粘包,所以需要将数据放入到缓存流中循环拆包

private void ReceiveCallBack(IAsyncResult ar)
{
    try
    {
        int len = m_Client.EndReceive(ar);
        if (len > 0)
        {
        	//已经接收到数据
            //把接受到数据 写入缓冲数据流尾部
            m_ReceiveMS.Position = m_ReceiveMS.Length;
            //把指定长度的字节写入数据流
            m_ReceiveMS.Write(m_ReceiveBuffer, 0, len);

            //如果缓存>2说明至少有一个不完整的包发送过来了,客户端定义Ushort就是2
            if (m_ReceiveMS.Length > 2)
            {
                //循环 拆分包
                while (true)
                {
                    //把数据流指针位置放在0
                    m_ReceiveMS.Position = 0;
                    //包体的长度
                    int currMsgLen = m_ReceiveMS.ReadUShort();
                    //总包的长度
                    int currFullMsgLen = 2 + currMsgLen;
                    //如果缓存流的数据》=整包,说明至少接收有一个完整
                    if (m_ReceiveMS.Length >= currFullMsgLen)
                    {
                        //定义包体的数组
                        byte[] buffer = new byte[currMsgLen];
                        //把数据流指针位置放在2,也就是包体的位置
                        m_ReceiveMS.Position = 2;
                        //把数据流读到数组里buffer也就是我们要的数据
                        m_ReceiveMS.Read(buffer, 0, currMsgLen);
                        lock (m_ReceiveQueue)
                        {
                            m_ReceiveQueue.Enqueue(buffer);
                        }
                        //===========================处理剩余字节====================================
                        //剩余字节
                        int reMainLen = (int)m_ReceiveMS.Length - currFullMsgLen;
                        if (reMainLen > 0)
                        {
                            m_ReceiveMS.Position = currFullMsgLen;
                            byte[] reMainBuffer = new byte[reMainLen];
                            m_ReceiveMS.Read(reMainBuffer, 0, reMainLen);
                            //清空数据流
                            m_ReceiveMS.Position = 0;
                            m_ReceiveMS.SetLength(0);
                            //把剩余字节重新写入数据流
                            m_ReceiveMS.Write(reMainBuffer, 0, reMainBuffer.Length);
                            reMainBuffer = null;
                        }
                        else
                        {
                            //清空数据流
                            m_ReceiveMS.Position = 0;
                            m_ReceiveMS.SetLength(0);
                            break;
                        }
                    }
                    else
                    {
                        //还没有收到完整的包
                        break;
                    }
                    System.Threading.Thread.Sleep(1);
                }
            }
            ReceiveNsg();
        }
        else
        {
            //客户端断开
            Debug.Log(string.Format("服务器{0}断开连接", m_Client.RemoteEndPoint.ToString()));
        }
    }
    catch
    {
        Debug.Log(string.Format("服务器{0}断开连接", m_Client.RemoteEndPoint.ToString()));
    }
}

在Update中将数据包解密后通过消息处理中心分发

int newCrc = Crc16.CalculateCrc16(newBuffer);
//传过来的crc是否=新包的crc
if (newCrc == crc)
{
    //异或原始数据
    newBuffer = SecurityUtil.Xor(newBuffer);
    if (isCompress)
    {
        newBuffer = ZlibHelper.DeCompressBytes(newBuffer);
    }

	//协议编号
    ushort protoCode = 0;
    byte[] protoConent = new byte[buffer.Length - 2];
    using (MemoryStreamBuffer ms = new MemoryStreamBuffer(newBuffer))
    {
        protoCode = ms.ReadUShort();
        ms.Read(protoConent, 0, protoConent.Length);
        SocketDispatcher.Instance.Dispatch(protoCode, protoConent);
    }
}

这边也分享一个数据流的封装代码

public class MemoryStreamBuffer : MemoryStream
{
    public MemoryStreamBuffer()
    {

    }

    public MemoryStreamBuffer(byte[] buffer) : base(buffer)
    {

    }

    #region Short
    /// <summary>
    /// 从流中读取一个short数据
    /// </summary>
    /// <returns></returns>
    public short ReadShort()
    {
        byte[] arr = new byte[2];
        base.Read(arr, 0, 2);
        return BitConverter.ToInt16(arr, 0);
    }

    /// <summary>
    /// 把一个short数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteShort(short value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region UShort
    /// <summary>
    /// 从流中读取一个ushort数据
    /// </summary>
    /// <returns></returns>
    public ushort ReadUShort()
    {
        byte[] arr = new byte[2];
        base.Read(arr, 0, 2);
        return BitConverter.ToUInt16(arr, 0);
    }

    /// <summary>
    /// 把一个ushort数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteUShort(ushort value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region Int
    /// <summary>
    /// 从流中读取一个int数据
    /// </summary>
    /// <returns></returns>
    public int ReadInt()
    {
        byte[] arr = new byte[4];
        base.Read(arr, 0, 4);
        return BitConverter.ToInt32(arr, 0);
    }

    /// <summary>
    /// 把一个int数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteInt(int value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region UInt
    /// <summary>
    /// 从流中读取一个uint数据
    /// </summary>
    /// <returns></returns>
    public uint ReadUInt()
    {
        byte[] arr = new byte[4];
        base.Read(arr, 0, 4);
        return BitConverter.ToUInt32(arr, 0);
    }

    /// <summary>
    /// 把一个uint数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteUInt(uint value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region Long
    /// <summary>
    /// 从流中读取一个long数据
    /// </summary>
    /// <returns></returns>
    public long ReadLong()
    {
        byte[] arr = new byte[8];
        base.Read(arr, 0, 8);
        return BitConverter.ToInt64(arr, 0);
    }

    /// <summary>
    /// 把一个long数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteLong(long value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region ULong
    /// <summary>
    /// 从流中读取一个ulong数据
    /// </summary>
    /// <returns></returns>
    public ulong ReadULong()
    {
        byte[] arr = new byte[8];
        base.Read(arr, 0, 8);
        return BitConverter.ToUInt64(arr, 0);
    }

    /// <summary>
    /// 把一个ulong数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteULong(ulong value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region Float
    /// <summary>
    /// 从流中读取一个float数据
    /// </summary>
    /// <returns></returns>
    public float ReadFloat()
    {
        byte[] arr = new byte[4];
        base.Read(arr, 0, 4);
        return BitConverter.ToSingle(arr, 0);
    }

    /// <summary>
    /// 把一个float数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteFloat(float value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region Double
    /// <summary>
    /// 从流中读取一个double数据
    /// </summary>
    /// <returns></returns>
    public double ReadDouble()
    {
        byte[] arr = new byte[8];
        base.Read(arr, 0, 8);
        return BitConverter.ToDouble(arr, 0);
    }

    /// <summary>
    /// 把一个double数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteDouble(double value)
    {
        byte[] arr = BitConverter.GetBytes(value);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region Bool
    /// <summary>
    /// 从流中读取一个bool数据
    /// </summary>
    /// <returns></returns>
    public bool ReadBool()
    {
        return base.ReadByte() == 1;
    }

    /// <summary>
    /// 把一个bool数据写入流
    /// </summary>
    /// <param name="value"></param>
    public void WriteBool(bool value)
    {
        base.WriteByte((byte)(value == true ? 1 : 0));
    }
    #endregion

    #region UTF8String
    /// <summary>
    /// 从流中读取一个sting数组(读数据前先写数据,写了之后才能去读,写的时候写的长度是知道的,把长度和数组写进去分成两部分)
    /// </summary>
    /// <returns></returns>
    public string ReadUTF8String()
    {
        //数据的长度
        ushort len = this.ReadUShort();
        byte[] arr = new byte[len];
        base.Read(arr, 0, len);
        return Encoding.UTF8.GetString(arr);
    }

    /// <summary>
    /// 把一个string数据写入流
    /// </summary>
    /// <param name="str"></param>
    public void WriteUTF8String(string str)
    {
        byte[] arr = Encoding.UTF8.GetBytes(str);
        if (arr.Length > 65535)
        {
            throw new InvalidCastException("字符串超出范围");
        }
        //长度
        WriteUShort((ushort)arr.Length);
        base.Write(arr, 0, arr.Length);
    }
    #endregion

    #region ByteArray
    /// <summary>
    /// 从流中读取一个sting数组
    /// </summary>
    /// <returns></returns>
    public byte[] ReadByteArray(out ushort length)
    {
        length = this.ReadUShort();
        byte[] arr = new byte[length];
        base.Read(arr, 0, length);
        return arr;
    }

    /// <summary>
    /// 把一个string数据写入流
    /// </summary>
    /// <param name="str"></param>
    public void WriteByteArray(byte[] arr, ushort length)
    {
        if (arr.Length > 65535)
        {
            throw new InvalidCastException("字符串超出范围");
        }
        WriteUShort(length);
        base.Write(arr, 0, length);
    }
    #endregion
}

以上Unity客户端关于Socket的使用就OK了,实现双向发送和接收消息。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity使用socket可以通过引入socket模块来实现。首先需要构造一个socket对象,可以使用socket.socket()方法来创建。该方法需要传入两个参数,分别是socket.AF_INET和socket.SOCK_STREAM,分别表示使用IPv4地址和流式Socket。\[1\] 在Unity常用到的Socket方法包括: - IPAddress类:包含了一个IP地址 - IPEndPoint类:包含了一对IP地址和端口号 - Socket类的方法: - 创建一个Socket对象:Socket() - 绑定一个本地的IP和端口号:Bind() - 让Socket侦听传入的连接尝试,并指定侦听队列容量:Listen() - 初始化与另一个Socket的连接:Connect() - 接收连接并返回一个新的socket:Accept() - 输出数据到Socket:Send() - 从Socket读取数据:Receive() - 关闭Socket(销毁连接):Close()\[2\] 需要注意的是,至少要定义一个要连接的远程主机的IP和端口号。端口号必须在1和65535之间,最好在1024以后。要连接的远程主机必须正在监听指定端口,也就是说你无法随意连接远程主机。一个Socket一次只能连接一台主机。Socket关闭后无法再次使用,每个Socket对象只能连接一台远程主机。如果你想连接到多台远程主机,你必须创建多个Socket对象。\[2\] 另外,需要注意的是,在Unity 5.1 ~ Unity2018可以使用UNet(Unity Networking),但在Unity 2019之后,UNet已经被废弃,Mirror成为了替代UNet的解决方案。因此,不建议再使用UNet进行网络通信。\[3\] #### 引用[.reference_title] - *1* *3* [【游戏开发实战】Unity使用Socket通信实现简单的多人聊天室(万字详解 | 网络 | TCP | 通信 | Mirror | ...](https://blog.csdn.net/linxinfa/article/details/118888064)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [UnitySocket网络编程](https://blog.csdn.net/weixin_45348216/article/details/128084313)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值