美文网首页unity学习
Unity3D Socket通信的简单实现

Unity3D Socket通信的简单实现

作者: RE_my_world | 来源:发表于2017-06-19 05:03 被阅读1005次

    Socket的定义

    socket英文的含义为插座、孔,在我们的网络应用中通常称为套接字,大致理解为在tcp/ip网络抽象层中使用套接字ip+端口的网络通信协议,可认为是介于传输层与应用层中抽象出的socket层,我们可以使用它的接口来解决复杂的网络请求。

    GitHub

    Tcp/IP

    GitHub GitHub
    定义:

    ransmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。

    组成:

    由四部分組成,从低至高分別为链路层、网络层、传输层和应用层

    • 链路层:数据链路层是负责接收IP数据包并通过网络发送,或者从网络上接收物理帧,抽出IP数据包,交给IP层。主要表现为物理驱动,使用物理的方式进行数据交互。

    • 网络层:负责相邻计算机之间的通信。确定网络地址IP,对网络链接状况获取相关数据。

    • 传输层:提供应用程序间的通信。使用 tcp 或 udp 形式对主机进行网络请求,客户端与服务端使用流各自操作本地数据文本,以代理的形式实现通信。

    • 应用层:向用户提供一组常用的应用程序,主机上对网络获取的数据进行交互和效果展示。

    Socket通信过程

    GitHub

    名词解析:

    • IP:网络IP地址,网络进程唯一标识符。

    • 端口:应用进程中用来数据交互的接口,每个应用都有唯一的端口标识符。

    • IPv4,是互联网协议(Internet Protocol,IP)的第四版,也是第一个被广泛使用,构成现今互联网技术的基石的协议。1981年Jon Postel 在RFC791中定义了IP,Ipv4可以运行在各种各样的底层网络上,比如端对端的串行数据链路(PPP协议和SLIP协议) ,卫星链路等等。局域网中最常用的是以太网。

    IPv4中规定IP地址长度为32,即有232-1(符号表示升幂,下同)个地址

    • IPv6:

    IPv6是Internet Protocol Version 6的缩写,其中Internet Protocol译为“互联网协议”。IPv6是IETF(互联网工程任务组,Internet Engineering Task Force)设计的用于替代现行版本IP协议(IPv4)的下一代IP协议。

    IPv6中IP地址的长度为128,即有2^128-1个地址。

    IPv6具有更高的安全性。在使用IPv6网络中用户可以对网络层的数据进行加密并对IP报文进行校验,极大的增强了网络的安全性。

    代码实现

    • 初始化: 根据地址和端口创建socket资源
    
    string[]handled=tcp.Split(newChar[]{':'});
    
    stringhost=handled[0];
    
    inthostPort=handled.Length>1?int.Parse(handled[1]):80;
    
    //地址封装
    
    IPEndPointport=newIPEndPoint(Dns.GetHostAddresses(handled[0])[0],hostPort);
    
    //创建socket资源,新实例初始化 Socket 类使用指定的地址族、 套接字类型和协议。
    
    socket=newSocket(
    
        AddressFamily.InterNetwork,
    
        System.Net.Sockets.SocketType.Stream,
    
        ProtocolType.Tcp
    
    );
    
    
    • 请求连接: 异步向服务器发送连接请求,连接成功向服务器发送心跳包(心跳:服务器与客户端进行通信,客户端实现双向保活),socket进行异步挂载,准备接收数据
    
    ...
    
    //连接服务器
    
    IAsyncResultresult=socket.BeginConnect(
    
        port, ConnectCallBack, socket
    
    );
    
    //5s内判断是否连接成功
    
    boolisSucced=result.AsyncWaitHandle.WaitOne(5000,true);
    
    ...
    
    void ConnectCallBack(IAsyncResultresult){
    
        Socketsocket=(Socket)result.AsyncState;
    
        //异步挂载,等待数据接收
             socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);
    
    }
    
    
    • 接收数据:客服端开启异步处理,开启挂载对服务器的数据进行监听,接收到数据进行拆包处理,发送给各个应用模块。之后再将socket进行异步挂载,准备接收数据
    
    void ConnectCallBack(IAsyncResultresult){
    
        Socketsocket=(Socket)result.AsyncState;
    
        socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);
    
    }
    
    private void RecivieCallBack(IAsyncResultresult){
    
        Socketsocket=(Socket)result.AsyncState;
    
        //接收长度
    
        intlength=socket.EndReceive(result);
    
        if(length<=0){
    
            Console.Log(LogType.SOCKET,"server does not return data, but not disconnect");
    
        return;
    
        }
    
        //保持数据同步,对二进制字节拆包处理
    
        lock(receiveData){
    
        SplitePackage(receiveData,length);
    
        }
    
        try{
    
        if(respsonList.Count>0){
    
              respsonList.ForEach(packet=>{
    
                  sub.OnNext(packet);
    
              });
    
              respsonList.Clear();
    
          }
    
            socket.BeginReceive(receiveData,0,receiveData.Length,SocketFlags.None,newAsyncCallback(RecivieCallBack),socket);
    
         }
    
        catch(Exceptione){
    
        //Debug.LogError (e.ToString());
    
    }
    
    
    • 拆包:将从服务器接收的二进制数据进行拆包处理,根据包头获取数据长度,对二进制数据进行转化(* 处理粘包->使用循环,轮询处理 断包->将断包处保存再memoryStream中,再次接收数据的时候将包重新拼接)
    
    private void SaveBreak(byte[]data,intindex){
    
        streamLen=data.Length-index;
    
        byte[]breakData=newbyte[streamLen];
    
        Array.Copy(data,index,breakData,0,breakData.Length);
    
        stream.SetLength(streamLen);
    
        stream.Write(breakData,0,breakData.Length);
    
        isBreakPacket=true;
    
    }
    
    private void EndBreak(){
    
        if(stream!=null){
    
        stream.Close();
    
        stream=newMemoryStream();
    
        streamLen=0;
    
        isBreakPacket=false;
    
        }
    
    }
    
    //拆包
    
    public void SplitePackage(byte[]data,intbufferLen){
    
        //包头位置
    
        intindex=0;
    
        //断包判断
    
        byte[]bytes=newbyte[bufferLen];
    
        Array.Copy(data,bytes,bufferLen);
    
        //处于断包状态
    
        if(isBreakPacket){
    
        byte[]packetData=stream.ToArray();
    
        byte[]newData=newbyte[streamLen+bufferLen];
    
        Array.Copy(packetData,0,newData,0,streamLen);
    
        index+=streamLen;
    
        Array.Copy(bytes,0,newData,index,bufferLen);
    
        bytes=newData;
    
        index=0;
    
        EndBreak();
    
        }
    
        //循环拆包(粘包处理)
    
        while(index
    
        //断包头
    
        if((index+4)>=bytes.Length){
    
        SaveBreak(bytes,index);
    
        return;
    
        }
    
        //拆包头
    
        byte[]head=newbyte[4];
    
        Array.Copy(bytes,index,head,0,4);
    
        index+=4;
    
        Array.Reverse(head);
    
        intcount=BitConverter.ToInt32(head,0);
    
        //异常处理
    
        if(count>8192){
    
        EndBreak();
    
        HandleError("包头过长");
    
        return;
    
        }
    
        //断内容处理
    
        if((index+count)>bytes.Length){
    
        index-=4;
    
        SaveBreak(bytes,index);
    
        return;
    
        }
    
        //正常包处理
    
        byte[]callbackData=newbyte[count];
      
        Array.Copy(bytes,index,callbackData,0,count);
    
        stringiContent=Encoding.UTF8.GetString(callbackData);
    
        if(count==0){
    
        EndBreak();
    
        HandleError("接收数据为空");
    
        }
    
        respsonList.Add(iContent);
    
        index+=callbackData.Length;
    
        }
    
    }
    
    
    • 发送请求:客户端将发送的数据转化成二进制字节,进行装包处理发送给服务器
    
    public void SendMessage(stringstr)
    
    {
    
        if(socket!=null&&!socket.Connected){
    
        EndBreak();
    
        HandleError("socket is not connecting || socket is not existed");
    
        return;
    
        }
    
        byte[]msg=Encoding.UTF8.GetBytes(str);
    
        intmesgLength=msg.Length;
    
        byte[]head=BitConverter.GetBytes(mesgLength);
    
        byte[]sendData=newbyte[mesgLength+head.Length];
      
        Array.Reverse(head);//服务器与客户端的包头的解析顺序是相反的
    
        Array.Copy(head,sendData,head.Length);
    
        Array.Copy(msg,0,sendData,head.Length,msg.Length);
    
        try{
    
            IAsyncResultasyncSend=socket.BeginSend(sendData,0,sendData.Length,SocketFlags.None,newAsyncCallback(sendCallback),socket);
    
            boolsuccess=asyncSend.AsyncWaitHandle.WaitOne(7000,true);
    
            if(!success){
    
            throw new ApplicationException();
    
        }
    
        else{
    
        Console.Log(LogType.SOCKET_SEND,str);
    
        }
    
        catch{
    
        Console.Log(LogType.SOCKET,"send message exceptionally");
    
        }
    
    }
    
    //请求成功
    
    private void sendCallback(IAsyncResultasyncSend)
    
    {
    
    ...
    
    }
    
    
    • 关闭:断开连接,关闭socket资源
    
    public void Closed(){
    
        try{
    
            if(socket!=null&&socket.Connected){
    
            //先停止接收和发送的服务
    
            socket.Shutdown(SocketShutdown.Both);
    
            //关闭socket资源
    
            socket.Close();
    
            if(disposable!=null){
    
            disposable.Dispose();
    
            }
    
            socket=null;
    
          }
    
        }
    
        //必须加上,客户端使用轮询处理断线重连时候有可能多次调用,异步导致报错使程序直接卡死
    
        catch{
    
        }
    
    }
    
    

    常见问题

    • 数据同步: unity引擎中MonBehaviour只能处理主线程的数据,而我们socket请求数据是异步请求的

    • 拆包:当出现粘包、断包

    • 重连:应用保活与断线重连

    • 异常处理:socket异步访问异常导致程序卡死

    优化改进

    • buffer:创建缓冲类,将每次的数据进行管理

    • 创建包体实体类,对游戏数据进行封装

    参考链接

    • 百度TCP/IP协议:

    百度

    • 百度Socket:

    百度

    • Socket_API:

    微软官网

    • Socket拆包处理:

    Socket/TCP粘包、多包和少包, 断包


    第一次写技术文章,可能有些地方表达并不具体,请小伙伴们谅解。本人unity菜鸟一枚,如果由不懂的地方可以留言,我们可以一起分享心得。如果效果不错我会在闲暇之余继续修仙写文章,如果觉得还不错就顺手点个赞吧~


    相关文章

      网友评论

        本文标题:Unity3D Socket通信的简单实现

        本文链接:https://www.haomeiwen.com/subject/uyzwqxtx.html