패킷작업하기
서버, 클라이언트에 이 정보가 다 있어야 한다.
class Packet
{
public short size;
public short packetId;
}
class PlayerInfoReq : Packet
{
public long playerId;
}
class PlayerInfoOk : Packet
{
public int hp;
public int attack;
}
public enum PacketID
{
PlayerInfoReq = 1,
PlayerInfoOk = 2,
}
보내는거 설정
01 플레이어 정보 요청을 나타내는 패킷, PlayerInfoReq클래스의 인스턴스 생성
PlayerInfoReq는 클라이언트로부터 플레이어 정보를 요청하는 클래스이다.
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq };
02 packet객체를 바이트 배열로 변환화여 네트워크로 전송할 준비
ArraySegment<byte> openSegment = SendBufferHandler.Open(4096);
byte[] size = BitConverter.GetBytes(packet.size);
byte[] packetId = BitConverter.GetBytes(packet.packetId);
Array.Copy(size, 0, openSegment.Array, openSegment.Offset, size.Length);
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + size.Length, packetId.Length);
ArraySegment<byte> sendBuff = SendBufferHandler.Close(packet.size);
SendBufferHandler.Close
패킷의 크기를 전달받아 적절한 크시로 송신 버퍼를 잘라낸다.
sendBuff
이 byte배열은 네트워크를 통해 클라이언트로 전송되고 수신한 byte배열을 역직렬화하여 패킷을 복원하고 패킷의 내용을 파싱하여 서버 요청에 응답한다.
03 playerId 추가
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
byte[] playerId = BitConverter.GetBytes(packet.playerId);
Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset + size.Length, playerId.Length);
자동화를위해서 어떤 작업이 필요할까?
위의 작업들에서는 데이터를 원하는 위치로 직접 복사한다. 이는 수동으로 주소를 계산하고 복사한다. 이를 직접 계산하지 않고 자동으로 위치를 계산하는 방식을 사용해보자
04 count 변수를 사용하여 위치를 자동으로 계산
ushort count = 0;
Array.Copy(size, 0, openSegment.Array, openSegment.Offset + count, 2);
count += 2;
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + count, 2);
count += 2;
Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset + count, 8);
count += 8;
ArraySegment<byte> sendBuff = SendBufferHandler.Close(count);
05 bytes[]을 TryWirteBytes로 수정
byte[]를 만들면서 보내는거는 효율성이 떨어진다.
왜냐하면 데이터를 생성한 후 해당 데이터를 새로운 byte배열로 복사한다. 이 경우 크기가 큰 데이터를 복사할 때 메모리에 새로운 배열을 할당하고 원래 데이터를 복사하는 데 많은 시간과 메모리가 소모된다.
byte[] size = BitConverter.GetBytes(packet.size); // 2
byte[] packetId = BitConverter.GetBytes(packet.packetId); // 2
byte[] playerId = BitConverter.GetBytes(packet.playerId); // 8
06 TryWriteBytes를 사용하는 것으로 고쳐보자
TryWriteBytes알아보러 가기
size, packetId는 ushort이라 2씩 더해주고 playerId는 long이라 8을 더해준다.
bool success = true;
ushort count = 0;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count), packet.size);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), packet.packetId);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count-count), packet.playerId);
count += 8;
07 최종데이터 크기
아래 코드를 확인해보면 최종적으로 쓰이는 데이터의 크기가 size가 아니라 count변수에 의해 결정된다는 것을 확인할 수 있다. size는 ushort이라 2byte를 최종크기라고 생각했는데 count변수는 2 + 2 + 8 = 12가 최종크기가 된다. 그러므로 최종적으로 쓰이는 데이터의 크기는 12byte가 된다.
따라서 마지막으로 count값을 전테 데이터 크기로 설정해준다.
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count - count), packet.packetId);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset + count, openSegment.Count-count), packet.playerId);
count += 8;
success &= BitConverter.TryWriteBytes(new Span<byte>(openSegment.Array, openSegment.Offset, openSegment.Count),count);
08 c#에서 포인터 사용하는 법
설명 참조
static unsafe void ToBytes(byte[] array, int offset, ulong value)
{
fixed (byte* ptr = &array[offset])
*(ulong*)ptr = value;
}
08 success가 true인 경우에만 Send하기
if (success)
{
Send(sendBuff);
}
받는거 설정
buffer배열에서 데이터를 역직렬화하여 packId와 해당 packet에 필요한 데이터를 추출
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.PlayerInfoReq:
{
long playerid = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 8;
Console.WriteLine($"PlayerInfoReq: {playerid}");
}
break;
}
결과 확인
참고 : 본 내용은 MMORPG PART4 강의를 수강하여 작성하였습니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
'cs공부 > 네트워크프로그래밍' 카테고리의 다른 글
네트워크프로그래밍 - 패킷 자동화 처리 객체지향 방법으로 수행하도록 하기 (0) | 2023.08.07 |
---|---|
네트워크프로그래밍 - 직렬화(Serialization), 역직렬화(Deserialization), Session, 패킷 작업 정의 (0) | 2023.08.03 |
네트워크 프로그래밍 - PacketSession (0) | 2023.07.31 |
네트워크프로그래밍 - SendBuffer 개선하기 (0) | 2023.07.22 |
네트워크프로그래밍 - RecvBuffer 개선하기 (0) | 2023.07.19 |
댓글