C#/네트워크

[c#][서버] PacketGenerator (3)

goliot 2024. 6. 12. 17:50
반응형

파일 참조 자동화

  • 지금까지는 파일이나 xml을 만들고 수동으로 반영을 했어야 했다.
  • 이제 경로를 설정하여 생성된 파일을 자동으로 참조하도록 해보자

.bat 파일 작성

  • .bat파일을 생성하고 각자 경로에 맞게 다음과 같이 수정
START ../../PacketGenerator/bin/Debug/PacketGenerator.exe ../../PacketGenerator/PDL.xml
XCOPY /Y GenPackets.cs "../../DummyClient/Packet"
XCOPY /Y GenPackets.cs "../../Server/Packet"
  • 뒤쪽에 있는 인자가 main의 args에 들어가서 실행되게 된다.
  • XCOPY는 파일을 복사해서 넣는다는 것이고
    • /Y 옵션을 넣으면 동일 파일 존재할 시 덮어쓰기 이다
  • 생성 bat파일을 실행시켜보면?
    • 바로 밑에 GenPackets.cs가 생겨나고, XCOPY에 설정한 경로에도 복사되어 있

이렇게 결과물이 생겨난다

Packet 개선

switch((PacketID)id)
{
    case PacketID.PlayerInfoReq:
        {
            PlayerInfoReq p = new PlayerInfoReq();
            p.Read(buffer);
            Console.WriteLine($"PlayerInfoReq: {p.playerId} {p.name}");

            foreach(PlayerInfoReq.Skill skill in p.skills)
            {
                Console.WriteLine($"Skill({skill.id})({skill.level})({skill.duration})");
            }
        }
        break;
}
  • 패킷을 읽을 때 ClientSession의 이 부분에서 case가 많아지면 많아질수록 성능 저하로 이어질 것이다
  • OnRecvPacket 함수의 내부를 이 한줄로 대체하고, PacketManager, PacketHandler를 만든다
    • PacketManager.Instance.onRecvPacket(this, buffer);
namespace Server
{
    class PacketHandler
    {
        public static void PlayerInfoReqHandler(PacketSession session, IPacket packet)
        {
            PlayerInfoReq p = packet as PlayerInfoReq;

            Console.WriteLine($"PlayerInfoReq: {p.playerId} {p.name}");

            foreach (PlayerInfoReq.Skill skill in p.skills)
            {
                Console.WriteLine($"Skill({skill.id})({skill.level})({skill.duration})");
            }
        }
    }
    
        class PacketManager
    {
#region Singleton
        static PacketManager _instance;
        public static PacketManager Instance
        {
            get
            {
                if(_instance == null )
                    _instance = new PacketManager();
                return _instance;
            }
        }
#endregion
        //Protocol ID, 작업
        Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
        Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();

        public void Register()
        {
            _onRecv.Add((ushort)PacketID.PlayerInfoReq, MakePacket<PlayerInfoReq>);
            _handler.Add((ushort)PacketID.PlayerInfoReq, PacketHandler.PlayerInfoReqHandler);
        }

        public void onRecvPacket(PacketSession session, 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;

            Action<PacketSession, ArraySegment<byte>> action = null;
            if (_onRecv.TryGetValue(id, out action))
                action.Invoke(session, buffer);
        }

        void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new ()
        {
            T pkt = new T();
            pkt.Read(buffer);

            Action<PacketSession, IPacket> action = null;
            if (_handler.TryGetValue(pkt.Protocol, out action))
                action.Invoke(session, pkt);
        }
    }
}
  • 새로운 패킷 종류가 생긴다면, 해당 패킷 형식을 처리하는 인터페이스를 PacketHandler에다가 파주면 자동으로 찾아서 갈 것이다.
  • PacketManager는 자동화가 가능하니 PacketFormat에 넣어주자

Packet 안전성

  • 사실 DummyClient에서는 PacketHandler안의 PlayerInfoReqHandler가 정의될 필요가 없다.
    • 이건 서버에서 온 패킷을 읽을때 쓰는 것이다.
  • 그러므로, 이런 필요 없는 것들이 정의되지 않아야, 조금이라도 해킹에 안전성을 높일 수 있다.
    • 이런 저런 패킷 이름으로 패킷을 쏘다가 우연히 딱 걸릴 확률을 줄이자.
    • 또한, 패킷의 용도, 사용처 등등의 안전처리를 해두고, 매니저에는 딱 필요한 것만 추가해야 한다.
  • 패킷 정의 수정
<packet name="C_PlayerInfoReq">
...
</packet>
<packet name="S_Test">
...
</packet>
  • 위와 같이 C가 붙은 것은 클라 -> 서버로 보내는 것 = 서버 쪽 매니저에 등록
  • S가 붙은 것은 서버 -> 클라로 보내는 것 = 클라 쪽 매니저에 등록하게 한다
if(packetName.StartsWith("S_") || packetName.StartsWith("s_"))
    clientRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
else
    serverRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
  • 위와 같이 코드 작성시 필터를 하나 씌워주고
File.WriteAllText("ClientPacketManager.cs", clientManagerText);
string serverManagerText = string.Format(PacketFormat.managerFormat, serverRegister);
File.WriteAllText("ServerPacketManager.cs", serverManagerText);
  • 위와 같이 파일을 각각 따로 작성하게 한다, 물론 .bat파일도 수정해야 한다.
반응형