Hololens的Socket通信

写在最前面:

我觉得现在做Hololens开发,整体的风气有一个很大问题,就是太浅,太上层,太依靠MRHoloTooklit,微软出的这一套SDK封装了很多功能,虽然好用,但是颇为上层,存在一些局限性,而且实现某些复杂功能时,会有额外的开销。Hololens终究是一款移动平台,性能的瓶颈还是挺明显的。

所以摆脱MRTK(Mixed Reality Toolkit-Unity),直接用unity的 UnityEngine.XR.WSA提供的API来开发应用是开发Hololensy的长久之计,最近我也在看MRTK的源码,奈何技术不到家,读起来磕磕绊绊的。

大多数开发者开发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<SocketAsyncEventArgs>(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;
        }
    }

    /// <summary>
    /// 异步发送消息方法
    /// </summary>
    /// <param name="bytes"></param>
    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工作,(有高并发需求时,你需要一个SAEA池,里面放了很多SAEA,将空闲的SAEA拿来使用,微软希望开发者这么使用SAEA,来解决高并发)

后来发现在MixedRealTooklit里面有scoket组件,可以直接使用MRTK中Sharing文件夹中的组件,或者查看MRTK的源码,里面是用Windows.Networking和Task写的Socket,找了很长时间的SocketAPI,原来远在天边近在眼前,感叹当时怎么不好好看看MRTK!!  不过此时我已经用SAEA的方法写出来Hololens的sockt模块了~  没有用MRTK的那一套。

  • 9
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 54
    评论
评论 54
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值