공부/인프런 - Rookiss

Part 4-4-2. 패킷 직렬화 : Serialization #2

셩잇님 2023. 12. 4. 15:21
반응형

 

 

패킷 직렬화

 

 지난 시간에는 직렬화, 역직렬화의 개념과 이를 예시를 통해 이해하고 학습하였다. 물론 아직 개선이 필요한 부분은 많이 있지만.. 🤔

 


 

📌 직렬화 #2

 

Send 개선

 지난 시간에 열심히 뜯어고친 OnConneccted 메서드 내부에서 buffer를 Send 하는 부분을 더욱 편리하게 사용하기 위해 이를 PlayerInfoReq 클래스 내부에 함수로 만들어 줄 것이다.

 

출처 : velog @livelyjuseok

 

 위 스크립트에서 빨간 줄로 그어진 부분을 개선할 것이다. 😋

 

 먼저 사용하지 않는 PlayerInfoOk 클래스는 지워주고, Server 쪽에서 패킷을 읽는 부분(=역직렬화)도 매번 재사용 하기 위한 Read 메서드와, 패킷을 보내기 전 직렬화 하는 과정인 Write 메서드를 작성한다.

 

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

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

 

 먼저 최상위 부모 클래스인 Packet을 abstract로 바꿔주고, 위에서 말한 Write와 Read 메서드 또한 abstract로 구현하여 Packet을 상속받는 자식들은 이를 override하여 사용하도록 한다.

 

    class PlayerInfoReq : Packet
    {
        public long playerId;

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

        public override ArraySegment<byte> Write()
        {
        
		...
        
		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);
        
		...
        
		return SendBufferHelper.Close(count);

        }
    }

 

 해당 메서드를 다시 작성할 필요는 없고, 기존 OnConnected에서 작동하던 코드들을 Read, Write 메서드에 이동시킨다. 다만 packetId를 구분하기 위해서 this 키워드를 이용해 사용하여 준다. 마지막으로 return 값 자체가 ArraySegment이기 때문에 Close를 해줄 때에도 변수 없이 바로 닫아서 사용하면 된다. 

 

마지막으로 기존 OnConnected에서 직접 넣어준 packetId도 여기서 넣어준다.

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

 


 

Recv 개선

 

Send를 통해 받은 데이터를 Recv 부분에 가져와 작성하여 개선하면 된다.

 

출처 : velog @livelyjuseok

 

 빨간색 선으로 그린 부분을 PlayerInforeq에 Read 메서드의 넣어준다. 다만, OnRecvPacket의 size와 packId는 Read를 하기 전 미리 추출해야 하기 때문에 Read에서는 해줄 필요가 없다. 또한 packetId 또한 switch문에서 따로 처리하고 있다.

 

Recv 예외처리

 

😠 패킷 사이즈 조작

클라이언트에서 패킷을 전송할 때에는 항상 잘못된 정보가 올 수 있다고 생각한다.

 

 

 위 이미지는 클라리언트에서 패킷을 전송 처리 하는 부분이다. 기존에는 count 변수를 통해 정상적이라면 12라는 변수가 전송되어야 하지만, 임의로 4byte를 보내어 본다. 만약 이렇게 작성해서 프로그램을 실행하면 어떻게 될까? 

 

 

 예상한 것과 다르게 다른 값을 보낸다면 당연히 오류가 나야하지만, 실행 결과를 살펴보면 size의 값만 달라지고 프로그램은 정상적으로 실행이 잘 된다. 이렇게 처리하게 될 경우 악성 유저가 패킷의 사이즈를 조작하여 보낼 경우 큰 문제가 생길 수 있다. 따라서 Read 메서드를 실행하는 부분을 다음과 같이 수정한다.

 

public override void Read(ArraySegment<byte> s)
        {
            ushort count = 0;
            count += 2;
            count += 2;
            
            this.playerId = BitConverter.ToInt64(new 
			ReadOnlySpan<byte>(s.Array, s.Offset + count,
			s.Count - count));

            count += 8;
        }

 

 바로 BitConverter.Toint64의 오버로딩된 다른 기능을 활용하는 방법이다. 해당 오버로딩에서 ReadOnlySpan<>을 통해 값을 불러오는데, 만약 불러온 값이 이상하다면 오류를 출력해 줄 것이다.

 

스크립트 변경 이후 에러가 발생하는 실행결과.

 

 따라서 이렇게 클라이언트에서 보낸 패킷 정보를 무조건 Read 하여 확인하는 것이 아니라, 크기는 정확한지, 문제는 없는지 등에 대한 안정성을 위해 패킷의 사이즈를 체크하여야 한다.

 


 

불편..

 

 

 다만 현재 코드들을 작성할 때에 마다 Packet, PlayerInfoReq에 대한 정보를 Client, ServerSession 모두 가지고 있어야 하는 문제 점이 있다. 이는 추후에 Packet, PlayerInfoReq를 모두 라이브러리 형태로 개선하는 방안으로 수정할 수 있다.

 

 

반응형