이번 포스트에서는 이전의 코드에서 유니티의 자료형 중 하나인 GameObject를 사용해보겠습니다.
그러나 이전의 상태에서 유니티의 자료형 즉, 유니티의 데이터를 사용하는 경우
실행되지 않는 문제가 발생하게 됩니다.
그 이유는 다음과 같은데,
현재 네트워크 패킷은 유니티의 메인 스레드가 아닌
ThreadPool에 저장된 패킷을 가져와 사용하고 있습니다.
이때 유니티에서는 게임의 스레드가 아닌 다른 스레드에서
게임 요소와 관련된 로직을 수행하는 것을 원천봉쇄하기에 실행이 되지 않습니다.
따라서 이를 위해 만들어진 패킷을 바로 사용하는 것이 아닌 큐에 일시적으로 보관하고
보관된 패킷을 유니티에서 사용할 수 있도록 조정하여 사용하도록 하겠습니다.
1) 유니티 프로젝트 - PacketQueue
public class PacketQueue
{
public static PacketQueue Instance { get; } = new PacketQueue();
Queue<IPacket> _packetQueue = new Queue<IPacket>();
object _lock = new object();
public void Push(IPacket packet)
{
lock (_lock)
{
_packetQueue.Enqueue(packet);
}
}
public IPacket Pop()
{
lock (_lock)
{
if(_packetQueue.Count == 0)
return null;
return _packetQueue.Dequeue();
}
}
}
스레드 풀의 스레드에서 만들어진 패킷을 저장하는 용도의 큐를 위와 같이 만들었습니다.
기본적인 큐의 동작방식인 Pop과 Push를 구현하였습니다.
이때 Pop과 Push되는 주체는 IPacket입니다.
2) 유니티 프로젝트 - PacketHandler
class PacketHandler
{
// 인자는 어떤 세션에서 , 어떤 패킷이 조립되었는지를 의미함
public static void S_ChatHandler(PacketSession session, IPacket packet)
{
S_Chat chatPacket = packet as S_Chat;
ServerSession serverSession = session as ServerSession;
{
UnityEngine.Debug.Log(chatPacket.chat);
GameObject gameObject = GameObject.Find("Player");
if (gameObject == null)
UnityEngine.Debug.Log("Player not Found");
else
UnityEngine.Debug.Log("Player Found");
}
}
}
패킷이 수행하는 작업을 정의한 PacketHandler의 경우
위와 같이 Unity의 데이터 단위인 GameObject를 사용하도록 로직을 변경하였습니다.
3) PacketManager
class PacketManager
{
.
.
.
Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>> _makeFunc = new Dictionary<ushort, Func<PacketSession, ArraySegment<byte>, IPacket>>();
Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();
public void Register()
{
_makeFunc.Add((ushort)PacketID.S_Chat, MakePacket<S_Chat>);
_handler.Add((ushort)PacketID.S_Chat, PacketHandler.S_ChatHandler);
}
public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer, Action<PacketSession, IPacket> onRecvCallback = null)
{
.
.
.
Func<PacketSession, ArraySegment<byte>, IPacket> func = null;
if (_makeFunc.TryGetValue(id, out func))
{
// 패킷을 만들어서 리턴
IPacket packet = func.Invoke(session, buffer);
// 특정 Func를 호출하고자 하는 경우 수행
if (onRecvCallback != null)
onRecvCallback.Invoke(session, packet);
// 아니라면 기본 HandlePacket 콜백 호출
else
HandlePacket(session, packet);
}
}
T MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
{
T packet = new T();
packet.Read(buffer);
return packet;
}
public void HandlePacket(PacketSession session, IPacket packet)
{
Action<PacketSession, IPacket> action = null;
if (_handler.TryGetValue(packet.Protocol, out action))
action.Invoke(session, packet);
}
}
Packet을 관리하는 PacketManager의 경우, 많은 부분에서 변경이 생겼습니다.
우선, MakePacket 메소드의 동작 구조를 변경하였습니다.
해당 메서드의 로직 수행 과정을 분리하여 수행하도록 합니다.
Packet을 만들고 해당 Packet을 읽어들여 리턴까지 하는 것은 MakePacket에서 수행합니다.
읽어들인 Packet에 연결된 콜백함수를 수행하는 것은 HandlePacket 메소드에서 수행합니다.
또 사용하던 프로퍼티 _onRecv 딕셔너리의 이름을 수정하여 _makeFunc라는 이름으로 사용합니다.
메서드 OnRecvPacket의 구조를 바꿔서 인자로 수행할 콜백함수를 넣어주게끔 수정하였습니다.
따라서 수행할 특정 콜백함수를 넣어주면 해당 콜백 함수를 수행하지만,
넣어주지 않으면 기본적으로 HandlePacket 메서드를 수행합니다.
위와 같이 PacketManager의 구조가 변경되었으므로
PacketFormat의 구조도 변경하여 자동적으로 수정되게끔 해줄 필요가 있습니다.
이 부분은 생략하겠습니다.
4) 유니티 프로젝트 - ServerSession
class ServerSession : PacketSession
{
.
.
.
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
PacketManager.Instance.OnRecvPacket(this, buffer, (session, packet) => PacketQueue.Instance.Push(packet));
}
.
.
.
}
ServerSession의 경우 Packet을 받으면 해당 패킷을 PacketQueue에 Push 하도록 설정하였습니다.
위의 과정까지가 PacketQueue에 패킷을 보관하는 단계였습니다.
이제 NetworkManager에서 보관된 패킷을 실행하겠습니다.
5) 유니티 프로젝트 - NetworkManager
public class NetworkManager : MonoBehaviour
{
ServerSession _session = new ServerSession();
void Start()
{
.
.
.
StartCoroutine("SendPacketperTime");
}
void Update()
{
IPacket packet = PacketQueue.Instance.Pop();
if (packet != null)
{
PacketManager.Instance.HandlePacket(_session, packet);
}
}
IEnumerator SendPacketperTime()
{
while (true)
{
yield return new WaitForSeconds(3.0f);
C_Chat chatPacket = new C_Chat();
chatPacket.chat = "Hello from Unity!!!";
ArraySegment<byte> segment = chatPacket.Write();
_session.Send(segment);
}
}
}
매 프레임마다 실행하는 Update 메서드에서는 받은 Packet에 연결된 콜백함수를 실행합니다.
이때 특정 콜백 함수를 지정하지 않았으므로 HandlePacket이 실행됩니다.
또한 Start 메서드에서
ServerSession을 하나 생성하는 것은 이전과 동일하지만,
코루틴을 호출하여 3초 주기로 서버에 Packet을 보내게 됩니다.
이제 Server와 유니티 프로젝트만을 실행해보겠습니다.
최종 실행 결과는 다음과 같습니다.