Client, Server에서 사용하는 GenPakcets의 내용을 계속 작성하고, 복사 붙여넣기 하는 과정을 배치 파일을 만들어 실행시켜주는 방법으로 자동화를 하였다. 이제 다시 이어서 더 개선할 수 있는 부분 작업을 할 예정이다.
기존 코드를 보면 ClientSession의 OnRecvPacket에서 받은 패킷의 ID를 통해 Switch문에서 검사를 해주는 작업을 하였다.
하지만 갈수록 패킷의 종류는 다양해질 것이기에 수많은 Switch 문을 거치는 비요율적인 상황이 나오기에 이 부분을 패킷 타입에 맞게 자동 생성하는 코드로 변화시키고자 한다.
우선 어떠한 방식으로 구성할지 코드를 짜보고, 그걸 자동화 하도록 할 것이다.
packetId를 포함한 interface를 구현한 뒤, 상속 받아 따로 값을 보관하도록 한다.
필수로 구현 되어야하는 Read와 Write를 포함해주고, packetId를 담아둘 Protocol이라는 변수를 선언한 뒤에 return 값으로 선언해준다.
이제 추가한 영역을 PacketFormat을 통해 자동화 해주면 된다.
IPacket 이라는 interface는 fileFormat 영역에 적당히 잘 넣어주고,
packetFormat 영역에서 생성한 클래스가 IPacket을 상속 받고, 자신의 protocol을 가질 수 있도록 구현해주면 된다.
interface, abstact 차이?
기본적으로 둘다 상속을 받아 내용을 구현하는 부분은 굉장히 유사하나 사용 목적에 차이가 있다.
- abstact class는 기본적으로 클래스 이며 이를 상속, 확장하여 사용하기 위함
- interface는 해당 인터페이스를 구현한 객체들에 대한 동일한 사용방법과 동작을 보장하기 위함
이외에도 interface는 public이고 다중 상속이 가능하다라는 차이도 존재한다.
이제 앞서 말했던 Switch를 통해 하나하나 비교하며 처리해야하던 부분을 개선할 것이다.
우선 PacketHandler와 PacketManager 를 추가해주자.
PacketManager의 경우 여러번 등록하며 사용하는 것이 아닌 유일한 존재로 활용 가능하기에 싱글톤으로 등록한다.
#region Singleton
static PacketManager _instance;
public static PacketManager instance
{
get
{
if (_instance == null)
_instance = new PacketManager();
return _instance;
}
}
#endregion Singleton
OnRecvPacket()라는 함수를 만들고 ClientSession - OnRecvPacket()에서 동작하던 코드 일부를 여기로 옮겨준다.
그 뒤에 원래 동작하던 부분에서는 설정한 싱글톤을 활용하여 가볍게 동작시켜준다.
PacketManager.instance.OnRecvPacket(this, buffer);
이제 해당 패킷이 어떤 패킷인지 알려주고, 검사하기 위한 작업들이 추가 될 것인데
우선 어떤 패킷인지 등록하고 알려줄 방식으로 Dictionary
를 사용할 것이다.
Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv =
new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
Dictionary는 키, 벨류 값으로 구분되며 _onRecv 라는 Dictionary에 키 값에는 packetId, value 값에는 패킷을 만들어 넣어줄 것이다.
추가로 _handler로 활용한 부분도 미리 제작해준다. 여기도 예시는 PlayerInfoReqHandler이지만 패킷에 따라 변경되게 변환해줄 것이다.
MakePacket() 함수를 통해 기존에 패킷을 생성하고 Read를 해주던 작업을 할 것이다.
여기서 좀 생소한 개념들이 몇개 나오는데, 우선 PlayerInforReq라는 Class 타입을 받아야하기에 <T>
를 활용해 제네틱 타입으로 선언한다.
받아온 T가 IPacket을 상속 받고 있으며, new() 선언이 가능한지 제약을 걸어준다.
where T : IPacket, new()
void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
{
where T : 제약조건
Clss나 메서드 뒤에 where T : 제약조건을 선언 해주면 조건에 따라 원하는 제약을 설정해줄 수 있다.
제약 조건 | 설명 |
---|---|
where T : struct | T는 값 형식 |
where T : class | T는 참조 형식 |
where T : new() | T는 매개 변수가 없는 생성자가 있어야 함 |
where T : 부모 클래스 이름 | T는 부모 클래스의 자식 클래스여야 함 |
where T : 인터페이스 이름 | T는 인터페이스를 반드시 구현해야 함. 여러개의 인터페이스를 명시 가능 |
where T : U | T는 다른 형식 매개 변수 U로부터 상속받은 클래스여야 함 |
여기서는 T 값을 통해 PacketId Class를 넣어줄 것이다. 그를 선언하여 Read 시켜준다.
이후 Delegate-Action을 선언하고, TryGetValue
로 Dictionary에 넣어둔 값을 뽑아 out에 담아주고 handler를 실행 시켜준다.
이제 위에서 열심히 조립한 패킷이 들이 다 잘 도착했다면, 타입에 맞게 캐스트 해준 후 foreach를 돌며 출력 해주게 코드를 제작해주면 된다.
이 부분이 Switch문 안에 들어왔을 때 동작하던 부분이다.
이제 멀티 쓰레드에 영향을 받지 않는 곳에 Register를 등록 시켜주면 준비는 끝이다.
위와 같이 제작을 해주면 이제 어떤 패킷인지 검사하는 것이 아니라, 그 패킷에 맞게 생성을 하고 넘겨주는 방식으로 변경된 것이다.
시작 프로그램을 다시 여러개로 변경하여 테스트해보면 아주 잘 작동한다. 이후 자동화는 다음 시간에 진행될 예정이다. 이제 어떤 부분을 자동화 해야할지는 보인다. 어떻게 처리하면 될지를 빠르게 따라가 보자.