C#

직렬화(Serialization) 4

monstro 2025. 1. 6. 16:33
728x90
반응형

이번에는 패킷안에 스탯과 같은 구조체가 존재하고

이 구조체의 인스턴스를 저장하는 자료구조가 있는 경우

Read와 Write를 수행할 수 있도록 코드를 수정해보겠습니다.

 

1) 패킷의 소스코드

// 패킷의 인자를 설정하는 경우, 최대한 용량을 줄여주는 것이 좋음
public abstract class Packet
{
    // 패킷의 용량
    public ushort _packetSize;
    // 패킷의 아이디
    public ushort _packetID;

    // 패킷을 읽고, 패킷에 쓰기 위한 함수
    public abstract void Read(ArraySegment<byte> segment);
    public abstract ArraySegment<byte> Write();
}

class PlayerInfoRequire : Packet
{
    public long _playerID;
    public string _name;
    public StatInfo _statInfo;

    public List<StatInfo> _statInfoList = new List<StatInfo>();

    public struct StatInfo
    {
        public int _statID;
        public short _level;
        public float _connectDuration;

        public bool Write(Span<byte> span, ref ushort offset)
        {
            bool success = true;

            success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), this._statID);
            offset += sizeof(int);
            success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), this._level);
            offset += sizeof(short);
            success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), this._connectDuration);
            offset += sizeof(float);

            return success;
        }

        public void Read(ReadOnlySpan<byte> span, ref ushort count)
        {
            _statID = BitConverter.ToInt32(span.Slice(count, span.Length - count));
            count += sizeof(int);
            _level = BitConverter.ToInt16(span.Slice(count, span.Length - count));
            count += sizeof(short);
            _connectDuration = BitConverter.ToSingle(span.Slice(count, span.Length - count));
            count += sizeof(float);
        }
    }

    public PlayerInfoRequire()
    { 
        this._packetID = (ushort)PacketID.PlayerInfoRequire;
    }

    public override 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);
        
        // 패킷을 분해 - 바디(그중에서도 _playerID) 
        this._playerID = BitConverter.ToInt64(span.Slice(count, span.Length - count));
        count += sizeof(long);

        // 패킷을 분해 - 바디(그중에서도 _name)
        ushort nameLen = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
        count += sizeof(ushort);
        this._name = Encoding.Unicode.GetString(span.Slice(count, nameLen));
        count += nameLen;

        // 패킷을 분해 - 바디(그중에서도 _statList)
        _statInfoList.Clear();
        ushort statLen = BitConverter.ToUInt16(span.Slice(count, span.Length - count));
        count += sizeof(ushort);
        // _statList를 순회하면서 Stat을 Read
        for (int i = 0; i < statLen; i++)
        { 
            StatInfo stat = new StatInfo();
            stat.Read(span, ref count);
            _statInfoList.Add(stat);
        }
    }

    public override 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), this._packetID);
        offset += sizeof(ushort);
        
        // 패킷을 조립 - 바디(그중에서 _playerID)
        success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), this._playerID);
        offset += sizeof(long);

        // 패킷을 조립 - 바디(그중에서 _name)
        ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this._name, 0, this._name.Length, segment.Array, segment.Offset + offset + sizeof(ushort));
        success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), nameLen);
        offset += sizeof(ushort);
        offset += nameLen;

        // 패킷을 조립 - 바디(그중에서도 _statList)
        success &= BitConverter.TryWriteBytes(span.Slice(offset, span.Length - offset), (ushort)_statInfoList.Count);
        offset += sizeof(ushort);
        // _statList를 순회하면서 Stat을 Write
        foreach (StatInfo stat in _statInfoList)
        {
            success &= stat.Write(span, ref offset);
        }

        // 패킷 완성
        success &= BitConverter.TryWriteBytes(span, offset);

        if (success == false)
            return null;

        return SendBufferHelper.Close(offset);
    }
}

 

패킷을 위와 같이 수정하였습니다.

StatInfo 구조체를 만들어 3개의 멤버 변수를 갖게끔 설정하였고,

StatInfo 구조체에서는 내부적으로 Read와 Write를 수행할 수 있도록 멤버 함수를 만들었습니다.

 

패킷에서는 StatInfo 구조체를 저장할 수 있는 List를 만들었고,

패킷의 Read와 Write에서해당 List를 순회하면서 List의 인덱스마다 Read와 Write를 수행합니다.

 

2) 서버의 ClientSession

class ClientSession : PacketSession
{
    
	.
	.
	.

    public override void OnRecvPacket(ArraySegment<byte> buffer)
    {
        ushort count = 0;

        ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
        count += 2;
        ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
        count += 2;

        switch ((PacketID)id)
        {
            case PacketID.PlayerInfoRequire:
                {
                    PlayerInfoRequire playerInfoRequire = new PlayerInfoRequire();
                    playerInfoRequire.Read(buffer);
                    Console.WriteLine($"PlayerInfoReq ID : {playerInfoRequire._playerID}");
                    Console.WriteLine($"PlayerInfoReq Name : {playerInfoRequire._name}");

                    foreach (PlayerInfoRequire.StatInfo stat in playerInfoRequire._statInfoList)
                    { 
                        Console.WriteLine($"{playerInfoRequire._name}`s StatID is {stat._statID}");
                        Console.WriteLine($"{playerInfoRequire._name}`s Level is {stat._level}");
                        Console.WriteLine($"{playerInfoRequire._name}`s ConnectDuration is {stat._connectDuration}");
                    }
                }
                break;
        }

        Console.WriteLine($"RecvPacketId : {id}, Size : {size}");
    }
}

 

서버에서는 클라이언트로부터 보내진 패킷을 확인할 수 있도록

OnRecvPacket 함수를 위와 같이 수정하였습니다.

최종 실행결과는 다음과 같습니다.

728x90
반응형

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

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