숫자, 문자, 구조체와 같은 형태들을 어떻게 인코딩하여 패킷으로 넘겨주고, 디코딩하고 읽어올 수 있는지 Packet Session을 통해 제작해보았다.
자동화는 하드코딩으로 진행하여 구성한 뒤 자동화할 수 있는 부분으로 변경하는 방식을 사용하면 좋다.
지금까지 하드코딩으로 하나하나 입력해주었지만, 이제 Packet Generator를 통해 자동화 처리를해주고자한다.
작업을 하기 위해 새로운 콘솔앱을 추가하고, Tools라는 폴더를 만들어 넣어둔다.
기존에 코드를 자동화 하기 위해서는 원본에 대한 정의가 필요하다. 여기서는 XML이 가독성이 더 좋기에, XML로 사용 패킷 정의 방식을 사용할 것이다.
XML
XML이란 JSON과 비슷하게 데이터를 저장하고 전달하기 위한 수단이다. JSON에 비해 보안이 안정적이며 여러 인코딩들을 지원한다는 특징을 가지고 있다.
새항목 추가로 XML 파일을 추가한다.
추가 된 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>
변환이 필요한 값들을 담아서 불러온다.
XmlReaderSettings
선언하고 주석과 공백 무시 설정 한다.
XmlReaderSettings settings = new XmlReaderSettings()
{
// 주석 무시
IgnoreComments = true,
// 스페이스바(공백) 무시
IgnoreWhitespace = true,
};
나중에는 경로설정을 해주겠지만 지금 테스트를 위해서는 PDL 파일이 실행파일 폴더에 있어야 한다.
XmlReader
를 생성(Creat)해준다. 생성 해준 뒤에는 닫아(Dispose)주어야하는데 여기서 using문을 사용하여 알아서 닫아주게 만들 수 있다.
문장형태의 using
리소스에 액세스 하는 클래스들(File, Font)은 다 사용한 후에는 적절한 시기에 해제(Dispose)하여 해당 리소스(자원)을 다시 반납해야하는데, using을 사용하면 자동으로 Dispose를 한다
MoveToContent()
를 사용하면 앞의 헤더(Versiom, UTF-8) 부분을 넘기고 시작하게 한다.
XmlReaderSettings settings = new XmlReaderSettings()
{
// 주석 무시
IgnoreComments = true,
// 스페이스바(공백) 무시
IgnoreWhitespace = true,
};
// XML 을 생성하고 파싱 한 뒤에는 다시 닫아줘야 한다.
// 이때 using을 사용한다면 해당 영역에서 벗어날 때 Dispose를 진행
using (XmlReader x = XmlReader.Create("PDL.xml", settings))
{
// 버전 정보같은 헤더 영역은 건너 뛴다.
x.MoveToContent();
// 스트림 방식으로 읽어드림
while(x.Read())
{
if (x.Depth == 1 && x.NodeType == XmlNodeType.Element)
ParsePacket(x);
//Console.WriteLine(x.Name + " " + x["name"]);
}
}
depth에서 1과 같고, NoteType이 Elemnet(즉 시작 부분)인 경우에만 통과되도록 짠다.
depth
깊이 라는 뜻으로 한 단계 더 들어갈 경우 depth 값이 1 증가한다.<PDL>
부분의 depth가 0이라면<packet name="PlayerInforReq">
부분은 1이다. 그 안의 long, string 같은 경우는 2가된다.
기존에 하드코딩하며 인코딩, 디코딩했던 코드들을 복사하여 고정영역과 유동영역을 구분한다.
고정되는 부분은 그대로 두고, 사용하는 곳에 따라 달라지는 부분은 추후 대체될 수 있도록 {0}, {1} 이런 식으로 변경한다.
기존에 PlayerInfoReq로 Class에서 작성한 코드는 모두 가져와 작업하고 추가로 read, write의 인코딩, 디코딩 하는 부분도 가져와서 작업을 해준다.
string = @""
string을 선언하고@"내용"
해주면 내용을 예시들과 같이 길게 선언하고, 기호({}
,()
등)를 모두 텍스트로 구분하게 할 수 있다.
- 괄호
{}
사용하고 싶으면 두 번 연속으로 사용해주면 된다.
실제 데이터가 들어가야할 부분이기에
read와 write 모두 유동적인 부분들은 대체 처리를 해준다.
위에서 선언만 해두고 아직 만들지 않은 함수를 추가로 제작한다.
이 함수는 패킷의 시작 지점인지 확인하고, 해당 패킷의 멤버들을 불러오는 역할을 한다.
확인 과정은 아래와 같다.
이제 packet의 내부에있는 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;
}
}
예외사항들이 모두 걸리진 이후는 string, int 이런 타입 마다 인코딩, 디코딩 방식이 다르기 때문에 해당 타입별로 어떤 동작을 할 지를 정해주면 된다.