반응형

네트워크 메시지(Network Message)


지난 섹션들 중에선 Rpc와 Command, SyncVar와 Hook등을 통해서 서버와 클라이언트 사이에에서 통신을 하는 방법과 이것을 이용하기 위해 Network Behaviour를 가진 오브젝트를 스폰하는 방법을 알아보았다. 하지만 이러한 기능들은 유니티에서 정의한 네트워크 API 중에서도 높은 수준의 API(HLAPI)이고 특히 Network Server와 Network Client를 기반으로 작동하기 때문에 거기에 구애받는 제약사항이 존재한다.


Rpc와 Command 같은 원격 액션은 NetworkBehaviour에 속한다.


그 제약사항은 전 섹션인 클라이언트의 준비에서 다루었다시피 ClientRpc나 Command 같은 원격 액션과 멤버 변수 동기화를 담당하는 SyncVar와 Hook은 클라이언트가 준비되기 이전에는 동작하지 않는다는 것이다. 그렇기 때문에 클라이언트가 서버에 접속했을 때, 서버에서 클라이언트에 게임 진행에 필요한 데이터를 전송한다던가 하는 초기화 작업에는 원격 액션이나 SyncVar를 사용하지 않아야 한다.


그렇다면 클라이언트가 아직 준비되지 않은 상태에서는 어떤 방식으로 서버와 클라이언트가 통신을 해야하는가? 그 해답은 바로 네트워크 메시지(Network Message)를 사용하는 것이다. 네트워크 메시지는 위의 이미지를 보면 Network Server와 Network Client보다 Low level인 Tranport 레이어에 훨씬 가까운 것을 확인할 수 있다. 때문에 네트워크 메시지는 클라이언트가 준비 되어있는가에 대한 제약을 전혀 받지 않고 원하는 메시지를 주고 받을 수 있다.





기본적인 네트워크 메시지 전송


커스텀 네트워크 메시지 타입

public class CustomMsgType
{
    public static short YourMsgType = MsgType.Highest + 1;
}


유니티 네트워크에서 메시지를 전송하기 위해서는 몇 가지 절차가 필요한데, 첫 번째로는 당신이 보내고자하는 메시지의 타입이 무엇인지 정의하는 것이다. 유니티 네트워크에서는 기본적인 메시지를 시스템 내에서 전송하기 위해서 기본적인 메시지 타입을 정의하고 있는데, 이것은 MsgType 클래스에 상수로 정의되어 있다. 그 중에서 Highest라는 메시지 타입이 있는데 이것은 유니티가 사용하는 기본 메시지 타입 중 가장 마지막 타입을 의미하며, 이 이후의 타입은 개발자가 원하는 타입으로 정의해서 사용하면 된다는 뜻이다.


새로운 메시지를 사용할 때마다, MsgType.Highest에 +n하여 사용하면 되는데, 그냥 사용할 때마다 MsgType.Highest + n 으로 사용해도 되지만, 새로 정의한 메시지 타입의 가독성을 확보하기 위해서는 위의 예시 코드와 같이 별도의 메시지 타입 클래스를 만들고 메시지 타입 상수를 정의해서 대입해두는 것이 좋다.





유니티에서 지원하는 기본 네트워크 메시지


바로 위 파트에서는 전송하고자 하는 네트워크 메시지의 종류를 정의했다면, 이번에는 메시지의 내용을 채우는 것이다. 보내고자 하는 메시지가 단지 무언가가 되었다는 신호를 보내는 것일 수도 있지만, 일반적으로는 보내고자 하는 메시지의 내용이 있을 것이다. 이것을 위해서 유니티에서는 아주 기본적인 메시지 클래스를 제공하는데 그것은 다음과 같다.


using UnityEngine.Networking.NetworkSystem;


EmptyMessage   // 빈 메시지 아무 내용도 전송하지 않는다.
IntegerMessage  // int 를 전송하는 메시지
StringMessage    // string 을 전송하는 메시지


유니티 네트워크 내장 메시지 클래스를 사용하기 위해서는 UnityEngine.Networking.NetworkSystem을 사용해야 한다.





네트워크 메시지를 처리할 핸들러 등록하기


다음으로 할 작업은 개발자가 정의한 타입의 메시지를 처리할 핸들러를 만들고 등록하는 것이다. 기본적인 네트워크 메시지 핸들러 함수는 다음과 같이 작성하면 된다.


private void OnCustomMessageHandler(NetworkMessage netMsg)
{
}


이렇게 만들어진 핸들러를 네트워크 매니저에 등록해야하는데, 이 과정은 서버와 클라이언트가 비슷하지만 약간 차이가 있다.


public override void OnStartServer()
{
    base.OnStartServer();

    NetworkServer.RegisterHandler(CustomMsgType.YourMsgType, OnCustomMessageHandler);
}


서버에서 핸들러를 등록할때는 NetworkServer 클래스의 정적 함수를 통해서 메시지의 타입과 핸들러 함수를 전달해주면 된다.


public override void OnStartClient(NetworkClient client)
{
    base.OnStartClient(client);

    client.RegisterHandler(CustomMsgType.YourMsgType, OnCustomMessageHandler);
}


클라이언트에서는 서버와는 다르게 클라이언트가 서버에 연결될 때, 네트워크 매니저에서 생성해서 전달해주는 NetworkClient 객체에 핸들러를 등록해야 한다.


서버에서만 받을 메시지라면 서버에만 핸들러를 등록하면 되고, 클라이언트에서만 받을 메시지라면 클라이언트에만 핸들러를 등록하면 된다.





네트워크 메시지 전송하기


앞에서 커스텀 메시지 타입을 만들고, 메시지 핸들러를 등록했다면 이번에는 기본적인 메시지를 전송해보자.


우선은 서버에서 클라이언트로 메시지를 전송하는 방법이다.


public override void OnServerConnect(NetworkConnection conn)
{
    base.OnServerConnect(conn);

    EmptyMessage msg = new EmptyMessage();
    conn.Send(CustomMsgType.YourMsgType, msg);
}


서버에서 클라이언트로 메시지를 전송하는 방법의 첫 번째는 서버 콜백의 매개변수로 전달되는 클라이언트의 네트워크 커넥션에 있는 Send() 함수로 메시지를 전달하는 방법이다. 이 방법은 콜백을 보낸 클라이언트에게 콜백에 대한 처리를 한 뒤에 메시지를 보낼 때 주로 사용된다.


Dictionary<string, NetworkIdentity> playerList = new Dictionary<string, NetworkIdentity>();

public void SomeSendMessageToClientProcess(string id)
{
    EmptyMessage msg = new EmptyMessage();
    playerList[id].connectionToClient.Send(CustomMsgType.YourMsgType, msg);
}


두 번째 방법은 플레이어 객체에 있는 NetworkConnect의 Send() 함수를 호출해서 메시지를 전송하는 방법이 있다. 두 번째 방법의 경우에는 원하는 클라이언트에 메시지를 보낼 수 있다는 장점이 있다. 단, connectionToClient는 서버 측에 있는 플레이어 객체에서만 유효하다. 즉, 클라이언트 측이나, 서버라도 플레이어 객체가 아닌 네트워크 객체에서는 connectionToClient가 null 값을 가지고 있기 때문에 그 connection에 메시지를 보낼 수 없다. 때문에 플레이어 객체가 만들어질 때, 네트워크 매니저에 만들어둔 하나의 컨테이너에 방금 만들어진 플레이어 객체의 Network Identity나, 플레이어 객체의 컴포넌트인 Network Behaviour를 등록해두고 빠르게 찾을 수 있도록 만들어 주는 것이 좋다.


다음은 반대로 클라이언트에서 서버로 메시지를 전송하는 방법이다. 이것 역시 서버로 메시지를 보내는 경로는 2가지 정도가 있다.


public override void OnClientConnect(NetworkConnection conn)
{
    base.OnClientConnect(conn);

    EmptyMessage msg = new EmptyMessage();
    conn.Send(CustomMsgType.YourMsgType, msg);
}


네트워크 매니저 콜백 섹션에서 이야기 했던 것과 같이 클라이언트 콜백의 매개변수로 넘어오는 Network Connection에는 연결된 서버에 대한 정보가 담겨있기 때문에 이 매개변수의 Send를 통해서 서버에 메시지를 보낼 수 있다.


public void SomeSendMessageToServerProcess()
{
    EmptyMessage msg = new EmptyMessage();

    client.Send(CustomMsgType.YourMsgType, msg);
}


서버로 메시지를 보내는 두 번째 방법은, 네트워크 매니저 클래스의 멤버 변수인 client를 통해서 Send() 함수를 호출하는 것이다.


서버에서 클라이언트로, 클라이언트에서 서버로 메시지를 보내는 방법을 알아보았는데, 클라이언트 간의 메시지 전송 방법에 대해서 궁금할 수도 있다. 하지만 서버를 구현할 때는, 보안과 안정성을 위해서 모든 메시지가 서버를 거쳐가도록 하는 것이 좋다. 만약 클라이언트에서 다른 클라이언트로 메시지를 보내고 싶다면, 메시지를 보내고자 하는 클라이언트에서 서버에 메시지를 보낸 뒤 서버가 그 메시지를 받아야 하는 클라이언트로 메시지를 보내도록 하는 중계 방식으로 구현하는 것이 좋다.





커스텀 메시지


기본적인 메시지 클래스를 이용해서 메시지를 주고 받는 방법을 확인했으니 이번에는 전송하고자 하는 메시지 내용에 맞는 커스텀 메시지를 만들고, 전송하는 법을 알아보자.


public class CustomMessage : MessageBase
{
    public int i;
    public float f;
    public string str;
}


커스텀 메시지 클래스를 구현하는 방법은 위의 예시 코드와 같이 MessageBase를 상속받아서 메시지 클래스를 구현하면 된다. 메시지 클래스에는 기본적인 값 타입의 변수와, 구조체, 배열과 Vector3와 같은 대부분의 일반 Unity Engine 타입인 멤버를 포함할 수 있고, 복잡한 클래스나 제네릭 컨테이너는 멤버로 포함할 수 없다.


public void SomeSendMessageProcess(NetworkConnection conn)
{
    CustomMessage msg = new CustomMessage()
    {
        i = 10,
        f = 3.14f,
        str = "Hello"
    };

    conn.Send(CustomMsgType.YourMsgType, msg);
}


커스텀 메시지를 전송하는 법은 매우 간단하다. 객체를 생성하고 앞 파트에서 배운 방식대로 그 메시지를 그대로 전송하면 된다.


private void OnCustomMessageHandler(NetworkMessage netMsg)
{
    var msg = netMsg.ReadMessage<CustomMessage>();


    Debug.Log(msg.i);
    Debug.Log(msg.f);
    Debug.Log(msg.str);
}


그리고 메시지를 받는 쪽에서는 메시지가 NetworkMessage 타입의 객체로 전달되는데 이것을 ReadMessage<>() 함수로 원래 타입의 메시지 객체로 변환해서 사용하면 된다.

반응형

+ Recent posts