C#/네트워크

[c#][서버] Serialization

goliot 2024. 6. 9. 02:12
반응형

Serialization

public override void OnRecvPacket(ArraySegment<byte> buffer)
{
    ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
    ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + 2);
    Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}

public override void OnConnected(EndPoint endPoint)
{
    Console.WriteLine($"OnConnected : {endPoint}");

    Packet packet = new Packet() { size = 4, packetId = 7 };

    //보낸다
    for (int i = 0; i < 5; i++)
    {
        ArraySegment<byte> openSegment = SendBufferHelper.Open(4096); //최조 덩어리 생성
        byte[] buffer = BitConverter.GetBytes(packet.size);
        byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
        Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
        Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
        ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);

        Send(sendBuff);
    }
}
  • 위와 같이 Byte 배열에 넣고, 풀고 하는 작업
  • 이를 분리해보자

Serialization 분리

public override void OnConnected(EndPoint endPoint)
{
    Console.WriteLine($"OnConnected : {endPoint}");

    PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };

    //보낸다
    //for (int i = 0; i < 5; i++)
    {
        ArraySegment<byte> s = SendBufferHelper.Open(4096); //최조 덩어리 생성

        byte[] size = BitConverter.GetBytes(packet.size);
        byte[] packetId = BitConverter.GetBytes(packet.packetId);
        byte[] playerId = BitConverter.GetBytes(packet.playerId);

        ushort count = 0;
        Array.Copy(size, 0, s.Array, s.Offset + count, 2);
        count += 2;
        Array.Copy(packetId, 0, s.Array, s.Offset + count, 2);
        count += 2;
        Array.Copy(playerId, 0, s.Array, s.Offset + count, 8);
        count += 8;

        ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);

        Send(sendBuff);
    }
}
  • 이렇게 카운터로 바이트 수를 추적하면서 보내는 방법이 있음
  • 하지만, 비효율적이고, 뭔가 추가될 때마다 byte배열을 하나씩 더 만드는 것이 굉장히 비효율적
    • 해결해보자
public override void OnConnected(EndPoint endPoint)
{
    Console.WriteLine($"OnConnected : {endPoint}");

    PlayerInfoReq packet = new PlayerInfoReq() { packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };

    //보낸다
    //for (int i = 0; i < 5; i++)
    {
        ArraySegment<byte> s = SendBufferHelper.Open(4096); //최조 덩어리 생성

        ushort count = 0;
        bool success = true;

        //단 한 비트라도 실패했는지 검사
        count += 2;
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.packetId);
        count += 2;
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.playerId);
        count += 8;
        //실제 사이즈는 모든 작업이 끝난 후 마지막에 삽입하여야 정확
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), count);

        ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);

        if(success)
            Send(sendBuff);
    }
  • TryWriteBytes 키워드를 사용하여, 배열을 하나하나 만드는 작업을 대체할 수 있다.
  • 배열 문제는 해결 했고, 이제 count += 하는 부분을 처리해야 한다.
    • 저렇게 길게 쓰는것보단 함수로 합쳐서 호출하는게 낫지 않나?
public abstract class Packet
{ //최대한 크기를 작게 보내자
    public ushort size; //2 Byte
    public ushort packetId;

    public abstract ArraySegment<byte> Write();
    public abstract void Read(ArraySegment<byte> s);
}

class PlayerInfoReq : Packet
{
    public long playerId;

    public PlayerInfoReq()
    {
        this.packetId = (ushort)PacketID.PlayerInfoReq;
    }

    public override void Read(ArraySegment<byte> s)
    {
        ushort count = 0;

        //ushort size = BitConverter.ToUInt16(s.Array, s.Offset);
        count += 2;
        //ushort id = BitConverter.ToUInt16(s.Array, s.Offset + count);
        count += 2;
        
        //패킷 헤더의 사이즈가 적힌 만큼만 파싱을 하기 위함
        this.playerId = BitConverter.ToInt64(new ReadOnlySpan<byte>(s.Array, s.Offset + count, s.Count - count));
        count += 8;
    }

    public override ArraySegment<byte> Write()
    {
        ArraySegment<byte> s = SendBufferHelper.Open(4096); //최조 덩어리 생성

        ushort count = 0;
        bool success = true;

        //단 한 비트라도 실패했는지 검사
        count += 2;
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), this.packetId);
        count += 2;
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), this.playerId);
        count += 8;
        //실제 사이즈는 모든 작업이 끝난 후 마지막에 삽입하여야 정확
        success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), count);

        if (success == false)
            return null;

        return SendBufferHelper.Close(count);
    }
}

public enum PacketID
{
    PlayerInfoReq = 1,
    PlayerInfoOk = 2,
}

class ServerSession : Session
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected : {endPoint}");

        PlayerInfoReq packet = new PlayerInfoReq() { playerId = 1001 };

        //보낸다
        //for (int i = 0; i < 5; i++)
        {
            ArraySegment<byte> s = packet.Write();

            if(s != null)
                Send(s);
        }
    }
}
  • Read측에서, ReadOnlySpan<byte> 를 활용해, 정확히 넘어온 패킷의 헤더에 있는 size만큼 역직렬화를 하도록 함
    • Client 측을 반만 신뢰하여, size를 맞게 보내줬다고 가정, 아니라면 exception에 걸리도록
반응형

'C# > 네트워크' 카테고리의 다른 글

[c#][서버] Serialization (2)  (0) 2024.06.09
[c#][서버] Unicode & Encoding / UTF8 vs UTF16  (0) 2024.06.09
[c#][서버] PacketSession  (1) 2024.06.07
[c#][서버] RecvBuffer, SendBuffer  (1) 2024.06.07
[c#][서버] TCP vs UDP  (0) 2024.06.07