728x90
반응형
이번에는 기존의 패킷에서 Session들에서 수행하던
Read와 Write를 패킷에서 수행할 수 있도록 변경해보겠습니다.
공통적으로 패킷의 구조를 다음과 같이 변경하였습니다.
차후에 패킷의 클래스를 따로 선언하여 클라이언트와 서버가 갖게끔 구성하도록 수정하겠습니다.
1) Server와 Client 모두가 공통적으로 갖는 패킷
// 패킷의 인자를 설정하는 경우, 최대한 용량을 줄여주는 것이 좋음
public abstract class Packet
{
// 패킷의 용량
public ushort _packetSize;
// 패킷의 아이디
public ushort _packetID;
// 패킷을 읽고, 패킷에 쓰기 위한 함수
public abstract void Read(ArraySegment<byte> segment);
public abstract ArraySegment<byte> Write();
}
// PlayerInfoRequire의 멤버는 _packetSize, _packetID, _playerId = 12 바이트
class PlayerInfoRequire : Packet
{
public long _playerID;
// 생성자에서 PlayerInfoRequire 패킷의 PacketID를 설정함
public PlayerInfoRequire()
{
this._packetID = (ushort)PacketID.PlayerInfoRequire;
}
// 패킷을 읽는(=역직렬화) 로직은 다음과 같이 동작함
// PlayerID의 경우, 읽기 전용으로 만들어 외부에서 패킷을 덮어씌우는 경우를 막음
public override void Read(ArraySegment<byte> segment)
{
ushort count = 0;
count += 2;
count += 2;
this._playerID = BitConverter.ToInt64(new ReadOnlySpan<byte>(segment.Array, segment.Offset + count, segment.Count - count));
count += 8;
}
// 패킷을 쓰는(=직렬화) 로직은 다음과 같이 동작함
// 기존의 방식 대신 this 포인터를 사용하여 프로퍼티로 설정함
public override ArraySegment<byte> Write()
{
ArraySegment<byte> segment = SendBufferHelper.Open(4096);
bool success = true;
ushort offset = 0;
success &= BitConverter.TryWriteBytes(new Span<byte>(segment.Array, segment.Offset, segment.Count), this._packetSize);
offset += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(segment.Array, segment.Offset + offset, segment.Count - offset), this._packetID);
offset += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(segment.Array, segment.Offset + offset, segment.Count - offset), this._playerID);
offset += 8;
success &= BitConverter.TryWriteBytes(new Span<byte>(segment.Array, segment.Offset, segment.Count), (ushort)offset);
if (success == false)
return null;
return SendBufferHelper.Close(offset);
}
}
public enum PacketID
{
PlayerInfoRequire = 1,
PlayerInfo = 2,
}
패킷의 구조를 위와 같이 수정하였습니다.
따라서 Session 별로 쓰기와 읽기를 수행하지 않고
패킷을 생성하여 생성된 패킷의 인스턴스에서 읽기와 쓰기를 수행할 수 있습니다.
바뀐 구조에 맞춰 Client와 Server 코드를 수정해보았습니다.
2) 클라이언트 - ServerSession
class ServerSession : Session
{
// 연결되는 경우 호출
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"[Connect from] : {endPoint}");
PlayerInfoRequire packet = new PlayerInfoRequire() { _playerID = 1001 };
// 서버로 전송
{
ArraySegment<byte> segment = packet.Write();
if (segment != null)
Send(segment);
}
}
.
.
.
}
새로운 PlayerInfoRequire 패킷의 인스턴스를 생성하고,
생성된 패킷 인스턴스로부터 Write 메서드를 수행하여 직렬화를 한뒤
Send를 통해 해당 패킷을 서버로 전송합니다.
3) 서버 - ClientSession
class ClientSession : PacketSession
{
.
.
.
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
ushort count = 0;
// buffer로 받은 패킷을 가져와 헤더를 가져오고
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
count += 2;
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 2;
// 패킷의 ID를 가져와서
switch ((PacketID)id)
{
// PlayerInfoRequire 패킷인 경우 다음의 로직을 수행함
case PacketID.PlayerInfoRequire:
{
PlayerInfoRequire playerInfoRequire = new PlayerInfoRequire();
playerInfoRequire.Read(buffer);
Console.WriteLine($"PlayerInfoReq : {playerInfoRequire._playerID}");
}
break;
}
Console.WriteLine($"RecvPacketId : {id}, Size : {size}");
}
}
PlayerInfoRequire 패킷에 대한 역직렬화를
PlayerInfoRequire의 메서드인 Read를 통해 수행하는 것을 볼 수 있습니다.
최종 수행 결과는 다음과 같습니다.
728x90
반응형
'C#' 카테고리의 다른 글
직렬화(Serialization) 3 (0) | 2025.01.06 |
---|---|
UTF-8과 UTF-16 (0) | 2025.01.06 |
직렬화(Serialization) 1 (1) | 2025.01.01 |
PacketSession (0) | 2024.12.31 |
RecvBuffer와 SendBuffer (0) | 2024.12.30 |