728x90
반응형
이번 포스트에서는 패킷을 자동으로 생성하는 과정에서 패킷에 포함되어 있던
구조체와, 구조체를 담은 자료구조까지 패킷에 포함시켜 생성해보도록 하겠습니다.
기존의 코드에서 일부 변경사항이 추가되었고,
마지막에는 기존의 세션에서 사용하던 패킷 코드를 자동 생성된 패킷으로 교체헤보도록 하겠습니다.
염두할 변경사항으로는 기존의 StatInfo로 사용하던 구초제 이름을 Stat으로 변경한 것입니다.
1) PacketGenerator
class Program
{
static string genPackets;
static void Main(string[] args)
{
XmlReaderSettings settings = new XmlReaderSettings()
{
IgnoreComments = true,
IgnoreWhitespace = true,
};
using (XmlReader reader = XmlReader.Create("PDL.xml", settings))
{
reader.MoveToContent();
while (reader.Read())
{
// 파싱 중인 XML 파일의 깊이가 1이라는 것은 해당 XML 파일에 문제없이 접근한 것을 의미
if (reader.Depth == 1)
ParsePacket(reader);
// Console.WriteLine(reader.Name + " " + reader["name"]);
}
File.WriteAllText("GenPackets.cs", genPackets);
}
}
// XML 파일이 패킷 구조를 정의한 XML인지 판단하는 함수
static public void ParsePacket(XmlReader reader)
{
// XML 구조에서 처음 읽어들인 단계가 마지막 element라면 리턴
if (reader.NodeType == XmlNodeType.EndElement)
return;
// XML 구조에서 처음 읽어들인 단계가 packet이 아니라면 리턴
if (reader.Name.ToLower() != "packet")
{
Console.WriteLine("Invalid Packet Node");
return;
}
string packetName = reader["name"];
// 파싱해온 XML 단계의 이름이 null이거나 비어있다면 리턴
if (string.IsNullOrEmpty(packetName))
{
Console.WriteLine("Packet Without Name");
return;
}
Tuple<string, string, string> packetTuple = ParsePacketMember(reader);
genPackets += string.Format(
PacketFormat.packetFormat,
packetName,
packetTuple.Item1,
packetTuple.Item2,
packetTuple.Item3
);
}
// {1} 패킷 멤버
// {2} 멤버 변수 Read
// {3} 멤버 변수 Write
// 패킷 구조를 정의한 XML 파일의 내부(=패킷의 멤버)를 파싱하는 함수
static public Tuple<string, string, string> ParsePacketMember(XmlReader reader)
{
string packetName = reader["name"];
string memberCode = "";
string readCode = "";
string writeCode = "";
// XML의 구조에서 멤버를 순회하는 과정
int depth = reader.Depth + 1;
while (reader.Read())
{
if (reader.Depth != depth)
break;
string memberName = reader["name"];
if (string.IsNullOrEmpty(memberName))
{
Console.WriteLine("Member Without Name");
return null;
}
// 줄바꿈이 있는 경우, 개행하기 위한 코드
if (string.IsNullOrEmpty(memberCode) == false)
memberCode += Environment.NewLine;
if (string.IsNullOrEmpty(readCode) == false)
memberCode += Environment.NewLine;
if (string.IsNullOrEmpty(writeCode) == false)
memberCode += Environment.NewLine;
// 파싱한 XML의 각 단계마다 동작을 수행
string memberType = reader.Name.ToLower();
switch (memberType)
{
case "bool":
case "byte":
case "short":
case "ushort":
case "int":
case "long":
case "float":
case "double":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readFormat, memberName, ToMemeberType(memberType), memberType);
writeCode += string.Format(PacketFormat.writeFormat, memberName, memberType);
break;
case "string":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readStringFormat, memberName);
writeCode += string.Format(PacketFormat.writeStringFormat, memberName);
break;
case "list":
Tuple<string, string, string> tuple = ParseList(reader);
memberCode += tuple.Item1;
readCode += tuple.Item2;
writeCode += tuple.Item3;
break;
default:
break;
}
}
memberCode = memberCode.Replace("\n", "\n\t");
readCode = readCode.Replace("\n", "\t\t");
writeCode = writeCode.Replace("\n", "\t\t");
return new Tuple<string, string, string>(memberCode, readCode, writeCode);
}
// 구조체를 저장한 리스트를 패킷에서 파싱하는 함수
public static Tuple<string, string, string> ParseList(XmlReader reader)
{
string listName = reader["name"];
if (string.IsNullOrEmpty(listName))
{
Console.WriteLine("List without Name");
return null;
}
Tuple<string, string, string> tuple = ParsePacketMember(reader);
string memberCode = string.Format(
PacketFormat.memberListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName),
tuple.Item1,
tuple.Item2,
tuple.Item3
);
string readCode = string.Format(
PacketFormat.readListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName)
);
string writeCode = string.Format(
PacketFormat.writeListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName)
);
return new Tuple<string, string, string>(memberCode, readCode, writeCode);
}
// 패킷에서 추출한 문자열로 표시된 자료형을 기준으로 To~ 함수를 찾는 함수
public static string ToMemeberType(string memberType)
{
switch (memberType)
{
case "bool":
return "ToBoolean";
case "byte":
case "short":
return "ToInt16";
case "ushort":
return "ToUInt16";
case "int":
return "ToInt32";
case "long":
return "ToInt64";
case "float":
return "ToSingle";
case "double":
return "ToDouble";
default:
return "";
}
}
// 문자열의 첫 시작 부분을 대문자로 바꾸는 함수
public static string FirstCharToUpper(string input)
{
if(string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToUpper() + input.Substring(1);
}
// 문자열의 첫 시작 부분을 소문자로 바꾸는 함수
public static string FirstCharToLower(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToLower() + input.Substring(1);
}
}
최종적으로 main 함수가 실행되면 실해파일이 있는 경로에 GenPackets.cs 파일이 생성됩니다.
해당 파일에는 자동으로 생성한 패킷이 들어있습니다.
2) PacketFormat
class PacketFormat
{
// {0} 패킷 이름
// {1} 패킷 멤버
// {2} 멤버 변수 Read
// {3} 멤버 변수 Write
public static string packetFormat =
@"
class {0}
{{
{1}
public void Read(ArraySegment<byte> segment)
{{
ushort count = 0;
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(segment.Array, segment.Offset, segment.Count);
count += sizeof(ushort);
count += sizeof(ushort);
{2}
}}
public ArraySegment<byte> Write()
{{
ArraySegment<byte> segment = SendBufferHelper.Open(4096);
bool success = true;
ushort offset = 0;
Span<byte> span = new Span<byte>(segment.Array, segment.Offset, segment.Count);
offset += sizeof(ushort);
success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), (ushort)PacketID.{0});
offset += sizeof(ushort);
{3}
success &= BitConverter.TryWriteBytes(span, offset);
if (success == false)
return null;
return SendBufferHelper.Close(offset);
}}
}}
";
// {0} 변수의 자료형
// {1} 변수의 이름
public static string memberFormat =
@"public {0} {1};";
// {0} 리스트 이름 {대문자}
// {1} 리스트 이름 {대문자}
// {2} 멤버 변수들
// {3} 멤버 변수 Read
// {4} 멤버 변수 Write
public static string memberListFormat =
@"public new List<{0}> _{1}List = new List<{0}>();
public struct {0}
{{
{2}
public void Read(ReadOnlySpan<byte> span, ref ushort count)
{{
{3}
}}
public bool Write(Span<byte> span, ref ushort offset)
{{
bool success = true;
{4}
return success;
}}
}}";
// {0} 변수 이름
// {1} To~변수의 자료형
// {2} 변수의 자료형
public static string readFormat =
@"this.{0} = BitConverter.{1}(span.Slice(count, span.Length - count));
count += sizeof({2});";
// {0} 변수 이름
public static string readStringFormat =
@"ushort {0}Len = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
count += sizeof(ushort);
this.{0} = Encoding.Unicode.GetString(span.Slice(count, {0}Len));
count += {0}Len;";
// {0} 리스트 이름 - 대문자
// {1} 리스트 이름 - 소문자
public static string readListFormat =
@"_{1}List.Clear();
ushort {1}Len = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
count += sizeof(ushort);
for (int i = 0; i < {1}Len; i++)
{{
{0} {1} = new {0}();
{1}.Read(span, ref count);
_{1}List.Add({1});
}}";
// {0} 변수 이름
// {1} 변수의 자료형
public static string writeFormat =
@"success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), {0});
offset += sizeof({1});";
// {0} 변수 이름
public static string writeStringFormat =
@"ushort {0}Len = (ushort)Encoding.Unicode.GetBytes(this.{0}, 0, this.{0}.Length, segment.Array, segment.Offset + offset + sizeof(ushort));
success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), {0}Len);
offset += sizeof(ushort);
offset += {0}Len;";
// {0} 리스트 이름 - 대문자
// {1} 리스트 이름 - 소문자
public static string writeListFormat =
@"success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), (ushort)_{1}List.Count);
offset += sizeof(ushort);
foreach ({0} {1} in _{1}List)
success &= {1}.Write(span, ref offset);
";
}
패킷의 양식을 나타내는 기존의 코드에 위와 같이
구조체의 Read와 Write 그리고, 해당 구조체를 저장하는 자료구조에 대한 Read와 Write까지 수행하는 과정을
포함하였습니다. 이제 자동으로 생성된 패킷으로 기존의 패킷을 대체한 후 실행결과를 확인해보겠습니다.
문제없이 실행되는 것을 확인할 수 있습니다.
728x90
반응형
'C#' 카테고리의 다른 글
Packet Generator #4 (0) | 2025.01.08 |
---|---|
Packet Generator #3 (0) | 2025.01.08 |
Packet Generator #1 (0) | 2025.01.07 |
직렬화(Serialization) 4 (0) | 2025.01.06 |
직렬화(Serialization) 3 (0) | 2025.01.06 |