Serialization이란 뭘까?
한글로는 직렬화 라는 뜻을 지닌다
직렬화는 객체나 데이터 구조를 Byte Stream같은 전송 및 저장에 적합한 연속적인
형태로 변환하는 과정
어디다가써요?
데이터를 파일이나 네트워크를 통해 다른 시스템이나 나중에 다시 사용하기 위해 저장할 수 있다.
반대로 바이트 스트림을 원래 객체로 복원하는 과정을 역 직렬화(DeSerialization)라 부른다
우리는 이번 강의에서 진행할 것은
여러 직렬화의 방식중 몇 가지만 다룰 것이고
직렬화를 통해 네트워크에 Send 및 Recv를 진행 할 예정
직렬화 관련 강의가
총 1~4장의 파트로 나뉘어져있지만 계속 개선을 하는 강의 방향이므로
중요부 내용만 설명할 예정
부모 클래스 소개
- 현재 클래스의 변수의 선언 이유를 소개할것
public abstract class Packet
{
public ushort size;
public ushort packetId;
public abstract ArraySegment<byte> Write();
public abstract void Read(ArraySegment<byte> sagment);
}우리는 Server와 패킷을 주고 받으면서 데이터를 보내고 확인을 할 것이다
Size
public ushort size;이전에 TCP에 알아봤을 때 특징 중 하나는
데이터를 조각내서 보낼 경우도 있다고 했다
받는 입장에서는 패킷이 완전체로 왔는지 조각나서 왔는지 확인을 할 방도가 없다
그래서 Packet의 size를 넣어줌으로 패킷이 완전체로 왔는지 확인을 할 수 있게된것
PacketId
public ushort packetId;송 수신할때 하나의 약속을 통해 이 Id는 어떤것에 해당하는것이니
이렇게 읽어들여라 라고 해주는 Id값
메서드
public abstract ArraySegment<byte> Write();
public abstract void Read(ArraySegment<byte> sagment); 상속받은 패킷의 데이터를 어떻게 쓰고 읽을지를 강제성을 부여해주기 위해서 선언
자식 클래스 설명 (Primitive Type만 소개)
Primitive Type (long)예시 데이터 송 수신
가변적 데이터 송 수신은 설명X
public long playerId;Write
- 송신 측에서 Send보낼 데이터 버퍼 생성
- count값 설정 우리가 보낸 byte갯수를 알고있어야지 수신측에서 알 수 있음
| 또한 우리가 만든 버퍼의 크기가 문제없는지 확인도 가능 - success를 통해 버퍼에 데이터를 잘 쌓았는지 확인
- BitConverter의 TryWriteByte를 사용하기 위해
Span으로 우리가 사용 할 데이터의 크기를 찝어줌 - TryWriteBtye를 사용해 데이터 삽입
- 문제 없다면 Send
Span MSDN : https://learn.microsoft.com/ko-kr/dotnet/api/system.span-1?view=net-8.0
TryByWrite MSDN : https://learn.microsoft.com/ko-kr/dotnet/api/system.bitconverter.trywritebytes?view=net-9.0
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
ushort count = 0;
bool success = true;
Span<byte> sagment = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);
count += sizeof(ushort);
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), this.packetId);
count += sizeof(ushort);
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), this.playerId);
count += sizeof(long);
success &= BitConverter.TryWriteBytes(sagment, count);
if (false == success)
return null;
return SendBufferHelper.Close(count);Read
- Buff가 도착했다는것은 데이터가 전부 왔다는 뜻
- 이걸 가지고 하나하나 데이터를 까봐야함
- BitConverter를 이용한 데이터를 가져오기
- 각 데이터 고유의 크기 만큼 함수를 결정 (예 : ToUInt16, ToInt64 등)
- count값으로 어디서부터 읽어야 할지 정해줌
--- 외부 처리 ---
ushort count = 0;
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 2;
ushort packetId = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 2;
--- 내부 처리 ---
public override void Read(ArraySegment<byte> sagment)
{
ushort count = 0;
ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(sagment.Array, sagment.Offset, sagment.Count);
count += sizeof(ushort);
count += sizeof(ushort);
this.playerId = BitConverter.ToInt64(s.Slice(count, s.Length - count));
count += sizeof(long);
}
--- 나눔 처리이유 --
고유의 Read방식을 처리할 거 이기에 공용된 데이터는 밖에서 꺼내서 처리
-- 이외 개개인의 별도 처리 --
switch ((PacketID)packetId)
{
case PacketID.PlayerInfoReq:
{
PlayerInfoReq req = new PlayerInfoReq();
req.Read(buffer);
Console.WriteLine($"PlayerInfoReq {req.playerId}");
}
break;
case PacketID.PlayerInfoOk:
break;
}
Console.WriteLine($"Recv PacketId {packetId}, Size {size}");전체 코드
class PlayerInfoReq : Packet
{
public long playerId;
public string name;
public struct SkillInfo
{
public int id;
public short level;
public float duration;
public bool Write(Span<byte> s, ref ushort count)
{
bool success = true;
success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.id);
count += sizeof(int);
success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.level);
count += sizeof(short);
success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.duration);
count += sizeof(float);
return success;
}
public void Read(ReadOnlySpan<byte> s, ref ushort count)
{
this.id = BitConverter.ToInt32(s.Slice(count, s.Length - count));
count += sizeof(int);
this.level = BitConverter.ToInt16(s.Slice(count, s.Length - count));
count += sizeof(short);
this.duration = BitConverter.ToSingle(s.Slice(count, s.Length - count));
count += sizeof(float);
}
}
public List<SkillInfo> skills = new List<SkillInfo>();
public PlayerInfoReq()
{
this.packetId = (ushort)PacketID.PlayerInfoReq;
}
public override void Read(ArraySegment<byte> sagment)
{
ushort count = 0;
ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(sagment.Array, sagment.Offset, sagment.Count);
count += sizeof(ushort);
count += sizeof(ushort);
this.playerId = BitConverter.ToInt64(s.Slice(count, s.Length - count));
count += sizeof(long);
ushort nameLen = BitConverter.ToUInt16(s.Slice(count, s.Length - count));
count += sizeof(ushort);
this.name = Encoding.Unicode.GetString(s.Slice(count, nameLen));
count += nameLen;
ushort skillLen = BitConverter.ToUInt16(s.Slice(count, s.Length - count));
count += sizeof(ushort);
this.skills.Clear();
for(ushort i =0; i < skillLen; ++i)
{
SkillInfo info = new SkillInfo();
info.Read(s, ref count);
this.skills.Add(info);
}
}
public override ArraySegment<byte> Write()
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
ushort count = 0;
bool success = true;
Span<byte> sagment = new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count);
count += sizeof(ushort);
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), this.packetId);
count += sizeof(ushort);
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), this.playerId);
count += sizeof(long);
ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this.name, 0, this.name.Length, openSegment.Array, openSegment.Offset + count + sizeof(ushort));
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), nameLen);
count += sizeof(ushort);
count += nameLen;
success &= BitConverter.TryWriteBytes(sagment.Slice(count, sagment.Length - count), (ushort)skills.Count);
count += sizeof(ushort);
foreach(SkillInfo skill in skills)
success &= skill.Write(sagment, ref count);
success &= BitConverter.TryWriteBytes(sagment, count);
if (false == success)
return null;
return SendBufferHelper.Close(count);
}
}
public enum PacketID
{
PlayerInfoReq = 1,
PlayerInfoOk = 2,
}