Unity开发之网络一TCP客户端实现

网络开发系列 Unity开发之网络—集成Protobuf Unity开发之网络一HTTP客户端实现 Unity开发之网络一HTTP服务端实现 下面介绍Sockt通信的实现,和Http相比Tcp/IP实现要复杂一些。不过使用C#语言开发要容易了很多。为什么这样说呢,因为以前写过使用C++实现Socket通信,在项目中已经编写完成,但因为外因,项目没有上线,最后日志也不了了之 跨平台客户端Socket 一 数据包定义 下面介绍使用C#实现Socket通信,包体结构及定义从C++修改而来。 主要涉及三个类SocketModule,ClientSocket,ClientSocketHelper SocketModule:定义包头结构,ClientSocket的接收连接等接口,以及其他定义 ClientSocket:网络连接的具体实现 ClientSocketHelper:二次封装,方便应用层调用 ClientSocket主要代码如下: 网络连接

public virtual bool Connect(string szServerIP, short wPort){
    //效验参数
    Debug.Assert(m\_SocketState == enSocketState.SocketState\_NoConnect);
    m\_SocketState = enSocketState.SocketState\_Connecting;

    IPAddress\[\] addressArray = Dns.GetHostAddresses(szServerIP);
    if (addressArray\[0\].AddressFamily == AddressFamily.InterNetworkV6)
    {
        //创建Socket对象, 连接类型是TCP  
        m_hSocket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
    }
    else
    {
        //创建Socket对象, 连接类型是TCP  
        m_hSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

    //服务器IP地址  
    IPAddress ipAddress = IPAddress.Parse(addressArray\[0\].ToString());
    //服务器端口  
    IPEndPoint ipEndpoint = new IPEndPoint(ipAddress, wPort);
    //这是一个异步的建立连接,当连接建立成功时调用connectCallback方法  
    IAsyncResult result = m\_hSocket.BeginConnect(ipEndpoint, new AsyncCallback(connectCallback), m\_hSocket);
    //这里做一个超时的监测,当连接超过30秒还没成功表示超时  
    bool success = result.AsyncWaitHandle.WaitOne(30000, true);
    if (!success)
    {
        //超时  
        CloseSocket(false);
        UnityLibs.Debuger.Log("connect Time Out");

        m\_SocketState = enSocketState.SocketState\_NoConnect;
        if (m_pClientSocketDelegate != null)
        {
            m_pClientSocketDelegate.OnSocketConnect(-1, "连接超时", this);
        }

        return false;
    }
    else
    {
        return true;
    }
}

private void connectCallback(IAsyncResult async)
{
    try
    {
        //结束挂起的异步连接
        Socket socket = (Socket)async.AsyncState;
        if (socket.Connected)
        {
            socket.EndConnect(async);

            //设置参数
            m_dwRecvSize = 0;
            m_cbSendRound = 0;
            m_cbRecvRound = 0;
            m_dwSendXorKey = 0x12345678;
            m_dwRecvXorKey = 0x12345678;
            m_dwSendTickCount = DateTime.Now.Millisecond / 1000;
            m_dwRecvTickCount = DateTime.Now.Millisecond / 1000;

            m\_SocketState = enSocketState.SocketState\_Connected;
            if(m_pClientSocketDelegate != null)
            {
                m_pClientSocketDelegate.OnSocketConnect(0, "", this);
            }

            //与m_hSocket建立连接成功,开启线程接受服务端数据。  
            Thread thread = new Thread(new ThreadStart(RecvSocket));
            thread.IsBackground = true;
            thread.Start();

            //开启心跳检测,毫秒为单位
            m\_timerDetectSocket = new Timer(timerHandler, m\_hSocket, 60 * 1000, 5 * 1000);
            UnityLibs.Debuger.Log("connectSuccess");
        }
        else
        {
            m\_SocketState = enSocketState.SocketState\_NoConnect;
            if (m_pClientSocketDelegate != null)
            {
                m_pClientSocketDelegate.OnSocketConnect(-2, "连接出错", this);
            }
        }
    }
    catch (Exception e)
    {
        UnityLibs.Debuger.Log("error." + e);
        m\_SocketState = enSocketState.SocketState\_NoConnect;
        if (m_pClientSocketDelegate != null)
        {
            m_pClientSocketDelegate.OnSocketConnect(-2, "连接异常", this);
        }
    }
}

需要注意的是连接时要检测是否是IPV6的网络,否则iOS无法通过审核。连接成功会开启接收线程和心跳包检测定时任务。 接收:

private void RecvSocket()
{
    //在这个线程中接受服务器返回的数据  
    while (m\_SocketState == enSocketState.SocketState\_Connected)
    {
        if (!m_hSocket.Connected)
        {
            //与服务器断开连接跳出循环  
            UnityLibs.Debuger.Log("Failed to clientSocket server.");
            CloseSocket(true);
            break;
        }

        try
        {
            if (m_hSocket.Available <= 0) continue;

            int iRetCode = m\_hSocket.Receive(m\_cbRecvBuf,m\_dwRecvSize,m\_cbRecvBuf.GetLength(0) - m_dwRecvSize, SocketFlags.None);
            //int iRetCode = m\_hSocket.Receive(m\_cbRecvBuf, m\_dwRecvSize, SocketModule.SOCKET\_BUFFER - m_dwRecvSize, SocketFlags.None);
            if (iRetCode <= 0)
            {
                CloseSocket(true);
                break;
            }
            else
            {
                Debug.Assert(m_dwSendPacketCount > 0);
                m_dwRecvSize += iRetCode;
                m_dwRecvTickCount = DateTime.Now.Millisecond / 1000;

                //变量定义
                short wPacketSize = 0;
                //byte \[\]cbDataBuffer = new byte\[SocketModule.SOCKET_BUFFER\];
                CMD\_Head pHead = new CMD\_Head();

                while (m\_dwRecvSize >= CMD\_Head.sizeof\_CMD\_Head)
                {
                    //效验参数
                    pHead.Decode(m_cbRecvBuf);
                    wPacketSize = pHead.cmdInfo.wDataSize;
                    Debug.Assert(wPacketSize <= (SocketModule.SOCKET\_PACKAGE + CMD\_Head.sizeof\_CMD\_Head));
                    if (pHead.cmdInfo.cbMessageVer != SocketModule.SOCKET_VER) break;// throw ("数据包版本错误");
                    if (wPacketSize > (SocketModule.SOCKET\_PACKAGE + CMD\_Head.sizeof\_CMD\_Head)) break;// throw ("数据包太大");
                    if (m_dwRecvSize < wPacketSize) break;//throw ("数据包太小");

                    byte\[\] cbDataBuffer = new byte\[wPacketSize\];
                    //拷贝数据
                    m_dwRecvPacketCount++;
                    Array.Copy(m_cbRecvBuf,cbDataBuffer,wPacketSize);
                    //删除缓存数据
                    m_dwRecvSize -= wPacketSize;
                    Array.Copy(m\_cbRecvBuf, wPacketSize, m\_cbRecvBuf, 0, m_dwRecvSize);

                    //解密数据
                    short wRealySize = CrevasseBuffer(cbDataBuffer, wPacketSize);
                    Debug.Assert(wRealySize >= CMD\_Head.sizeof\_CMD_Head);

                    //解释数据
                    short wDataSize = (short)(wRealySize - CMD\_Head.sizeof\_CMD_Head);
                    CMD_Command Command = pHead.command;
                    Array.Copy(cbDataBuffer, CMD\_Head.sizeof\_CMD_Head, cbDataBuffer, 0, wDataSize);

                    //UnityLibs.Debuger.Log("RecvData :" + pHead.command.wMainCmdID
                    //  \+ " sub:" + pHead.command.wSubCmdID
                    //  \+ " dataSize:" + (pHead.cmdInfo.wDataSize - CMD\_Head.sizeof\_CMD_Head)
                    //  \+ " recvSize:" + pHead.cmdInfo.wDataSize
                    //  );

                    //内核命令
                    if (Command.wMainCmdID == SocketModule.MDM\_KN\_COMMAND)
                    {
                        switch (Command.wSubCmdID)
                        {
                            case SocketModule.SUB\_KN\_DETECT_SOCKET:  //网络检测
                                {
                                    //发送数据
                                    SendData(SocketModule.MDM\_KN\_COMMAND, SocketModule.SUB\_KN\_DETECT_SOCKET, cbDataBuffer, wDataSize);
                                    break;
                                }
                        }
                    }
                    else
                    {
                        bool bSuccess = false;
                        if (m_pClientSocketDelegate != null)
                        {
                            bSuccess = m_pClientSocketDelegate.OnSocketRead(Command, cbDataBuffer, wDataSize, this);
                        }

                        if (bSuccess == false)
                        {
                            foreach (IClientSocketRecvDelegate socketRecv in m_listSocketRecvDelegate)
                            {
                                bSuccess = socketRecv.OnSocketRead(Command, cbDataBuffer, wDataSize, this);
                                if (bSuccess)
                                {
                                    break;
                                }
                            }

                            if (bSuccess == false)
                            {
                                UnityLibs.Debuger.Log("网络数据包处理失败");
                            }
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
            UnityLibs.Debuger.Log("Failed to clientSocket error." + e);
            CloseSocket(true);
            break;
        }
    }
}

接收线程主要是对网络传递的数据流解析为定义的数据包,要注意的是数据的连包和沾包处理。 数据发送

public virtual bool SendData(short wMainCmdID, short wSubCmdID){
    //效验状态
    if (m_hSocket == null) return false;
    if (m\_SocketState != enSocketState.SocketState\_Connected) return false;

    //构造数据
    CMD\_Head pHead = new CMD\_Head();
    pHead.command.wMainCmdID = wMainCmdID;
    pHead.command.wSubCmdID = wSubCmdID;
    //if (AppUser.Instance.Session.Length == SocketModule.SOCKET\_PACKAGE\_SESSION)
    //    Array.Copy(Encoding.Default.GetBytes(AppUser.Instance.Session), pHead.cbSession, SocketModule.SOCKET\_PACKAGE\_SESSION);

    byte\[\] cbDataBuffer = new byte\[SocketModule.SOCKET_BUFFER\];
    Array.Copy(pHead.Encode(), cbDataBuffer, CMD\_Head.sizeof\_CMD_Head);

    //加密数据
    short wSendSize = EncryptBuffer(cbDataBuffer, CMD\_Head.sizeof\_CMD_Head, (short)cbDataBuffer.GetLength(0));

    //发送数据
    return SendBuffer(cbDataBuffer, wSendSize);
}

//发送函数
public virtual bool SendData(short wMainCmdID, short wSubCmdID, byte\[\] pData, short wDataSize){
    //效验状态
    if (m_hSocket == null) return false;
    if (m\_SocketState != enSocketState.SocketState\_Connected) return false;

    //效验大小
    Debug.Assert(wDataSize <= SocketModule.SOCKET_PACKAGE);
    if (wDataSize > SocketModule.SOCKET_PACKAGE) return false;

    //构造数据
    byte\[\] cbDataBuffer = new byte\[SocketModule.SOCKET_BUFFER\];

    CMD\_Head pHead = new CMD\_Head();
    pHead.command.wMainCmdID = wMainCmdID;
    pHead.command.wSubCmdID = wSubCmdID;

    Array.Copy(pHead.Encode(), cbDataBuffer, CMD\_Head.sizeof\_CMD_Head);
    if (wDataSize > 0)
    {
        Debug.Assert(pData != null);
        Array.Copy(pData,0, cbDataBuffer, CMD\_Head.sizeof\_CMD_Head,wDataSize);
    }

    //加密数据
    short wSendSize = EncryptBuffer(cbDataBuffer, (short)(CMD\_Head.sizeof\_CMD_Head + wDataSize), (short)cbDataBuffer.GetLength(0));

    //发送数据
    return SendBuffer(cbDataBuffer, wSendSize);
}

bool SendBuffer(byte\[\] pBuffer, int dwSendSize)
{
    bool ret = true;
    try
    {
        IAsyncResult asyncSend = m\_hSocket.BeginSend(pBuffer, 0, dwSendSize, SocketFlags.None, new AsyncCallback(sendCallback), m\_hSocket);
        bool success = asyncSend.AsyncWaitHandle.WaitOne(5000, true);
        if (!success)
        {
            CloseSocket(true);
            ret = false;
            UnityLibs.Debuger.Log("Failed to SendMessage server.");
        }
    }
    catch
    {
        ret = false;
        UnityLibs.Debuger.Log("send message error");
    }

    return ret;
}

private void sendCallback(IAsyncResult async)
{
    try
    {
        //结束挂起的异步发送
        SocketError errorCode;
        Socket client = (Socket)async.AsyncState;
        int dwSendSize = client.EndSend(async,out errorCode);
        if (dwSendSize == 0) {
            CloseSocket(true);
        }

        //UnityLibs.Debuger.Log("send size :" + dwSendSize.ToString());
    }
    catch (Exception e)
    {
        CloseSocket(true);
    }
}

心跳检测

//心跳包
private void timerHandler(object state)
{
    if (m\_SocketState == enSocketState.SocketState\_Connected)
    {
        SendData(SocketModule.MDM\_KN\_COMMAND, SocketModule.SUB\_KN\_DETECT_SOCKET);
        if (m_pClientSocketDelegate != null)
        {
            m_pClientSocketDelegate.OnSocketDetect(this);
        }
    }
}

在ClientSocket的变量定义中有一个:

//连接回调 
protected IClientSocketDelegate m_pClientSocketDelegate;

通过这个变量将消息传递出去

 public class ClientSocketHelper : Singleton<ClientSocketHelper>, IClientSocketDelegate

ClientSocketHelper 实现这个接口,并接收来自ClientSocket的消息

public bool OnSocketConnect(int iErrorCode, string pszErrorDesc, IClientSocket pIClientSocket)
{
    if (iErrorCode != 0)
    {
        --ConnectTimes;
        if (ConnectTimes > 0)
        {
            //再次连接
            ConnectServer();
            return false;
        }
        else
        {
            \_connectTimes = SocketModule.CONNECT\_MAX_TIMES;
        }
    }

    if (onSocketConnect != null)
    {
        return onSocketConnect(iErrorCode, pszErrorDesc);
    }

    return false;
}

public bool OnSocketRead(CMD_Command Command, byte\[\] pBuffer, short wDataSize, IClientSocket pIClientSocket)
{
    if (onSocketRead != null)
    {
        return onSocketRead(Command, pBuffer, wDataSize);
    }

    return false;
}

public bool OnSocketClose(IClientSocket pIClientSocke, bool bCloseByServer)
{
    if (onSocketClose != null)
    {
        return onSocketClose();
    }

    return false;
}

public bool OnSocketDetect(IClientSocket pIClientSocke)
{
    if (IsScheduleClose)
    {
        _scheduleTime++;
        //UnityLibs.Debuger.Log("OnSocketDetect:" + _scheduleTime);
        if (\_scheduleTime * SocketModule.SOCKET\_DETECT >= SocketModule.CONNECT\_ACTIVE\_BACKGROUND)
        {
            CloseSocket();
        }
    }

    return true;
}

应用层又是如果接收到最终的数据呢,通过代理的方式

public delegate bool OnSocketConnectEvent(int iErrorCode, string pszErrorDesc);
public delegate bool OnSocketReadEvent(CMD_Command Command, byte\[\] pBuffer, short wDataSize);
public delegate bool OnSocketCloseEvent();
public delegate bool OnSocketErrorEvent(object data);

示例

 public class HandlerObservable : Observable
    {
        public event OnSocketConnectEvent onSocketConnect;
        public event OnSocketCloseEvent onSocketClose;
        public event OnSocketErrorEvent onSocketError;
.........................................................
public void OnInit(string addr, short port)
        {
            if (_isInit == false)
            {
                ClientSocketHelper.Instance.ServerAddr = addr;
                ClientSocketHelper.Instance.ServerPort = port;

                ClientSocketHelper.Instance.onSocketConnect += OnSocketConnect;
                ClientSocketHelper.Instance.onSocketRead += OnSocketRead;
                ClientSocketHelper.Instance.onSocketClose += OnSocketClose;

                _isInit = true;
            }
        }

因为在消息的传递过程中,是在自定义的线程中(接收线程)处理的,需要将消息传送到主线程中,方便Unity的调用。和HTTP封装类类似,需要在Update中将消息分发到具体的处理类中。 具体参考HTTP客户端的实现。 因为涉及的内容比较多,就定这些吧,

使用Socket通信需要注意的有:

1.网络连接重试,可能第一次未连接上,需要多试几次;

2.断线重连处理,移动应用网络不稳定,网络断线重连对用户透明;

3.应用退到后台,iOS会自动断开,Android不会,所以加逻辑断开;

4.IPV6的支持,曾因这个被拒了两次。

下一篇介绍服务端的实现


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 xue_huashan@163.com

文章标题:Unity开发之网络一TCP客户端实现

文章字数:2k

本文作者:max-xue

发布时间:2018-07-03, 21:05:41

最后更新:2019-11-09, 22:39:39

原始链接:http://blog.le-more.com/2018/07/03/u3d/unity-e5-bc-80-e5-8f-91-e6/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏