공부/인프런 - Rookiss

Part 4-4-10. 패킷 직렬화 : Packet Generator #5

셩잇님 2023. 12. 22. 11:20
반응형

 

 

패킷 직렬화

 

 지난 시간에는 Client/Server Session 에서 사용하는 GenPackets의 내용을 수정할 때 마다 매 번 세션에 복사, 붙여넣기 해주던 작업을 ~.bat (=배치파일)을 만들어 실행시켜주는 방법을 통해 자동화를 하였다. 추가적으로 이제 더 무엇을 자동화 하여야할까? 를 생각해보며 오늘 학습을 이어나가 보자.

 


 

👚 OnRecvPacket 개선

 

 기존에 사용하던 OnRecvPacket 스크립트는 아래와 같다.

 

 

 이를 자세히 살펴보면 ClientSession의 OnRecvPacket에서 받은 패킷 ID를 통해 Switch 문에서 검사를 해주어 작업을 진행하였다. 하지만 가면 갈수록 패킷의 종류는 다양해질 수 밖에 없기 때문에 수 많은 Switch 문을 거치는 로직은 비효율적일 수 밖에 없다. 따라서 해당 부분을 패킷 타입에 맞게 자동으로 처리하는 코드로 변경시키고자 한다.

 

🦥 GenPacket 수정

 

 우선 어떠한 방식으로 코드를 구성할지 생각해보고, 이를 자동화하는 과정을 거칠 것이다. 생각해보면 packetId를 포함한 interface를 구현한 뒤, 이를 상속받아 따로 값을 보관하여 처리하도록 한다.

 

 

 따라서 필수로 구현이 필요한 Read와 Write를 인터페이스 내부에 포함해주고, packetId를 담아둘 Protocol 이라는 변수를 선언한 뒤 이를 return 값으로 선언해 준다.

 

🕯️ 추가 영역 자동화

 

 GenPacket에 추가적으로 내용을 작성하여 수정하였기 때문에 PacketFormat을 통해 이를 자동화하는 과정을 거쳐야 한다. 따라서 PacketFormat으로 돌아가 fileFormat 영역에 IPacket 을 생성한 영역에 맞춰 동일하게 처리하여 준다.

 

 

 또한 PacketFormat 영역에서 생성한 클래스가 IPacket을 상속 받고, 자신의 protocold을 가질 수 있도록 추가적으로 작성하여 준다.

 

 

interface, abstract의 차이점은?
기본적으로 둘 다 상속을 이용해 내용을 구현하여 처리바는 방법은 유사하나, 사용 목적에 따라 차이점이 있다.

1. abstract class는 기본적으로 클래스이며, 이를 상속하여 확장하여 사용하기 위함이다.
2. interface는 말 그대로 해당 인터페이스를 구현한 객체들에 대한 동일한 사용방법과 동작을 보장하기 위함이다. 이 외에도 interface는 다중 상속이 가능하다.

 


 

🛒 ClientSession - Onrecv 자동화

 

 이제 앞에서 말했던 swtich를 통해 하나하나 비교하여 처리하던 부분을 개선할 것이다. 우선 PacketHandler와 PacketManager를 새롭게 만들어 추가해주자.

 

🏳️‍🌈 PacketManager

 

 PacketManager의 경우 여러번 등록하며 사용하는 것이 아니라, 유일한 존재로 활용되어 사용하기에 싱글톤화 하여 등록해 사용한다.

 

        #region Singleton
        static PacketManager _instance;
        public static PacketManager instance
        {
            get
            {
                if (_instance == null)
                    _instance = new PacketManager();
                return _instance;
            }
        }
        #endregion Singleton

 

 이 후, OnRecvPacket 메서드를 만들고 Client Session에 있던 OnRecvPacket() 메서드에서 동작하던 코드를 옮겨준다.

 

 

 이 후 원래 동작하던 부분에서는 설정한 싱글톤을 활영하여 동작시켜준다.

 

PacketManager.instance.OnRecvPacket(this, buffer);

 

🏳️‍🌈 Register

 

 이제 해당 패킷이 어떠한 패킷인지 알려주고, 검사하기 위한 작업들을 추가적으로 진행해야 하는데 우선 어떠한 패킷이 등록하고 알려줄 방식으로 Dictionary 자료형을 사용할 것이다.

 

Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();

 

 Dictionary 자료형은 키, 벨류로 구분되며, _onRecv 라는 Dictionary 키 값에는 packetId, 벨류 값에는 패킷을 만들어서 넣어줄 것이다. 추가적으로 _handler로 활용한 부분도 미리 제작해준다. 예시에서는 PlayerInfoReqHanlder이지만, 패킷에 따라 변경되게 처리하기 위해 제네릭을 활용하여 처리해 줄 것이다.

 

 

🏳️‍🌈 MakePacket() 메서드 제작

 

 MakePacket() 메서드를 통해 기존에 아래와 같이 패킷을 생성하고 Read 해 주었던 작업을 처리할 것이다.

 

 

 이 때 PlayerInfoReq Class 타입을 받아와야 하기 때문에 <T>를 활용하여 제네릭 타입으로 선언해준다. 받아온 T가 IPacket을 상속 받고 있으며, new() 선언이 가능한지 제약을 걸어준다. 따라서 다음과 같다.

 

void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
{
	...
}

 

Where T : 제약조건
Class나 메서드 뒤에 where T : 제약조건을 선언해 준다면 조건에 따라 원하는 제약을 설정해 줄 수 있다.

 

제약 조건 설명
where T : struct T는 값 형식
where T : class T는 참조 형식
where T : new() T는 매개 변수가 없는 생성자가 있어야 함
where T : 부모 클래스 이름 T는 부모 클래스의 자식 클래스여야 함
where T : 인터페이스 이름 T는 인터페이스를 반드시 구현해야 함.
여러개의 인터페이스를 명시 할 수 있음.
where T : U T는 다른 형식 매개 변수 U로 부터 상속받은 클래스여야 함

 

 여기에서는 T 값을 통해 PacketId Class를 넣어줄 것이다. 이를 선헌하여 Read 시켜준다.

 

 

 이 후, Delegate - Action을 선언하고, TryGetVaule로 Dictionary 에 넣어둔 값을 뽑아 out에 담아주고, handler를 통해 실행시켜준다.

 

🏳️‍🌈 PacketHandler

 

 이제 위에서 열심히 조립한 패킷들이 모두 잘 도착했다면, 타입에 맞게 캐스팅 해준 후, foreach 문을 돌며 출력할 수 있게 코드를 제작해주면 된다. 해당 부분이 Switch 문 안에 들어왔을 때 동작하던 부분이다.

 

 

 이제 멀티 쓰레드의 영향을 받지 않는 곳에 Register를 등록시켜준다면 준비는 끝이 나게 된다. 😎

 

 

 위와 같이 작성한다면 이제 어떠한 패킷인지 검사하는 것이 아닌, 해당 패킷에 맞게 생성하고 넘겨주는 방식으로 변경되어 진행된다.

 


 

실행 결과 😎

 

 

 실행한다면 아주 잘 작동한다. 끝!

 

 

반응형