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

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!!