공부/인프런 - Rookiss

Part 4-4-6. 패킷 직렬화 : Packet Generator #1

셩잇님 2023. 12. 8. 18:17
반응형

 

 

패킷 직렬화

 

 지난 시간에는 직렬화, 역직렬화를 마무리 하는 시간을 가졌다. 이를 통해 숫자, 문자, 구조체, 리스트 등 다양한 형태의 자료형들을 어떠한 방법으로 인코딩하여 패킷을 보내주고, 이를 수신하여 읽을 때 어떻게 역직렬화 하여 데이터를 처리하는 지 알아보았다. 즉 Packet 하나를 만들어 Session에 대해서 학습하였다. 오늘 학습은 이러한 패킷들을 자동화하여 처리하는 Packet Generator에 대해서 알아보도록 하자.

 


 

📪 Packet Generator

 

 자동화는 지금까지 하드코딩으로 진행하며 작업했던 것들을 자동화하여 처리하는 것을 뜻한다. 여태껏 Session을 만들어 줄 때 자료형을 하나하나 입력해주어서 만들어 주었지만, 이제 Packet Generator를 통해 자동화 처리를 한다. 😎

 

🌱 환경설정

 

 작업을 하기 위해 새로운 프로젝트를 만들어준다. 프로젝트의 이름은 PacketGenerator 라고 설정하고, 이를 Tools 라는 폴더를 만들어 해당 폴더 내부에 넣어주도록 한다. 기존 코드를 자동화 하기 위해서는 원본에 대한 정의가 필요한데, XML, JSON, 사용자 정의 IDL 등이 있지만 루키스님은 XML이 가독성이 더 좋기 때문에 XML을 이용하여 패킷을 정의한다.

 

PacketGenerarator 프로젝트와 폴더 구성

 


 

👀 XML

 

 XML이란 JSON과 유사하게 데이터를 저장하고 전달하기 위한 수단이다. JSONd에 비해 보안이 안정적이며 유니코드 기반으로 여러 언어들을 지원하는 특징을 가진다.

 

👨‍🚒 XML 추가

 

 

 프로젝트를 우클릭하여 새 항목 추가를 눌러 데이터 항목의 XML 파일을 선택하여 "PDL"이라는 이름으로 생성하여 준다. 생성된 XML 파일 내에 우리가 넣고자 하는 데이터들을 담아주고, 이를 읽어오는 방식으로 사용 할 것인데 테스트를 위해 아래와 같이 작성한다.

 

<PDL>
	// packet이란 타입은 사용자가 직접 설정 할 수 있다.
	<packet name="Player InforReq">
    	<long name="playerId"/> // 한 줄일 경우 끝에 '/'를 넣어 닫을 수 있다.
		<string name="name"/>
		<list name ="skill">
			<int name="id"/>
			<short name="level"/>
			<float name="duration"/>
		</list>
    </packet> // <packet>을 통해 열었다면 </packet>으로 닫아주어야한다.
</PDL>

 

👨‍🚒 XML Read

 

 아래와 같이 XmlReaderSettings를 선언 후, 주석과 공백을 무시하는 설정을 진행한다.

 

 XmlReaderSettings settings = new XmlReaderSettings()
            {
                // 주석 무시
                IgnoreComments = true,
                // 스페이스바(공백) 무시
                IgnoreWhitespace = true,
            };

 

 나중에서는 XML 파일의 경로 설정을 지정해주겠지만, 현재는 테스트를 위해 PDL 파일을 실행파일로 옮겨 주도록한다.

 

 

 XMLReader를 생성해준다. Reader를 생성해준 뒤에는 이를 삭제(Dispose)해주어야 하는데 이 때에 using 문을 이용할 경우 사용 이후 알아서 삭제 처리를 할 수 있다.

 

        static void Main(string[] args)
        {
            XmlReaderSettings settings = new XmlReaderSettings()
            {
                IgnoreComments = true,
                IgnoreWhitespace = true
            };

            using (XmlReader r = XmlReader.Create("PDL.xml", settings))
            {
                // 헤더를 건너뛴다
                r.MoveToContent();

                while (r.Read())
                {
                    if (r.Depth == 1 && r.NodeType == XmlNodeType.Element)
                    {
                        ParsePacket(r);
                    }

                    // Console.WriteLine(r.Name + " " + r["name"]);
                }
            }
        }

 

문장 형태를 using
 리소스에 액세스 하는 클래스들(File, Font)는 사용 한 후에는 적절한 시기에 해제(Dispose)하여 해당 리소스(자원)을 다시 반납해야 한다. 그러나 이 때 using을 사용한다면 자동으로 Dispose를 처리한다. 👍

 

 또한 MoveToContent 메서드를 사용하면 XML 파일 최상단에 있는 헤더(Version, UTF-8) 부분을 건너뛰고 시작하게 된다. 그리고 조건문을 추가하여 depth를 비교하고 NoteType이 Element(시작부분)일 경우에만 로직이 실행되도록 작성한다.

 

Depth
 깊이라는 뜻으로 XML 내부에서 들여쓰기와 같은 부분에 해당한다고 보면 된다. 즉 들여쓰기 한번에 depth의 값이 1 증가한다. 예를 들어 PDL.xml 파일 <PDL> 부분의 depth는 0이고, <packet name="PlayerInfoReq"> 영역은 1의 값을 가지게 된다. 마찬가지로 long, int, string 자료형을 선언해 준 부분은 2의 값을 가진다고 볼 수 있다.

 

😎 실행결과

 

 

 

 XML에 작성된 내용이 정상적으로 출력되는 것을 볼 수 있다. 👍

 


 

🚶‍♂️ Packet Format 처리

 

Packet Format Class 추가

 기존에 하드코딩하며 Write, Read 하던 함수들을 복사하여 고정적으로 사용하는 영역과 유동적으로 변경되는 영역을 구분하여 처리를 진행한다.

 

 

 고정되는 영역은 그대로 두어 사용하고, 유동적으로 변경이 필요한 부분은 자동화에 따라 대체될 때 마다 적용하기 위해 아래와 같은 방법으로 수정한다.

 

 

 기존에 PlayerInfoReq Class에서 작성한 코드를 모두 가져와 작업하고 추가로 Read, Write 하는 부분 또한 복사하여 위와 같은 작업을 진행한다.

 

 

String = @""
 string을 선언하고 @"내용"을 입력한다면 내용을 위의 이미지와 같이 길게 사용하며 기호등을 모두 텍스트로 구분하여 사용 할 수 있다.
* 괄호( {} )를 사용하고 싶을 경우 두번 연속으로 사용하여 처리한다.

 


 

🚶‍♂️ PacketGenerator 추가 작성

 

 위에서 선언만 해두고 아직 만들지 않은 함수를 추가적으로 작성하여 준다.

 

ParsePacket

 해당 함수는 패킷의 시작 지점을 확인하고, 패킷의 멤버들을 불러오는 역할을 가진다. 확인하는 과정은 다음과 같다.

1. NoteType의 EndElement인가?

2. 이름이 내가 설정한 packet과 동일한가?

3. 값이 null이 아닌가?

 

 

ParseMember

해당 함수는 패킷 내부에 있는 member들을 불러오기 위한 작업을 처리하는 함수이다. Depth 값을 구해 이를 비교해주며 예외처리가 생길 때를 처리한다.

 

 public static void ParseMembers(XmlReader x)
        {
            string packetName = x["name"];

            // 내부 멤버들은 뎁스가 하나 추가되니 +1
            int depth = x.Depth + 1;
            while(x.Read())
            {
                // 순서대로 불러오기에 /packet이 오면 depth가 1이라서 
                // 알아서 나가질 것
                if (x.Depth != depth)
                    break;

                string memberName = x["name"];  
                if(string.IsNullOrEmpty(memberName))
                {
                    Console.WriteLine("Member without name");
                    return;
                }
		}

 

 이 후 예외처리에 걸러지지 않은 진짜 자료형(int, string 등)을 switch문을 통해 나누어주고, 각각의 영역에서 어떠한 동작을 정해주기만 설정하면 된다.

 

 

-완-

 

 

반응형