C#

Packet Generator #1

monstro 2025. 1. 7. 12:20
728x90
반응형

이번 포스트부터 패킷 자동화를 수행하도록 하겠습니다.
이때 패킷의 구조에 관한 서술xml과 같은 외부 파일에 정의하도록 하겠습니다.

 

1) 패킷 구조 XML

<?xml version="1.0" encoding="utf-8" ?> 
<PDL>
	<packet name ="PlayerInfoRequire">
		<long name = "_playerID"/>
		<string name = "_name"/>
		<list name = "Stat">
			<int name = "_statID"/>
			<short name = "_level"/>
			<float name = "_connectDuration"/>
		</list>
	</packet>
</PDL>

 

2) 패킷을 자동으로 생성하는 PacketGenerator

class PacketGenerator
{
    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"]);
            }
        }
    }

    // XML 파일이 패킷 구조를 정의한 XML인지 판단하는 함수
    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;
        }

        ParsePacketMember(reader);
    }

    // 패킷 구조를 정의한 XML 파일의 내부(=패킷의 멤버)를 파싱하는 함수
    public void ParsePacketMember(XmlReader reader)
    {
        string packetName = reader["name"];

        // 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;
            }

            // 파싱한 XML의 각 단계마다 동작을 수행
            string memberType = reader.Name.ToLower();
            switch (memberType)
            {
                case "bool":
                case "byte":
                case "short":
                case "ushort":
                case "int":
                case "long":
                case "float":
                case "double":
                case "list":
                    break;
                default:
                    break;
            }
        }
    }
}

 

3) 패킷의 형식을 정의하는 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} 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 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;";
    }

 

이제 PacketGenerator에서 패킷을 생성하는 경우,

PacketFormat에서 {}로 표현된 대체가능한 부분대체하여 생성하게 됩니다.

728x90
반응형

'C#' 카테고리의 다른 글

Packet Generator #3  (0) 2025.01.08
Packet Generator #2  (0) 2025.01.07
직렬화(Serialization) 4  (0) 2025.01.06
직렬화(Serialization) 3  (0) 2025.01.06
UTF-8과 UTF-16  (0) 2025.01.06