Unity 게임 TCP 연결 및 네트워크 재연결 논의

8847 단어

Unity 게임 TCP 연결 및 네트워크 재연결 논의


Unity에서는 일반적으로 TcpClient 를 사용하여 Tcp 연결을 하고 TcpClient 비동기식 읽기와 쓰기를 지원하여 우리가 네트워크 데이터 발송을 따로 개척해야 하는 것을 피한다.비동기적인 읽기와 쓰기는 종종 사람들로 하여금 머리를 쓰지 못하게 하고 비교적 곤혹스럽게 한다.

1. 연결 설정

/// 
///  
/// 
public void ConnectServer (string host, int port)
{
    Log.Instance.infoFormat ("start connect server host:{0}, port:{1}", host, port);
    lock (lockObj) {
        //  
        if (null != client) {
            Close ();
        }
        //  
        client = new TcpClient ();
        client.SendTimeout = 1000;
        client.ReceiveTimeout = 1000;
        client.NoDelay = true;
        IsConnected = false;
        connectingFlag = true;
        try {
            client.BeginConnect (host, port, new AsyncCallback (OnConnect), client);
            
            //  , 。 。
            TimerManager timer = AppFacade.Instance.GetManager (ManagerName.Timer);
            timer.AddTask (OnConnectTimeout, CONN_TIMEOUT);
        } catch (Exception e) {
            Log.Instance.error ("connect server error", e);
            //  
            NetworkManager.AddEvent (Protocal.ConnectFail, null);
        }
    }
}

2. 비동기식 연결 결과 처리

/// 
///  
/// 
void OnConnect (IAsyncResult asr)
{
    lock (lockObj) {
        TcpClient client = (TcpClient)asr.AsyncState;
        bool validConn = (client == this.client);
        connectingFlag = false;
        try {
            //  
            client.EndConnect (asr);

            //  
            if (!validConn) {
                client.Close ();
            }

            if (client.Connected) {
                Log.Instance.info ("connect server succ");

                //  socket 
                socketStream = client.GetStream ();
                socketStream.BeginRead (byteBuffer, 0, MAX_READ, new AsyncCallback (OnRead), new SocketState (client, socketStream));

                //  
                IsConnected = true;
                NetworkManager.AddEvent (Protocal.Connect, null);
            } else {
                //  
                Log.Instance.info ("connect server failed");

                NetworkManager.AddEvent (Protocal.ConnectFail, null);
            }
        } catch (SocketException e) {
            Log.Instance.error ("connect error", e);

            if (validConn) {
                //  
                NetworkManager.AddEvent (Protocal.ConnectFail, null);
            } else {
                client.Close ();
            }
        }
    }
}

3. 연결 시간 초과 처리

/// 
///  
/// 
void OnConnectTimeout ()
{
    lock (lockObj) {
        if (connectingFlag) {
            Log.Instance.error ("connect server timeout");

            //  
            NetworkManager.AddEvent (Protocal.ConnectFail, null);
        }
    }
}

4. 비동기식 데이터 읽기

/// 
///  
/// 
void OnRead (IAsyncResult asr)
{
    int bytesRead = 0; //  
    bool validConn = false; //  

    SocketState socketState = (SocketState)asr.AsyncState;
    TcpClient client = socketState.client;
    if (client == null || !client.Connected) {
        return;
    }

    lock (lockObj) {
        try {
            validConn = (client == this.client);
            NetworkStream socketStream = socketState.socketStream;

            //  
            bytesRead = socketStream.EndRead (asr);

            if (bytesRead < 1) { 
                if (!validConn) {
                    //  
                    socketStream.Close ();
                    client.Close ();
                } else {
                    //  
                    //  
                    OnDisconnected (DisType.Disconnect, "bytesRead < 1");
                }
                return;
            }

            //  , 
            OnReceive (byteBuffer, bytesRead); 

            //  
            Array.Clear (byteBuffer, 0, byteBuffer.Length);   // 
            socketStream.BeginRead (byteBuffer, 0, MAX_READ, new AsyncCallback (OnRead), socketState);
        } catch (Exception e) {
            Log.Instance.errorFormat ("read data error, connect valid:{0}", e, validConn);

            if (validConn) {
                //  
                OnDisconnected (DisType.Exception, e);
            } else {
                socketStream.Close ();
                client.Close ();
            }
        }
    }

    //  
    if (bytesRead > 0) {
        OnDecodeMessage ();
    }
}

데이터의 패키지 해제와 봉인에 대해 추천MiscUtil 이 라이브러리는 매우 유용하고 대단위 소규모 모드가 모두 잘 처리될 수 있습니다.

5. 메시지 보내기

/// 
///  
/// 
public bool SendMessage (Request request)
{
    try {
        bool ret = WriteMessage (request.ToBytes ());
        request.Clear ();
        return ret;
    } catch (Exception e) {
        Log.Instance.errorFormat ("write message error, requestId:{0}", e, request.GetRequestId ());
    }
    return false;
}
/// 
///  
/// 
bool WriteMessage (byte[] message)
{
    bool ret = true;
    using (MemoryStream ms = new MemoryStream ()) {
        ms.Position = 0;
        EndianBinaryWriter writer = new EndianBinaryWriter (EndianBitConverter.Big, ms);
        int msglen = message.Length;
        writer.Write (msglen);
        writer.Write (message);
        writer.Flush ();

        lock (lockObj) {
            if (null != socketStream) {
                byte[] bytes = ms.ToArray ();
                socketStream.BeginWrite (bytes, 0, bytes.Length, new AsyncCallback (OnWrite), socketStream);
                
                ret = true;
            } else {
                Log.Instance.warn ("write data, but socket not connected");
                ret = false;
            }
        }
    }
    return ret;
}

/// 
///  
/// 
void OnWrite (IAsyncResult r)
{
    lock (lockObj) {
        try {
            NetworkStream socketStream = (NetworkStream)r.AsyncState;
            socketStream.EndWrite (r);
        } catch (Exception e) {
            Log.Instance.error ("write data error", e);
            if ((e is IOException) && socketStream == this.socketStream) {
                // IO  
                OnDisconnected (DisType.Exception, e);
            }
        }
    }
}

6. 총결산


병발을 방지하기 위해 여기에서 사용lock은 공유 변수client, socketStream에 대해 모두 자물쇠를 넣었다.이상이 발생하면 연결이 끊어질 때 이벤트 메커니즘을 통해 상부 사용자에게 던져주고 상부 사용자가 이 이상을 어떻게 처리하는지 결정한다.

7. 인터럽트 재연결 처리


단선 재연결 1단계 감청TcpClient이 사용되는 과정에서 이상 발생 후 재연결 논리를 촉발한다.그러나 이동단에서 비교적 중요한 점은 백그라운드에서 프론트로 돌아가는 과정에서 네트워크 연결 상태를 제때에 검사하고 다시 연결해야 한다.
Android 백그라운드에서 프론트 데스크톱으로 돌아가는 이벤트 흐름
onPause( ) -> onResume -> focusChanged(false) -> focusChanged(true) ( 3 )
  focusChanged(false) -> focusChanged(true) //  , 

IOS 백그라운드에서 프론트 데스크로 돌아가는 이벤트 흐름
IOS   resignActive( ) -> enterBackground -> enterForeground -> becomeActive ( 3 )
  resignctive -> becomeActive

위에서 보듯이
  • 안드로이드는 focusChanged(false)->focusChanged(true)를 감청할 수 있으며, onPause는 focusChanged(false)로 삼아야 한다.두 차례의 이벤트 간격을 기록한다. 예를 들어 간격이 너무 길면 직접 연결을 다시 만들고 비교적 짧으면 바로 네트워크 검사를 한다.
  • IOS는 resignctive -> becomeActive
  • TcpClient 0 , :
    /// 
    ///  socket 
    /// 
    /// true, if socket was checked, false otherwise.
    public bool CheckSocketState ()
    {
        Log.Instance.info ("check socket state start");
    
        // socket 
        if (client == null) {
            return true;
        }
    
        //  
        if (!client.Connected) {
            Log.Instance.info ("check socket state end, socket is not connected");
            return false;
        }
    
        //  
        bool connectState = true;
        Socket socket = client.Client;
        bool blockingState = socket.Blocking;
        try {
            byte[] tmp = new byte[1];
    
            socket.Blocking = false;
            socket.Send (tmp, 0, 0);
            connectState = true; //  Send catch , try 
    
            Log.Instance.info("check socket state succ");
        } catch (SocketException e) {
            Log.Instance.warnFormat ("check socket error, errorCode:{0}", e.NativeErrorCode);
    
            // 10035 == WSAEWOULDBLOCK
            if (e.NativeErrorCode.Equals (10035)) {
                // Still Connected, but the Send would block
                connectState = true;
            } else {
                // Disconnected
                connectState = false;
            }
        } finally {
            socket.Blocking = blockingState;
        }
    
        return connectState;
    }
    

    좋은 웹페이지 즐겨찾기