Server programming

03_02_패킷 직렬화_Serialization #2_자동화

devRiripong 2023. 4. 11.
반응형

확인 테스트

Q1. 어떻게 자동화를 하면 좋을까요?

-> 쓰는 부분과 읽는 부분을 함수로 빼서 Packet클래스를 상속받은 PlayerInfoReq클래스에 정의해 사용합니다.

Q2. Write와 Read의 다른 점은 무엇인가요?

-> 전부 다 Read에서 뽑는게 아니라 size와 id는 OnRecvPacket에서 뽑고 playerId만 Read에서 뽑는다.

Q3. Read에서 size와 id를 추출할 필요가 없는 이유는 무엇인가요?

-> size와 id는 OnRecvPacket에서 뽑고 Read를 호출하기 때문이다. Read가 같은 buffer를 넘겨 받고 같은 프로토콜을 사용하고 있기 때문이다.

Q4. Read에서 무엇을 하는가?

->PlayerInfoReq의 멤버 변수 playerId의 값을 buffer로 부터 받은 값으로 채워 준다.

Q5. ServerSession의 OnConnected에서 packet을 만들 때 packetId를 넣을 필요 없는 이유는?

-> 생성자에서 넣어주기 때문이다

Q6.ServerSession의 OnConnected에서가 아니라 그냥 인게임 코드에서 패킷을 보낼 때는 어떻게 하면 될까?

-> PlayerInfoReq의 packet을 하나 생성해 주고, Write로 배열을 만들어 ArraySegment<byte>에 담은 다음에 Send하면 된다.

Q7. Read를 ClientSession의 OnRecvPacket에서 사용하려면 어떻게 해야 하나?

-> PlayerInfoReq 객체를 생성한 다음에 p.Read(buffer)를 해주면 된다.

Q8. 받은 값을 출력할 때는 어떻게 해야 하나요?

-> 객체에 값을 넣었기 때문에 객체가 p라면 p.playerId로 사용해야 한다.

Q9. Read는 왜 해킹에 문제가 될 수 있는가?

-> 클라이언트로 부터 받은 패킷을 까서 나오는 size라는 값이 실제랑 같은지 판별하기 어렵기 때문. Write의 경우는 그냥 Open해서 버퍼 열고 닫을 때는 받은 값의 양만큼 할당해서 닫기 때문에 딱히 받은 값에 영향을 받지 않는다.

Q10. Read는 어떻게 수정해야 안전하게 될 수 있을까?

-> BitConverter.ToInt64를 할 때 인자로 new ReadOnlySpan을 넣어서 범위가 넘어가는 걸 파싱하려고 하면 에러가 나게 하면 된다.

Q11. 직렬화 자동화 작업을 위해 한 일을 나열해 보세요.

 

-> ServerSession의 class Packet에abstract로 Write, Read 함수를 추가한다. Read는 ArraySegment<byte> s를 매개 변수로 받는다. 에러가 나니 calss Packet도 public abstract클래스로 만든다. PlayerInfoOk는 삭제한다. class PlayerInfoReq 에러 표시 부분에서 abastract클래스 구현을 누른다.

 

->Write부분은 ServerSession의 OnConected에서 보낸다 부분의 Send를 빼고 Write에 붙여 넣는다. SendBufferHelper.Close(count);에 return을 붙여 함수를 호출해 return 값을 사용할 수 있게 한다.

Write함수에서 packet.PacketId를 this.PacketId로 바꿔준다.

 

->SeverSession의 OnConnected의 PlayerInfoReq packet = new PlayerInfoReq() { packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 }; 여기에서 packetId에 값을 넣어주는 부분을 삭제하고 public PlayerInfoReq() { this.packetId = (ushort)PacketID.PlayerInfoReq; } 생성자를 만든다.

 

-> PlayerInfoReq클래스의 Write함수에서 success가 false면 Close하기 전에 return null을 해준다.

 

-> ClientSession의 OnRecvPacket의 playerId 변수를 선언하고 값을 파싱해 받고, count를 8 증가해주는 코드를 PlayerInfoReq의 Read로 복붙한다. 그리고 count 변수와 size와 id를 뽑아내는 부분도 복붙한다. Read가 받는 ArraySegment<byte> 매개변수명이 s이므로 buffer를 s로 바꿔준다. Read의 size와 id를 뽑는 부분은 주석처리 해준다. Read의 long playerId를 this.playerId로 바꾼다.

 

-> ServerSession의 OnConnected의 PlayerInfoReq packet = new PlayerInfoReq() { packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 }; 에서 packetId 부분은 삭제한다. 그리고 그 아래 Open부터, serialize하고 Close하는 부분을 ArraySegment<byte> s = packet.Write();로 교체해주고 s를 null 체크 후 send에 s를 넣는다.

 

-> ClientSession의 OnRecievePacket에서 case분기 안의 playerId를 얻는 부분을 BitConverter로 할 필요 없이, PlayerInfoReq p = new PlayerInfoReq(); p.Read(buffer); 이렇게 Read를 사용하는 것으로 수정해 준다.

 

-> DummyClient의 ServerSession에서 작업해준 Packet부분을 Server의 ClientSession에 복붙해줘서 error를 해결한다.

 

-> Server의 ClientSession의 OnRecvPacket에서 playerId값을 출력할 때 p.을 붙여 p.playerId로 사용한다.

Write에서 size를 밀어 넣을 떄 잘못된 값인 (ushort)4를 밀어 넣으면 어떻게 되는지 실험해 본다.

-> ServerSession과 ClienteSession 둘 다 Read에서 BitConverter.ToInt64를 할 때 new ReadOnlySpan을 사용하는 버전으로 수정한다. Write에서 size를 밀어 넣을 떄 잘못된 값인 (ushort)4를 밀어 넣으면 어떻게 되는지 실험해 본다.

 

 

 

 

지난 시간 까지 Serialization 기초에 대해서 얘기를 했었어.

PlayerInfoReq packet을 만들어서 buffer에 밀어 넣는 작업을 해봤고, 직렬화 라고 했어.

그리고 Send를 한 다음에 반대 쪽에서도 역직렬화라고 해가지고 buffer에 있는 것을 꺼내서 쓰는 것 까지 해봤다.

 

오늘도 이 부분에 대해 고민을 더 해볼거야.

 

앞서 말한대로 하드코딩을 하는게 아니라 자동화를 해야 한다고 했어.

자동화 하기 앞서서 계속 테스트 하면서 어떤 인터페이스로 얘를 제공할지를 고민을 해야한다. 이거는 한번에 결정되는 것이 아니라 계속 이리저리 왔다갔다 하면서 고민을 하면서 조금씩 수정이 되는 경우도 있을 거야.

 

ServerSession OnConnected의직렬화 하는 부분을 자동화 해서 편리하게 사용하기 위해서는 패킷을 만드는 부분은 동일하게 있다고 쳐도 밀어 넣어주는 부분들이 따로 빠져서 함수형태로 호출하하면 좋을 거 같다는 생각이 든다. 오늘 그 작업을 해볼거야.

 

여러가지 방법이 있겠지만 일단은 최상위 클래스가 Packet에다 몇 개를 만들어 보도록 하자.

인터페이스를 어떻게 맞춰 줄거냐, 일단 OnConnected에서 처럼 뭔가를 쓰는 인터페이스(밀어넣는)가 있을거고, 반대로 빼내주는 부분이 있을 거니까 이름은 간단하게 Wirte, Read라고 하자. Serialize, Deserialize도 나쁘지 않다.

**public abstract** class Packet
    {
        public ushort size;
        public ushort packetId;

				public **abstract** ArraySegment<byte> Write(); // 밀어 넣는 부분 Serialize
        public **abstract** void Read(ArraySegment<byte> s); // 빼내 주는 부분 DeSerialize
        
    }

이렇게 public abstract class로 바꿔주면 다른 부분에서 에러가 난다. 나머지 애들도 고쳐줘야 하니까.

일단 PlayerInfoOk는 삭제 하고 PlayerInfoReq로만 작업을 해볼거야.

자동화 하면 클래스 몇 개든 중요하지 않기 떄문에 일단 하나로만 작업을 해볼거야.

PlayerInfoReq의 error 표시 부분에서 추상클래스 구현을 누르면 abastract클래스를 구현하는 부분이 생성이 된다. 여기다가 뭔가를 넣어줄 것이야 .

class PlayerInfoReq : Packet    // 클라에서 서버로 플레이어 정보를 알고 싶어
    {
        public long playerId;

        public override void Read(ArraySegment<byte> s)
        {
            throw new NotImplementedException();
        }

        public override ArraySegment<byte> Write()
        {
            throw new NotImplementedException();
        }
    }

 

Write부분은 ServerSession의 OnConnected에서 보낸다 부분의 Send를 빼고 넣으면 될 거 같아.

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);

        ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
        bool success = true;

        // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
        //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
        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);
    }
}

Send 시점 일아서 선택하게 하고 윗 부분은 복사해 Write에 붙여 넣는다.

 

public override ArraySegment<byte> Write()
{
    ArraySegment<byte> s = SendBufferHelper.Open(4096);

    ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
    bool success = true;

    // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
    //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
    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);

    **return** SendBufferHelper.Close(count);
}

그리고 Close의 결과값을 return을 해줘서 밖에서 얘를 쓰라고 하면 된다.

 

그 다음에 에러 나는 부분을 하나씩 고쳐 보자면

Packet을 밖에 꺼내 쓰는게 아니라 이제 얘 자체가 Packet class 이니까(Write는 Packet을 상속한 PlayerInfoReq의 메서드) packet을 this.로 자신의 변수를 참조하라고 바꿔준다.

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;

 

그리고 ServerSession의 OnConnnected에서 PacketID를

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

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

이렇게 직접 넣어주고 있었는데 이렇게 넣어 주는게 불필요할 거 같아.

packetId 는 PlayerInfoReq라는 클래스에 종속적인 아이니까 여러가지 방법이 있겠지만

PacketId를 PlayerInfoReq생성자를 만들어서 설정해준다.

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

 

class Packet의 public ushort size; 같은 경우도 애매하다 굳이 들고 있어야 할지 아닐지도 고민이 되긴 하는데 조금 이따가 고민해 보도록 하고,

public override ArraySegment<byte> Write()
{
    ArraySegment<byte> s = SendBufferHelper.Open(4096);

    ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
    bool success = true;

    // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
    //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
    count += 2;
    success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), **this.packetId**);
// 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);
}

이렇게 success가 false면 보내지 않는 거였는데 null을 return 해준다.

null로 뱉어 주면 Write의 return 값 **ArraySegment<byte>**가 null로 세팅이 되어서 구분을 할 수 있게 된다.

ArraySegment를 f12로 보면 operator ==오버로딩 했었어. 결국에는 null 체크를 어떤 식으로든 해줄 것이라는 것을 알 수 있어. 이렇게 하면 성공 여부를 boolean 변수로 따로 받아주지 않더라도 그냥 ArraySegment의 결과값만 보고 판별을 할 수 있을거야.

 

Read도 맞춰줘야 한다.

Read는 반대쪽인 ClientSesson 쪽에 있는데

얘같은 경우에는 조금 다른게 뭐냐

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

// **일단 여기서 파싱을 해가지고**
    ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
    count += 2; 
// **프로토콜 ID를 먼저 뽑아낸 다음에** 
    ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
    count += 2; 

// **그거에 따라 가지고 구분을 해**
    switch((PacketID)id)
    {
        case PacketID.PlayerInfoReq:
            {
// 이런 식으로 여기서 playerId를 만들어줘야 한다. 
// **일단 여기 부분은 필수적으로 들어가야 한다. 이부분을 복사해서 SeverSession의 Read로 옮긴다.**
                **long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
                count += 8;**
                Console.WriteLine($"PlayerInfoReq: {playerId}"); 
            }
            break;
    }            
    Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
public override void Read(ArraySegment<byte> s)
        {
            long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
            count += 8;        
        }

이렇게 옮기고 시작을 한다.

그리고 ClientSession의 OnRecvPacket의 나머지 size와 id를 뽑아내는 이 부분도 필요할까 싶지만 일단은 옮겨보자.

class **PlayerInfoReq** : Packet    // 클라에서 서버로: 나는 플레이어 정보를 알고 싶어. 그 정보는 playerId라는 플레이어야.
    {
        public long playerId;

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

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

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

            long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
            count += 8;        
        }

 

설령 size, id, playerId 얘네가 Read안에 있다 하더라도 PlayerInfoReq 패킷이 왔다는 걸 알기 위해서는 ClientSession의 OnRecvPacket에서 한번은 id를 까봐야 한다.

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;

id를 판별하는 건 공통적으로 들어갈 거니까. 사실은 Read에서 size랑 id를 뽑아 내는게 의미가 있을까 싶긴 하다.

일단 Read에서 이름을 에러 안나게 buffer를 s로 바꿔주도록 하자.

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;**

    long playerId = BitConverter.ToInt64(s.Array, s.Offset + count);
    count += 8;        
}

 

id는 굳이 추출할 필요 없다. 여기까지 Read로 들어 왔다는 건 ClientSession의 OnRecvPacket의

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.PlayerInfoReq:
                    {
                        //long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
                        //count += 8; // 나중에 이어서 파싱을 해야 할 경우를 대비해서 맞춰준다.
                        **PlayerInfoReq p = new PlayerInfoReq();**  
                        **p.Read(buffer);  // 나중에 넣어준 거지만 헷갈려서 지금 써 놨다.**
                        Console.WriteLine($"PlayerInfoReq: {p.playerId}");
                    }
                    break;
            }

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

여기 까지 들어왔다는 얘기인 거니까 현재 이 ArraySegment나의 프로토콜을 사용하고 있다. 내 자신이다 라는 걸 알 수 있으니까 id는 굳이 추출할 필요 없을 거 같고, size도 필요할지 의문이니 주석처리를 해준다 . 나중에 조금 더 살펴 보도록 하고,

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

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

				    this.playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
            count += 8;        
        }

 

기존에는 Server의 ClientSession에서 playerId를 빼서

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.PlayerInfoReq:
                    {
                        long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
                        count += 8;
                        **Console.WriteLine($"PlayerInfoReq: {playerId}");** 
                    }
                    break;
            }            
            Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
        }

이렇게 로그만 찍고 있었지만

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(s.Array, s.Offset + count);
            count += 8;
        }

이 패킷 클래스 자체의 instancebuffer에 있는 정보로 채워주는 그런 부분이 된다.

이렇게 까지 했으면 대부분의 기능들이 완성이 된 거 같아.

 

그냥 지난번에 했던 거를 클래스 안에다 넣어준거다.

 

이것만 해도 의미가 있는 건 이제 기존에 사용하던 OnConnected 부분을 더 깔끔하게 수정 할 수 있게 된다.

 

일단 ServerSession의 OnConnected의

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

여기에서 packetId의 경우는 여기서 넣어줄 필요가 없으니까 일단 삭제한다.

 

그리고,

class ServerSession : Session
    {
        public override void OnConnected(EndPoint endPoint)
        {
            Console.WriteLine($"OnConnected : {endPoint}");
// packetId도 여기서 넣어줄 필요 없으니까 삭제해도 될 거 같고, 
            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;

                // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
                //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
                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);
            }
        }

이 부분이 훨씬 쉽게 바뀐다.

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();** 
// 여기다가 내부적으로 SendBuffer에다가 buffer를 하나 뽑아와서 써주는 작업까지 한 다음에 
// ArraySegment<byte>로 뱉어줄건데 얘가 null이 아니라고 한다면
                if (s != null)
                    **Send**(s); // Send로 보내준다.
            }
        }

이렇게 수정하면 된다.

 

실제 패킷을 주고 받을 때는 꼭 여기 OnConnected가 아니라 하더라도 그냥 인게임의 코드에서도 패킷을 주고 받는 일이 생길텐데 그럴 때는

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

이런식으로 packet 클래스를 하나 만들어준 다음에

**ArraySegment<byte> s = packet.Write();** 

걔 Write를 해주고 serialize를 해줘서 바이트 배열로 만들어 준 다음에

            **Send**(s);

이렇게 보내주면 된다는 얘기다.

실제로 이 Write안에서 해주는 건 당장 컨텐츠를 개발하는 사람은 몰라도 되지만 실제로는 Write() 코드에서 보는 것 처럼 한땀 한땀 밀어넣고 있었다는 얘기가 된다.

 

반대로 ClientSession의 OnRecievePacket에서 반대 쪽에서도 기존과 마찬가지로 복잡하게 뭔가를 찾아서 할 필요가 없이 이제는

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.PlayerInfoReq:
            {
                **long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
                count += 8;**
                Console.WriteLine($"PlayerInfoReq: {playerId}"); 
            }
            break;
    }            
    Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}

이런식으로 하는게 아니라

 

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.PlayerInfoReq:
                    {
// PlayerInfoReq를 만들어 준 다음에 
                        **PlayerInfoReq p = new PlayerInfoReq();
// Read를 통해서 buffer를 넘겨주면 된다. 
                        p.Read(buffer);** 
                        Console.WriteLine($"PlayerInfoReq: {playerId}"); 
                    }
                    break;
            }            
            Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
        }

이렇게 PlayerInfoReq를 만들어 준 다음에 Read를 사용하게 수정해준다.

 

근데 이렇게 하면 에러가 뜨는데 Client와 ServerSession만 작업을 했어서 ClientSession의 Packet 부분이 안맞기 때문이야. DummyClient의 ServerSession에서 작업해준 Packet부분을 Server의 ClientSession복붙해주면 된다. 언젠가는 Packet 클래스를 공용으로 사용할 수 있는 라이브러리든 어딘가에 밀어 넣어야겠다는 생각이든다.

		public abstract class Packet
    {
        public ushort size;
        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)
        {
            throw new NotImplementedException();
        }

        public override ArraySegment<byte> Write()
        {
            ArraySegment<byte> s = SendBufferHelper.Open(4096);

            ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
            bool success = true;

            // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
            //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
            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);

            return SendBufferHelper.Close(count);
        }
    }

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

이렇게 수정을 하고 다시 보면

 

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.PlayerInfoReq:
                    {
// 여기서 객체를 만들어준 다음에 
                        **PlayerInfoReq** p = new PlayerInfoReq();
                        **p.Read(buffer);** 
                        Console.WriteLine($"PlayerInfoReq: {**p.**playerId}"); 
                    }
                    break;
            }            
            Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
        }

객체를 만들어 준 다음에

여기서 Read를 해주면 Deserialize라고 역직렬화 해서 buffer에 있는 값을 추출을 해서 PlayerInfoReq에다가 빼내 줄 것이다.

Read코드를 보면 PlayerInfoReq클래스의 playerI에 값을 역직렬화 해 넣고 있으므로

p.playerId로 사용한다.

 

일렇게 첫번째 수정을 한 것이고,

여기까지 잘 되는지 실행을 해보자.

 

잘 나온다. 그냥 매핑만 한거니까

 

packetId를 굳이 Packet에 넣어줘야 하는지가 고민이야.

public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

size, packetId는 애당초 직접 사용할 일이 거의 없어.

지금도 Write를 할 떄

public override ArraySegment<byte> Write()
{
    ArraySegment<byte> s = SendBufferHelper.Open(4096);

    ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
    bool success = true;

    // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
    //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
    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);

    return SendBufferHelper.Close(count);
}

packetId를 밀어 넣어 줄 때만 사용하고 있어.

굳이 packetId라는 변수를 사용하지 않고,

public override ArraySegment<byte> Write()
{
    ArraySegment<byte> s = SendBufferHelper.Open(4096);

    ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
    bool success = true;

    // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
    //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
    count += 2;
    success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), **(ushort)PacketID.PlayerInfoReq**); 
    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);
}

이런 식으로 넣어줘도 된다. packetId라는 존재 자체가 딜레마이긴 하고,

size 같은 경우에도 거의 쓸 일이 없다. 네트워크 단에서 패킷 세션이라는 애가 패킷을 조립할 때 참고하는 정보였지 패킷 자체에서 이용하는 건 없다. 경우에 따라서 size, packeId를 제거해도 상관 없다. 일단은 냅두도록 하자.

 

Write할 때 Open 부분은 버퍼를 열어서 할당을 한 다음에 거기다가 정보를 기입하고 있었어. 그리고 버퍼를 닫는 부분까지 작업을 하고 있었는데

Write하는 부분은 우리가 온전히 컨트롤 하는 부분이라 별다른 문제가 없다.

상대방이 악의적인 코드로 해킹을 한다는게 없는게 애당초 우리가 다 컨트롤 하고 있는 영역이라 아무런 문제가 없는데

근데 Read는 문제가 될 수 있다.

애당초 Read를 하는 부분이 어디인지 다시 살펴 보면

 

ClientSession에서

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.PlayerInfoReq:
                    {
                        PlayerInfoReq p = new PlayerInfoReq();
                        p.Read(buffer); 
                        Console.WriteLine($"PlayerInfoReq: {p.playerId}"); 
                    }
                    break;**
            }            
            Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
        }

클라이언트 쪽에서 서버 쪽으로 뭔가 패킷을 보냈는데

걔를 패킷을 까보면서 작업을 하고 있어. Read라는 애도 결국에는 버퍼를 찝어서 작업을 하긴 하는데 size라는 인자로 넘겨 애가 실제 패킷이랑 일치하는지 아닌지는 판별하기가 굉장히 어렵다는 문제가 있다.

왜 이런 일이 발생하느냐면

서버를 만들 때는 항상 클라이언트 쪽에서 거짓말을 하고 있다고 가정을 하고 안전하게 만들어야 한다는게 중요하다고 했어.

테스트를 해보자.

 

ServerSession에서 Write를 하면 12바이트를 보내고 있었어.

public override ArraySegment<byte> Write()
{
    ArraySegment<byte> s = SendBufferHelper.Open(4096);

    ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
    bool success = true;

    // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
    //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
    count += 2;
    success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), (ushort)PacketID.PlayerInfoReq); 
    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);
}

근데 마지막 count가 12여서 다 쓴 다음에 첫 offset에다가 몇 바이트를 썼는지를 count에 채워주고 있었는데

만약에

    success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), **(ushort)4**);

(ushort)4로 강제로 바꿔서 보내 버릴거야. 그럼 어떻게 될지 궁금해

실제로 버퍼 Close를 할 때는 12바이트였지만 실제 패킷 해더에는 4바이트 짜리 였다고 거짓말을 한 셈이 된다.

과연 어떻게 반응할까?

실행을 해보면

마찬가지로 1001이라는 값이 제대로 갔다. 다만 Size만 4라는 값으로 거짓말 한 값으로 출력을 해주고 있다.

애당초 패킷 헤더에 있는 정보는 믿을 수 없고 그냥 참고만 해야 한다는 거.

 

이런 식으로 패킷 조작을 해서 잘못된 정보를 보내도 코드는 다 감지를 해서 체크를 해야 된다는 얘기가 된다.

근데 이상해 4바이트를 보내 주면 애당초 패킷 조립하는 부분에서

ClientSession의 Read에서 ArraySegment에서 4 바이트만 찝어 줄 거였을텐데 이상하다.

// [][][][][][][][][][][][]
// 이렇게 12바이트가 정상적으로 와야 되는데 처음에 패킷 헤더에 4라고 거짓말을 한거, 
// 그럼 실제로 패킷을 패킷 세션에서 조립을 할 때 
// [][][][]만 유효 범위라고 arraySegment에다가 딱 찝은 상태로 Read를 호출을 해줄 것이다. 
// 그렇다고 해서 뒷 부분이 메모리 상에 없는 그런게 아니라. 단지 유효 범위를 찝어 놨을 뿐이지. 
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(s.Array, s.Offset + count);
            count += 8;
        }

Read에서 4바이트만 유효한 범위라고 찝어줄 것인데 그렇다고 해서 뒷부분이 메모리상에 없는 건 아냐. 그래서 이런 문제가 발생한다. 비록 사이트는 4 바이트라고 거짓말 했지만 실제로 데이터는 들어있기 때문에 사이즈를 넘는지 안넘는지 체크를 안하고, 이런 식으로 BitConverter강제로 뒤에 있는 값을 추출을 하면 정상적으로 추출이 된다. 약간 문제가 있는 셈이다.

 

이걸 어떻게 고칠지 고민을 하면

Read에서 BitConverter.ToInt64를 무조건 100프로 확률로 하는게 아니라 충분한 공간이 있는지 계속 체크를 해야 한다.

BitConvewrter.ToInt64()를 보면 버전이 여러개 있어. 2번쨰 버전을 사용하고 있었는데

1번을 사용해 본다면

Span이란 걸 써본적 있었어. ArraySegment와 마찬가지로 범위를 찝어주는 역할을 하고 있었어.

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; 
}

이렇게 범위를 찝어주는 형태로 바꾼다면 해결이 될 거 같아. 마지막 인자로 이제 length를 입력해 주는 것을 볼 수 있다.

TryWriteBytes와 마찬가지로 범위를 집어주면서 추가로 확실하게 몇 바이트 짜리였는지를 입력해준 거니까 BitConverter.ToInt64를 할 때 범위가 넘어가는 걸 파싱하려고 하면 자동으로 에러가 나면서 exception이 발생한다. exception을 catch를 해서 처리를 하면 된다.

ClientSession 쪽의 Read도 수정해준다.

success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), (ushort)4);

이제는 ServerSession의 Write에서 (ushort)4로 거짓말을 한 상태에서 하면 어떻게 될지 궁금하니까 ClientSession Read에 break point를 잡은 상태로 실행해 보자.

이렇게 영역을 찝어 주고 있다.

그리고 ReadOnlySpan을 이용해 그 영역을 그대로 찝어줬다. 이미 count가 4가 됐기 때문에 s.Count-count 값이 0이 됐다. 그 다음에 어떻게 될지 실행해 보면

exception으로 간다.

argument out of range로 뜬다. 영역에 벗어나서 에러가 났다는 걸 알 수 있다.

여기까지 오면 의심할 수 있다. 어떤 부분에서 에러가 났는지 보고 패킷 조작이 의심이 된다면 바로 Disconnect를 하는 방식을 채택을 하면 된다.

패킷 설계시 짜잘짜잘한 부분까지 조심해서 설계를 해야 한다. 거짓말 걸러내지 못하면 문제된다.

그럼에도 클라이언트를 통해 size를 받는 건 완전히 믿을 수는 없지만 절반 정도는 신용을 하고 진행을 하는 거. 맞다고 가정하고 조립을 하되 만약 recieve에서 문제가 생기면 뭔가 이상하구나 라는 걸 판별하는 방식.

 

이렇게 해서 지난 시간에 만들어 본 Serialize랑 DeSerialize를 개선을 해서 Packet클래스 안에다 배치를 시키는 작업을 해봤다.

 

자동화에 한걸음 다가갔다. 결국 나중에 Read, Write를 자동화해서 처리하면 된다. 아직은 부족해.

다음 시간에는 고정적 크기가 아닌 스트링이나 byte배열이나 list같은 가변적 길이 갖는 애들 어떻게 처리할지 보자.

 

작업한 코드

ServerSession

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ServerCore;

namespace DummyClient
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        public abstract ArraySegment<byte> Write(); // 밀어 넣는 부분 Serialize
        public abstract void Read(ArraySegment<byte> s); // 빼내 주는 부분 DeSerialize
    }

    class PlayerInfoReq : Packet    // 클라에서 서버로: 나는 플레이어 정보를 알고 싶어. 그 정보는 playerId라는 플레이어야.
    {
        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(s.Array, s.Offset + count);
            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;

            // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
            //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
            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), (ushort)4);

            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);
            }
        }

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

        public override int OnRecv(ArraySegment<byte> buffer)
        {
            string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
            Console.WriteLine($"[From Server] {recvData}");
            return buffer.Count;
        }

        public override void OnSend(int numOfBytes)
        {
            Console.WriteLine($"Transferred bytes: {numOfBytes}");
        }
    }
}

ClientSession

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;
using System.Net;

namespace Server
{
    public abstract class Packet
    {
        public ushort size;
        public ushort packetId;

        public abstract ArraySegment<byte> Write(); // 밀어 넣는 부분 Serialize
        public abstract void Read(ArraySegment<byte> s); // 빼내 주는 부분 DeSerialize
    }

    class PlayerInfoReq : Packet    // 클라에서 서버로: 나는 플레이어 정보를 알고 싶어. 그 정보는 playerId라는 플레이어야.
    {
        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(s.Array, s.Offset + count);
            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;

            // 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
            //success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
            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 Packet
    //{
    //    public ushort size;
    //    public ushort packetId;
    //}

    //class PlayerInfoReq : Packet    // 클라에서 서버로: 나는 플레이어 정보를 알고 싶어. 그 정보는 playerId라는 플레이어야.
    //{
    //    public long playerId;
    //}

    //class PlayerInfoOk : Packet     // 서버에서 클라로: 답변을 주는 거. 
    //{
    //    public int hp;
    //    public int attack;    // hp와 attack을 반환한다고 가정을 해보자. 간단하게 상황을 만들어 준 거. 
    //}

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

            //Packet packet = new Packet() { size = 100, packetId = 10 };

            //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(buffer.Length + buffer2.Length);

            //Send(sendBuff);
            Thread.Sleep(5000);
            Disconnect();
        }
        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.PlayerInfoReq:
                    {
                        //long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
                        //count += 8; // 나중에 이어서 파싱을 해야 할 경우를 대비해서 맞춰준다.
                        PlayerInfoReq p = new PlayerInfoReq();
                        p.Read(buffer);
                        Console.WriteLine($"PlayerInfoReq: {p.playerId}");
                    }
                    break;
            }

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

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

        public override void OnSend(int numOfBytes)
        {
            Console.WriteLine($"Transferred bytes: {numOfBytes}");
        }
    }

}

 

반응형

댓글