상세 해설 - 프로토콜 토큰
Bolt에는 프로토콜 토큰이라고 하는 기능을 가지고 있는데 Bolt에서 발생할 수 있는 특정 액션으로 독단적인 데이터(C# 객체들)를 붙이기 위해서 사용되는 것은 아래와 같은 것 입니다:
- 엔티티 인스턴스화
- 서버에 접속
- 씬 로딩
프로토콜 토큰이 존재하는 이유는 많은 경우에 특정 작업이 발생할 때 작은 데이터 조각을 사용하기를 원할 때가 있기 때문입니다.
네트워크의 특성상 데이터의 신뢰성이 보장되지 않기 때문에 액션 자체와같이 전송되어야 합니다.
프로토콜 토큰을 사용하여 이것을 수행할 수 있습니다.
특정 작업이 수행될 때 사용할 수 있는 데이터의 예는 다음과 같습니다:
- 플레이어 캐릭터를 인스턴스화 할 때 플레이어가 선택한 커스터마이징된 옵션 전송을 원할 때.
- 서버에 접속 시 플레이어가 사용자명과 비밀번호를 전송하기 원할 때.
- 씬이 로딩을 시작할 때 계절(가을, 겨울 등)에 대한 올바른 텍스쳐를 로드하기를 원하는 경우
프로토콜 토큰은 일반적인 C# 클래스를 정의하여 생성되고 Bolt.IProtocolToken
인터페이스에서 상속 됩니다.
이 인터페이스는 두 개의 메소드를 정의합니다: Write
와 Read
입니다.
이 메소드들은 클래스 내에서 구현할 필요가 있습니다.
C#
public class CharacterCustomization : Bolt.IProtocolToken
{
public int SkinId;
public int HatId;
public void Write(UdpKit.UdpPacket packet) {
packet.WriteInt(SkinId);
packet.WriteInt(HatId);
}
public void Read(UdpKit.UdpPacket packet) {
SkinId = packet.ReadInt();
HatId = packet.ReadInt();
}
}
여기에서 엔티티 인스턴스를 생성할 때 일부 캐릭터 커스머마이징 데이터를 포함하고 있는 프로토콜을 정의 했습니다.
두 개의 간단한 정수형 필드가 있으며 캐릭터의 스킨 id 번호와 모자 id를 나타냅니다.
Write
메소드에서 전달할 패킷 객체에 WriteXXX()
메소드를 호출하여 패킷으로 데이터를 씁니다.
반대로 Read
에서는 전달된 패킷에 ReadXXX()
를 호출 합니다.
중요: WriteXXX
와 ReadXXX
는 상응되는 Write
와 Read
메소드에서 동일한 순서로 호출 해야 합니다.
이제 토큰이 자주 사용하도록 설계된 경우 PooledProtocolToken
(Bolt v1.2.14 부터 시작)을 구현하는 것이 좋습니다.
이 유형은 일반 프로토콜 토큰을 확장하지만 토큰을 처리할 때 할당된 메모리를 재사용하기 위해 내부 풀링 시스템을 사용합니다.
아래에서 볼 수 있듯이 풀링된 버전을 정의하는 유일한 차이점은 클래스를 Bolt.IProtocolToken
이 아닌 Bolt.PooledProtocolToken
에서 확장하고 새 Reset
메소드를 구현합니다.
C#
public class PooledCharacterCustomization : PooledProtocolToken
{
public int SkinId;
public int HatId;
public override void Read(UdpPacket packet)
{
// Deserialize Token data
SkinId = packet.ReadInt();
HatId = packet.ReadInt();
}
public override void Write(UdpPacket packet)
{
// Serialize Token Data
packet.WriteInt(SkinId);
packet.WriteInt(HatId);
}
public override void Reset()
{
// Reset Token Data
SkinId = default(int);
HatId = default(int);
}
}
사용 예제
기본적인 사용
Protocol Token 클래스를 사용하려면 Bolt에 등록해야 합니다. 이 작업은 Bolt.GlobalEventListener
에서 상속되는 Unity behaviour를 생성하여 구현할 수 있는 BoltStartBegin
콜백에서 수행해야 합니다.
Bolt.GlobalEventListener
메소드에 대한 자세한 정보는글로벌 콜백 섹션에서 확인할 수 있습니다.
토큰 클래스를 등록하려면 BoltNetwork.RegisterTokenClass<T>()
를 호출해야 합니다. 지네릭 매개 변수 T
로 전달하며 아래 예제를 참조하십시오.
C#
[BoltGlobalBehaviour]
public class NetworkCallbacks : Bolt.GlobalEventListener {
public override void BoltStartBegin() {
BoltNetwork.RegisterTokenClass<CharacterCustomization>();
}
}
또 다른 옵션은 Protocol Token Registry
를 사용하여 토큰을 등록하는 것입니다.
Bolt v1.2.14
부터는 Bolt/Protocol Token Registry
메뉴에서 토큰 레지스트리를 열 수 있습니다. 레지스트리가 아직 없으면 새 레지스트리를 만들라는 메시지가 표시됩니다.
레지스트리 에셋이 생성되면 Refresh Protocol Token Registry
버튼을 클릭하면 볼트가 프로젝트를 스캔하고 모든 IProtocolToken
을 나열합니다.
이게 다 입니다. 수동 등록 코드를 모두 제거하고 Bolt에게 맡기면 돼요.
레지스트리 목록에 따라 볼트는 런타임에 모든 피어에서 항상 동일한 순서로 토큰을 자동으로 등록합니다.
등록 후 다음 두 가지 방법으로 토큰 인스턴스를 생성할 수 있습니다.
Bolt.IProtocolToken
:new
키워드를 사용한 일반적인 인스턴스화입니다.;Bolt.PooledProtocolToken
: 내부 풀에서 인스턴스를 얻기 위해서, 간단하게ProtocolTokenUtils.GetToken<T>()
를 호출하기만 하면 되며,T
는 풀된 프로토콜 토큰 유형입니다.
엔티티 인스턴스화 예제
새로운 엔티티
생성을 위해 BoltNetwork.Instantiate
메소드를 호출 할 때 CharacterCustomization
클래스의 인스턴스를 전달할 수 있습니다:
C#
// ...
public void SpawnCharacter()
{
var token = new CharacterCustomization();
token.SkinId = 5;
token.HatId = 12;
BoltNetwork.Instantiate(BoltPrefabs.CharacterPrefab, token);
}
// ...
이 토큰을 Bolt.EntityBehaviour
그리고 Bolt.EntityBehaviour<T>
에서 상속을 받은 C# 스크립트 Attached
콜백에서 접근할 수 있습니다:
C#
public class MyEntityBehaviour : Bolt.EntityBehaviour<ICharacterState> {
public override void Attached() {
var customization = (CharacterCustomization)entity.attachToken;
// ... use the customization data here
}
}
인증 예제
이 예에서는 Protocol Token을 사용하여 사용자/암호 페어를 전송하여 서버를 통해 클라이언트를 인증하고 인증 결과에 대한 서버의 응답을 수신하여 플레이어가 적절하게 처리할 수 있도록 합니다.
이 예제가 제대로 작동하려면 Bolt 설정
창에서 수동
을 자동
에서 수동
으로 변경해야 합니다.
이렇게 하면 서버는 클라이언트가 보낸 토큰을 처리하고 토큰이 유효한지 여부를 결정할 수 있습니다.
먼저 다음 두 가지 새 프로토콜 토큰을 정의합니다.
UserToken
: 클라이언트가 인증 정보와 함께 전송.AuthResultToken
: 서버에서 작업 결과와 함께 전송.
C#
using UdpKit;
namespace ProtocolTokenExample
{
public class UserToken : Bolt.IProtocolToken
{
public string Username { get; set; }
public string Password { get; set; }
public void Read(UdpPacket packet)
{
this.Username = packet.ReadString();
this.Password = packet.ReadString();
}
public void Write(UdpPacket packet)
{
packet.WriteString(this.Username);
packet.WriteString(this.Password);
}
}
}
C#
using System;
using UdpKit;
namespace ProtocolTokenExample
{
public class AuthResultToken : Bolt.IProtocolToken
{
public static AuthResultToken Invalid = new AuthResultToken();
public int Ticket { get; private set; }
public string Id { get; private set; }
private static int CurrentTicket = 0;
public AuthResultToken()
{
this.Ticket = CurrentTicket++;
this.Id = "";
}
public AuthResultToken(string id) : this()
{
this.Id = id;
}
public void Read(UdpPacket packet)
{
this.Ticket = packet.ReadInt();
this.Id = packet.ReadString();
}
public void Write(UdpPacket packet)
{
packet.WriteInt(this.Ticket);
packet.WriteString(this.Id);
}
}
}
당사는 서버 및 클라이언트 이벤트 수신기를 구현하여 인증 시스템을 계속 구축하고 있습니다.
이렇게 하면 호스트의 클라이언트에서 요청을 처리할 수 있습니다.
먼저, 샘플에 포함되어 있는 BoltInit.cs
파일을 수정할 것입니다.
서버에 연결하려고 할 때, UserToken
코드를 변경할것입니다. 아래를 확인해보세요:
C#
// BoltInit.cs
public override void BoltStartBegin()
{
// In order to use the Tokens, we need to register them
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
// ...
void State_SelectRoom()
{
GUI.Label(labelRoom, "Looking for rooms:", labelRoomStyle);
if (BoltNetwork.SessionList.Count > 0)
{
GUILayout.BeginVertical();
GUILayout.Space(30);
foreach (var session in BoltNetwork.SessionList)
{
var photonSession = session.Value as PhotonSession;
if (photonSession.Source == UdpSessionSource.Photon)
{
var matchName = photonSession.HostName;
var label = string.Format("Join: {0} | {1}/{2}", matchName, photonSession.ConnectionsCurrent, photonSession.ConnectionsMax);
if (ExpandButton(label))
{
// Using the UserToken to send our credential information
UserToken credentials = new UserToken();
credentials.Username = "Bob";
credentials.Password = "654321";
BoltNetwork.Connect(photonSession, credentials);
state = State.Started;
}
}
}
GUILayout.EndVertical();
}
}
// ...
업데이트된 코드를 사용하면 클라이언트가 게임 호스트에 연결하려고 하면 사용자 이름
과 암호
정보를 전달하는 토큰이 전송됩니다.
좋습니다. 이제 이 토큰을 서버
쪽에서 받을 수 있는 방법이 필요합니다. 이렇게 하려면 예를들어, 호스트 플레이어에서만 실행되는 모든 Bolt.GlobalEventListener
의 ConnectRequest
콜백을 구현해야 합니다(). 다음을 참고하세요:
C#
using System;
using Bolt;
using UdpKit;
namespace ProtocolTokenExample
{
[BoltGlobalBehaviour(BoltNetworkModes.Server)]
public class NetworkServerCallbacks : Bolt.GlobalEventListener
{
public override void BoltStartBegin()
{
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
private bool AuthUser(string user, string pass)
{
// Authenticate your client...
return false;
}
public override void ConnectRequest(UdpEndPoint endpoint, IProtocolToken token)
{
BoltLog.Warn("Connect Request");
var userToken = token as UserToken;
if (userToken != null)
{
if (AuthUser(userToken.Username, userToken.Password))
{
AuthResultToken resultToken = new AuthResultToken(Guid.NewGuid().ToString());
BoltNetwork.Accept(endpoint, resultToken);
return;
}
}
BoltNetwork.Refuse(endpoint, AuthResultToken.Invalid);
}
}
}
마지막으로 클라이언트측에서는 서버가 보낸 이 정보를 ConnectRefused
또는 Connected
콜백으로 검색하여 플레이어에 대한 적절한 대응을 보여줄 수 있습니다.
이 작업은 다른 Bolt.GlobalEventListener
에서 수행할 수 있습니다. 하지만 이번에는 클라이언트에서만 실행됩니다.
C#
using Bolt;
using UdpKit;
namespace ProtocolTokenExample
{
[BoltGlobalBehaviour(BoltNetworkModes.Client)]
public class NetworkClientCallbacks : Bolt.GlobalEventListener
{
public override void BoltStartBegin()
{
BoltNetwork.RegisterTokenClass<UserToken>();
BoltNetwork.RegisterTokenClass<AuthResultToken>();
}
public override void Connected(BoltConnection connection)
{
BoltLog.Warn("Connection accepted!");
AuthResultToken acceptToken = connection.AcceptToken as AuthResultToken;
if (acceptToken != null)
{
BoltLog.Info("AcceptToken: " + acceptToken.GetType());
BoltLog.Info("Token: {0}-{1}", acceptToken.Ticket, acceptToken.Id);
}
else
{
BoltLog.Warn("AcceptToken is NULL");
}
}
public override void ConnectRefused(UdpEndPoint endpoint, IProtocolToken token)
{
BoltLog.Warn("Connection refused!");
var authToken = token as AuthResultToken;
if (authToken != null)
{
BoltLog.Warn("Token: {0}-{1}", authToken.Ticket, authToken.Id);
}
}
}
}
Back to top