• 您的位置:首页 > 新闻动态 > 技术文章

    HOLOLENS的SOCKET网络通讯1

    2019/5/23      点击:
    多数开发者开发Hololens的通信功能是先想到的是system.net.socket库里的socket,发布UWP的时候就可能出问题,因为UWP对system库不是完全的支持,很多方法或者类是没有定义的(这是一个很常见的发布UWP的报错)。本文用的system.net.socket里的SAEA系列,全称:SocketAsyncEvnetArgs,这是微软针对高并发而设计的一套API, SAEA是异步的socket参数,使用SAEA时需要注意三点:1.缓冲区  2.IP  3.完成后的回调,这三点是必要的,其次还有其他的SAEA参数,不是必要的,例如UserToken等,详细可查API。
    using UnityEngine;
    using System.Net;
    using System.Net.Sockets;
    using System;
    using System.Text;
    //这个脚本是hololens端的SocketUDP脚本,提供发送方法,初始化并开启接收方法
    public class MyUdpClient : MonoBehaviour
    {
        Socket socket; //目标socket
        //发送端口
        EndPoint serverEnd; 
        IPEndPoint ipEnd; 
        //接收端口
        IPEndPoint IPLocalPoint;
        //发送用的socket异步参数
        SocketAsyncEventArgs socketAsyceArgs;
        //接收用的socket异步参数
        SocketAsyncEventArgs reciveArgs;
        //接收SAEA用来接收的缓冲区
        byte[] reciveArgsBuffer;        
        //初始化
        void InitSocket()
        {
            //定义连接的服务器ip和端口,可以是本机ip,局域网,互联网
            ipEnd = new IPEndPoint(IPAddress.Parse("10.100.172.226"), 8001);
            //初始化要接收的IP,IPAddress.Any表示接收所有IP地址发来的字节流
            IPLocalPoint = new IPEndPoint(IPAddress.Any, 8002);
            //初始化socket
            socket = new Socket(IPLocalPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);  
            //定义服务端
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
            serverEnd = (EndPoint)sender;
            //初始化发送用的SAEA
            socketAsyceArgs = new SocketAsyncEventArgs();
            //设置发送用的SAEA的IP
            socketAsyceArgs.RemoteEndPoint = ipEnd;
            //初始化接收用的SAEA的缓冲区,此处我设为10K
            reciveArgsBuffer = new byte[1024 * 10];
            //初始化接收SAEA
            reciveArgs = new SocketAsyncEventArgs();
            //设置接收SAEA的接收IP地址
            reciveArgs.RemoteEndPoint = IPLocalPoint;
            //因为SAEA系列API 是异步方法,所以设置好完成方法后的回调
            reciveArgs.Completed += new EventHandler(CompletedRecive);
            //设置接收缓冲区
            reciveArgs.SetBuffer(reciveArgsBuffer, 0, reciveArgsBuffer.Length);
        }
        //异步方法完成后的complete时间
        private void CompletedRecive(object sender, SocketAsyncEventArgs e)
        {
            //通过SAEA.LastOperation这个枚举来判断完成的是什么方法,对应不同的操作
            switch (reciveArgs.LastOperation)
            {
                //因为reciveArgs是我专门用来接收的SAEA,所以这里只设置一个完成接收后用的方法
                case SocketAsyncOperation.ReceiveFrom:
                    PocessReceiveFrom(e);
                    break;       
            }
        }
        //中转缓冲区,将数据拷贝出来给主线程用
        byte[] tempBytes;
        //用来通知主线程的参数
        bool isOk=false;
        //注意:处理这个方法是辅线程,不要用Unity的类,否则报错,将收到的字节流拷贝出来,通知主线程来处理
        //接收完成后对应的处理方法
        public void PocessReceiveFrom(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //这里会造成内存垃圾以及内存碎片化,如果频繁的长时间的接收,建议做一个Byte池。
                tempBytes = new byte[e.BytesTransferred];     //将数据拷贝出来保证可以复用
                Array.Copy(e.Buffer, e.Offset, tempBytes, 0, tempBytes.Length);
                //通知主线程
                isOk = true;
            }
        }
        ////// 异步发送消息方法
        //////public void AsyncSend(byte[] bytes)
        {
            //设置缓冲区,缓冲区里是发送的字节流
            socketAsyceArgs.SetBuffer(bytes, 0, bytes.Length);
            //Debug.Log("socket异步参数字节流长度 " + socketAsyceArgs.Buffer.Length);
            bool bo = socket.SendToAsync(socketAsyceArgs);
            if (!bo)
            {
                //在hololens上发现过一段时间scoket就不会发送数据,*后这样处理:判断SentToAsync方法失败后,就重新new一个SAEA,解决socket发送失败的问题
                //注意初始化一个SAEA时,1.IP    2.缓冲区,3.完成后的回调事件  这三个都是必要的,
                socketAsyceArgs = new SocketAsyncEventArgs();
                socketAsyceArgs.RemoteEndPoint = ipEnd;
            }
        }
        //初始化socket并测试一下
        private void Start()
        {
            InitSocket();
            TestSocekt();
        }
        //用来测试socket的方法,发送一个信息
        void TestSocekt() {
            int tempInt = 9999;
            byte[] tempBytes;
     
            tempBytes=BitConverter.GetBytes(tempInt);
            AsyncSend(tempBytes);
        }
        private void Update()
        {
            if (isOk)
            {
                //对tempBytes进行处理
                int temp= BitConverter.ToInt32(tempBytes, 0);
                Debug.Log("接收socket,接收到了字节流,接收到的数字为 " + temp);
                isOk = false;
            }
        }
        //每隔一段时间就接受一下
        private void FixedUpdate()
        {
            socket.ReceiveFromAsync(reciveArgs);
        }
    }
    上面的代码把接收模块和发送模块写在一起,SAEA系列是异步的,所以使用起来对于多线程需要一些了解。
    一般的socket需求用上面的代码足够用的,由于上文中只有一个接收SAEA和一个发送SAEA,所以当一个SAEA在工作时,不要再让这个SAEA工作。

    捷径:后来发现在MixedRealTooklit里面有scoket组件,可以直接使用MRTK中Sharing文件夹中的组件,或者查看MRTK的源码,里面是用Windows.Networking和Task写的Socket,找了很长时间的SocketAPI,原来远在天边近在眼前,感叹当时怎么不好好看看MRTK!!