Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

알고리즘 공부방

(NW - Socket Programming) PacketManger 구현 본문

카테고리 없음

(NW - Socket Programming) PacketManger 구현

head89 2023. 12. 27. 17:06

먼저 PacketManager를 안 쓰고 패킷을 클라리언트와 서버가 주고받는다고 생각해보자.

기본적으로 Server에서든 Client에서든 패킷을 첫 byte는 packet의 ID로 설정할 것이다(일단 난 그렇게 함)

 PacketID를 통해 패킷에 맞는 Handler를 설정해줘야 할 것이다.

그렇다면 Manager를 거치지 않고 한다면 switch문과 같은 하드코딩식으로 들어갈 수 있다.

이렇게 된다면 굉장히 비효울적이다.

이것을 해결하기 위해 PacketManger를 공부 및 구현해보았다.

 

원초적으로 PacketManager가 하는 역할이 무엇일지,  PacketManager를 사용함으로써 해결되는 문제를 생각해보자.

앞서 위에서 PacketManager를 안 쓰고 구현했을 때 하드코딩이 될 수 있다는 단점이 있었다.

이 문제를 PacketManager를 사용하여 해결하면  2가지의 장점이 생긴다.

1. 하드코딩으로 하던 것을 자동화할 수 있다.

2. Handler를 하는 부분에서 PacketManager를 부르는 것만을 하기에 역할이 분리될 수 있어 객체지향적으로 

    더 효율적인 코딩이 가능해진다.

그렇기에 PacketManager에서는 PacketID에서 맞는 Handler를 실행시켜주는 역할을 하면 될 것같다.

 

 

1. PacketManager Singleton 패턴 적용

먼저 PacketManger는 Singleton패턴을 사용하여 하나의 Instance만 가지고 이것을 사용하면 좋을 것이다.

왜?

: 한번 정의를 해놓고 나면 따로 수정할 필요가 없기 때문에 싱글톤 패턴을 써서 하나의 Instance만 갖게 하는 것이 좋아보인다.

 

싱글톤 패턴을 구현하는 방법은 간단하다.

static PacketManager _instance;

public static PacketManager Instance
{
    get
    {
        if (_instance == null) _instance = new PacketManager();
        return _instance;
    }
}

 

다음과 같이 전역변수로 PacketManager에 접근할 수 있는 변수를 만들고, Properti를 이용하여

이 변수가 null이면 생성, null이 아니면 변수를 리턴해주는 식으로 구현해주면 된다.

 

 

2. 패킷이 생성이 되면 PacketManager에  등록

PacketManager는 각 패킷에 맞는 Handler를 연결시켜주는 역할을 해야한다.

이것을 어떻게 구현할 수 있을까?

먼저 처음 생각할 수 있는 것은 일단 각 패킷에 맞는 핸들러는 개발자가 코딩을 해야하는 부분이다.

그렇다면 어떤 부분을 자동화 시켜야할까?

모든 패킷은 공통적으로 앞에 4bit를 패킷 size와 패킷 id로 넣고 그 다음에 패킷에 정보를 넣는다.

그렇기에 첫 4bit을 꺼내서 어떤 패킷인지 확인하는 작업은 모든 패킷이 똑같은 것이다.

이 부분을 이제 PacketManager에 넣어준다.

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

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

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

 

다음으로 할 것이 받은 패킷의 ID를 알았으니 그 패킷에 맞는 Handler를 실행시켜줘야 한다.

이걸 구현할려면 하드코딩으로도 할 수 있겠지만, 취지와 맞지않는다.

그렇기에 자료구조인 Dictionarty를 사용하면 딕셔너리에 넣는 부분만 자동화시키면 전체 과정이 자동화 될 것이다.

그러면 Dictionary에 key와 value는 어떤게 들어가야할까?

key에는 packet의 ID가 들어가야 할 것이고, value에는 c#에서 action이 들어가면 될거같다.

 

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.C_PlayerInfoReq, MakePacket<C_PlayerInfoReq>);
    _handler.Add((ushort)PacketID.C_PlayerInfoReq, PacketHandler.C_PlayerInfoReqHandler);

}

 

 

마지막으로 할 것이, 각 패킷 id와 handler를 Dictionary에 넣어주는 작업을 자동화 해주면 된다.

 

3. Dictionary에 패킷에 맞는 Handler 자동 등록하기

이것을 하기위해서 먼저 해야할 작업이 "PacketManager Format 만들기"이다.

이게 무엇인가?

나도 이번에 처음으로 알게된 것인데, 기본적인 코드들을 미리 만들어놓고 바뀌는 부분을 인자로 받아 작성하는 방식이다.

예를 들어

        public static string packetManagerFormat =
@"
class PacketManager
{{
    static PacketManager _instance;

    public static PacketManager Instance
    {{
        get
        {{
            if (_instance == null) _instance = new PacketManager();
            return _instance;
        }}
    }}

    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()
    {{
        {0}
    }}
    public void OnRecvPacket(PacketSession session ,ArraySegment<byte> buffer)
    {{
        ushort count = 0;

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

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

    void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
    {{
        T p = new T();
        p.Read(buffer);
        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(p.Protocol, out action))
            action.Invoke(session, p);

    }}
}}
";

 

다음과 같이 PacketManager Format을 만들고 위의 "{0}"이 부분이 바뀌는 부분을 인자로 받아 넣어주게 되는 것이다.

즉 PDL을 받아 Packet을 만들때 마다 그 패킷의 정보를 "{0}"부분에 넣고, 이것을 Client와 Server쪽으로 뿌려주기만 하면 자동화가 되는 것이다.

이 부분은 좀 더 공부가 필요할거 같다. 

 

4. 전체 코드

(PacketManager Class)

using ServerCore;

class PacketManager
{
    static PacketManager _instance;

    public static PacketManager Instance
    {
        get
        {
            if (_instance == null) _instance = new PacketManager();
            return _instance;
        }
    }

    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.S_Test, MakePacket<S_Test>);
        _handler.Add((ushort)PacketID.S_Test, PacketHandler.S_TestHandler);

    }
    public void OnRecvPacket(PacketSession session ,ArraySegment<byte> buffer)
    {
        ushort count = 0;

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

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

    void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
    {
        T p = new T();
        p.Read(buffer);
        Action<PacketSession, IPacket> action = null;
        if (_handler.TryGetValue(p.Protocol, out action))
            action.Invoke(session, p);

    }
}

 

 

(Handler Class)

using ServerCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


class PacketHandler
{
    public static void C_PlayerInfoReqHandler(PacketSession session, IPacket packet)
    {
        C_PlayerInfoReq p = packet as C_PlayerInfoReq;
        Console.WriteLine($"PlayerId : {p.playerId}, PlayerName : {p.name}");

        foreach (C_PlayerInfoReq.Skile skile in p.skiles)
        {
            Console.WriteLine($"ID : {skile.id}, Level : {skile.level}, duration : {skile.duration}");
        }
    }
}