轩辕kuku
论坛版主
论坛版主
  • UID14
  • 粉丝6
  • 关注1
  • 发帖数78
  • 社区居民
  • 原创写手
阅读:1525回复:6

Unity3D移动端基于Socket的通信之UDP

楼主#
更多 发布于:2016-10-09 16:44
最近在忙手上的一个手机和电视端通信的项目,也就是用手机端识别卡片,电视端显示模型。目前已经完成演示的版本,是采用的socket的UDP方式,这种方式是采用无连接的形式,客户端和服务器端都采用用广播的方式发送数据。优点就是无连接,用户手机移动端无需知道电视服务器端的IP,直接连接发送数据即可,缺点就是当同时有几个移动端和电视端的时候就会操作有些混乱。不过目前定位是家庭用户,即环境比较简单,后来先采用了UDP的方式。Socket的通信有TCP和UDP,至于他们的区别大家可以去网上查找,很多专业的回答。下面具体介绍开发过程:
(1)定义数据结构,把想要传输的数据结构定义定义好,最好是一个数据包定义好包头包未,避免粘包,下面是定义的一个数据的父类:

using System.Collections;
using System.Text;
using System.Collections.Generic;
using System;
public delegate void DataReceiveCallBack(byte[] _bytes,int _type);//数据接受后处理的委托
public class Data {
    
    public int size;//数据包大小
    
    public int packetType;//数据包类型
    
    public virtual byte[] DataToByte()
    {
        return null;
    }
    
    
 #region 数据转换的公共方法    即byte数据和float、string、int、double的之间的转换
    /// <summary>
    /// 从byte到int
    /// </summary>
    /// <returns>The int.</returns>
    /// <param name="bytes">Bytes.</param>
    /// <param name="index">Index.</param>
    public static int getInt( byte[] bytes, int index)
    {
        return BitConverter.ToInt32(bytes, index);
    }
    
    /// <summary>
    /// 从byte到string
    /// </summary>
    /// <returns>The string.</returns>
    /// <param name="bytes">Bytes.</param>
    /// <param name="index">Index.</param>
    public static string getString( byte[] bytes,int index, int size)
    {
        byte[] _byte = new byte[size];
        Array.Copy(bytes, index, _byte, 0, size);
        string _s = Encoding.GetEncoding("UTF-8").GetString(_byte).TrimEnd('\0');
        return _s;
    }
    
    public static float getFloat(byte[] _bytes,int _index)
    {
        return BitConverter.ToSingle(_bytes,_index);
    }
    
    
    /// <summary>
    /// 将双精度byte数组转换成double类型变量
    /// </summary>
    /// <param name="bytes"></param>
    /// <param name="index"></param>
    /// <returns></returns>
    public  static double getDouble( byte[] bytes, int index)
    {
        string s = "";
        for (int i = index; i < index + 8; i++)
        {
            s += bytes<i>.ToString("X2");
                
        }
        ulong ul = ulong.Parse(s, System.Globalization.NumberStyles.AllowHexSpecifier);
        return System.BitConverter.ToDouble(System.BitConverter.GetBytes(ul), 0);
            
    }
        
    /// <summary>
    /// 从int到byte
    /// </summary>
    /// <returns>The bytes.</returns>
    /// <param name="_size">_size.</param>
    public static byte[] getBytes(int _size)
    {
        return BitConverter.GetBytes(_size);
            
    }
    
    /// <summary>
    /// 从string到byte.
    /// </summary>
    /// <returns>The bytes.</returns>
    /// <param name="str">String.</param>
    public static byte[] getBytes(string str)
    {
        return Encoding.Default.GetBytes(str);
    }
    
    public static byte[] getBytes(float f)
    {
        return BitConverter.GetBytes(f);
    }
    
    /// <summary>
    /// 将double类型变量转换成双精度byte数组
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static  byte[] getBytes(double value)
    {
        string s = BitConverter.DoubleToInt64Bits(value).ToString("X2");
        byte[] bytes = new byte[s.Length / 2];
        for (int i = 0; i < bytes.Length; i++)
        {
            bytes<i> = Convert.ToByte(s.Substring(i * 2, 2), 16);
        }
        return bytes;
    }
#endregion
}</i></i>
然后根据需要继承该类实现数据包,比如AR识别的数据包,UI交互的数据包等,这些都可以根据需要自己定义。比如我的AR识别的数据包代码,仅供参考:
using System.Collections;
using System.Text;
using System.Collections.Generic;
using System;
using UnityEngine;
     
public class ReconfigDataPacket : Data {
     
    public string parentSceneType;
     
    public string modelType;
     
    public string modelName;
     
    public ReconfigDataPacket()
    {}
     
    public ReconfigDataPacket(string _mt,string _mn,string _ps)
    {
        this.size = 16+_mn.Length;
        this.packetType = 1;
        this.modelName = _mn;
        this.modelType = _mt;
        this.parentSceneType = _ps;
    }
     
     
    public static ReconfigDataPacket ByteToData (byte[] _byte, int _index)
    {
        ReconfigDataPacket data = new ReconfigDataPacket();
        int index = _index;
        data.size = getInt(_byte,index);
        index = index + 4;
        data.packetType = getInt(_byte,index);
             
        index = index + 4;
        data.modelType = getString(_byte, index,8);
             
        index = index + 8;
        data.parentSceneType = getString(_byte,index,8);
        index = index + 8;
         
        data.modelName = getString(_byte, index,data.size-24);
     
        return data;
    }
     
    public override byte[] DataToByte ()
    {
        int index = 0;
        size = 24+modelName.Length;
        byte[] by = new byte[size]; //packetType:4 size:4 modelType:8 modelName:12  这里的长度根据项目需要更改
             
        byte[] tmp = getBytes(size);
        Array.Copy(tmp, 0, by, index, tmp.Length);
        index += 4;
        tmp = BitConverter.GetBytes(packetType);
        Array.Copy(tmp, 0, by, index, tmp.Length);
        index += 4;
             
        tmp = getBytes(modelType);
        Array.Copy(tmp, 0, by, index, tmp.Length);
        index += 8;
     
        tmp = getBytes(parentSceneType);
        Array.Copy(tmp, 0, by, index, tmp.Length);
        index += 8;
             
        tmp = getBytes(modelName);
        Array.Copy(tmp, 0, by, index, tmp.Length);
        return by;
    }
     
    public override string ToString ()
    {
        return "ReconfigDataPacket:Size:"+size+"modelType:"+modelType+"modelName:"+modelName+"parentSceneType:"+parentSceneType;
    }
}
(2)Socket通信两端的接受和发送数据包的方式,目前项目没有很强化服务端和客户端的概念,每一方都具备接收数据——处理数据和打包数据——发送数据的能力。
比如接收端:


private Socket server;
    private EndPoint remote;
    private IPEndPoint recip;
    public DataReceiveCallBack dataReceiveCallback;
    private Thread t ;
    string dataInfo = string.Empty;
    public static TVSocketUDP instance;
    public GetConnectdelegate getconnect;
    void Awake()
    {
        instance =this;
        DontDestroyOnLoad(gameObject);
    }
    void Start()
    {
        init();
    }
 //初始化Socket
public void init()
    {
        recip = new IPEndPoint(IPAddress.Any, 8041);
        server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
             
        server.Bind(recip);
        remote = recip;
        t = new Thread(RecMsg);
        t.Start();
        Debug.Log ("Udp Start");
    }
//另开线程接收数据
void RecMsg()
    {
        while (true)
        {
            byte[] data = new byte[1024];
            Debug.Log("listener");
            int r = server.ReceiveFrom(data, ref remote);
            string message = Encoding.UTF8.GetString(data, 0, r);
            if(message == "mobilecalltv")
            {
                Debug.Log("Mobile Single Coming");
                if(getconnect!= null)
                {
                    getconnect();
                }
                continue;
            }
     
            if (r > 0)
            {
                    int type = Data.getInt(data,6);//数据包类型
                    int size = Data.getInt(data,2);//数据包长度
                    byte[] dataBytes = new byte[size];
                         
                    Debug.Log("type:"+type+" Size:"+size);
                   //根据数据包的类型分开处理
                    switch(type)
                    {
                    case 0:
                          
                        break;
                    case 1:
                        System.Array.Copy(data, 2, dataBytes, 0, r - 4);
                        Debug.Log(dataBytes.Length);
                        dataInfo  = ReconfigDataPacket.ByteToData(dataBytes,0).ToString();
                        Debug.Log(dataInfo);
                        isStart = false;
                        Debug.Log("dataReceiveCallback != null:"+(dataReceiveCallback != null));
                        if (dataReceiveCallback != null)
                        {
                            dataReceiveCallback(dataBytes,1);
                        }
                        break;
                    case 2:
                         
                        break;
                    default :
                        break;
                    }
              }
            Thread.Sleep (1);
        }
    }
         
         
    public void OnDestroy()
    {
        if (t != null)
            t.Abort ();
        if (server != null)
            server.Close();
    }


发送端代码:
public bool isSend =false;
    public string specialIP = "172.16.4.171";
    private string dataInfo = "waitting data send";
     
    private Socket socket;
    private IPEndPoint ipLocalPoint;
    private EndPoint RemotePoint;
    public static MobileSocketUDP instance;
     
    void Awake()
    {
        DontDestroyOnLoad(gameObject);
        instance =this;
    }
         
    // Use this for initialization
    void Start () {
           
        ConfigUDP();
//      InvokeRepeating("SendInternetInfo",0,3);
    }
     
      
    //初始化Socket
    void ConfigUDP()
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
        ipLocalPoint = new IPEndPoint(IPAddress.Broadcast, 8041);
        
    }
 #region 定义发送数据的方法
    IEnumerator SendMessageIEnu(byte[] sendByte)
    {
        int split = 1024;
        for (int i = 0; i < sendByte.Length / split; i++)
        {
            Debug.Log("send num"+i);
            byte[] bytes = new byte[split];
            Array.Copy(sendByte, i * split, bytes, 0, split);
            socket.SendTo(bytes, ipLocalPoint);
            yield return new WaitForSeconds(0.001f);
        }
        int len = sendByte.Length / split * split;
        byte[] bytes1 = new byte[sendByte.Length - len];
        Array.Copy(sendByte, len, bytes1, 0, sendByte.Length - len);
        socket.SendTo(bytes1, ipLocalPoint);
    }
     
    public void SendMessage(Data _data)
    {
        if(!isSend)
            return;
        byte[] startByte = new byte[2];
        startByte[0] = 0x55;
        startByte[1] = 0x55;//定义的包头
        byte[] dataByte = _data.DataToByte();
        byte[] endByte = new byte[2];
        endByte[0] = 0xF5;
        endByte[1] = 0xFA;//定义的包尾
        int _size = _data.size;
        byte[] sendByte = new byte[_size+4];
        Array.Copy(startByte, 0, sendByte, 0, 2);
        Array.Copy(dataByte, 0, sendByte, 2, _size);
        Array.Copy(endByte, 0, sendByte, _size+2, 2);
        Debug.Log(sendByte.Length);
        socket.SendTo(sendByte, ipLocalPoint);
        dataInfo = _data.ToString();
        Debug.Log(dataInfo);
    }
 #endregion
    void OnDestroy()
    {
        socket.Close();
    }
         
         
    void OnGUI()
    {
        //GUI.Label(new Rect(20,20,200,200),dataInfo);
        //if (image != null)
        //    GUI.DrawTexture(new Rect(20, 20, image.width + 20, image.height + 20), image);
    }
其实整个过程中,需要注意的几个地方:
a、定义数据包,这一块依照需求而定,一定要仔细,都是按字节计算,错了就会导致后面数据解析出错,最好每个不同的数据包函数各自的转byte方法;
b、在数据包发送和接受的过程中,如果有几条发送和接受的线路,端口号一定要区别,这样才不会乱;
c、数据解包的过程中可能会出现粘包情况,可以采用定义包头包尾的方式来解决;

最后其实整个不是很难,关键要仔细,数据包定义要仔细、打包数据要仔细、解析数据包要仔细。另外一个版本将结合TCP的链接方式做。

图片:68_14_11710416e74227b.jpg


最新喜欢:

归海一啸归海一啸

欢迎分享

1739516172
精灵王
精灵王
  • UID748
  • 粉丝0
  • 关注3
  • 发帖数55
  • 社区居民
  • 忠实会员
沙发#
发布于:2016-10-11 09:31
这个营养价值高
ria09yh
禁止发言
禁止发言
  • UID3039
  • 粉丝0
  • 关注0
  • 发帖数7
板凳#
发布于:2016-11-13 14:26
用户被禁言,该主题自动屏蔽!
abc13645
贫民
贫民
  • UID1932
  • 粉丝0
  • 关注0
  • 发帖数5
地板#
发布于:2016-12-16 10:13
程序好像不完整啊,楼主。。。
轩辕kuku
论坛版主
论坛版主
  • UID14
  • 粉丝6
  • 关注1
  • 发帖数78
  • 社区居民
  • 原创写手
4楼#
发布于:2016-12-16 13:06
abc13645:程序好像不完整啊,楼主。。。回到原帖
哪里有问题呢,IP自己配好
abc13645
贫民
贫民
  • UID1932
  • 粉丝0
  • 关注0
  • 发帖数5
5楼#
发布于:2016-12-20 10:47
轩辕kuku:哪里有问题呢,IP自己配好回到原帖
好像没有public delegate void GetConnectdelegate();我这么定义也不知道是不是符合的,现在在unity上测试是可以,但是导出成Android包后在手机上发送数据,接收不到数据
轩辕kuku
论坛版主
论坛版主
  • UID14
  • 粉丝6
  • 关注1
  • 发帖数78
  • 社区居民
  • 原创写手
6楼#
发布于:2016-12-22 19:06
abc13645:好像没有public delegate void GetConnectdelegate();我这么定义也不知道是不是符合的,现在在unity上测试是可以,但是导出成Android包后在手机上发送数据,接收不到数据回到原帖
你检查一下的你的端口号和IP对不对,Unity测试通过代码就应该没问题的
游客

返回顶部