メインコンテンツまでスキップ

StreamingHub を始める

このチュートリアルでは StreamingHub を始めるための簡単な手順を紹介します。

手順

StreamingHub を定義、実装、利用するには下記の手順が必要となります。

  • サーバーとクライアントの間で共有する StreamingHub インターフェイスを定義する
  • サーバープロジェクトで定義した StreamingHub インターフェイスを実装する
  • クライアントプロジェクトで定義した StreamingHub レシーバーを実装する
  • クライアントプロジェクトで定義した StreamingHub を呼び出すためのクライアントプロキシーを作成する

サーバーとクライアントの間で共有する StreamingHub インターフェイスを定義する

共有ライブラリープロジェクトに StreamingHub のインターフェイスを定義します (Unity の場合はソースコードコピーやファイルリンクで対応します)。

StreamingHub のインターフェースは IStreamingHub<TSelf, TReceiver> を継承する必要があります。TSelf にはインターフェース自身、TReceiver にはレシーバーインターフェイスを指定します。レシーバーインターフェースはサーバーからクライアントにメッセージを送信し、受信するためのインターフェースです。

以下はチャットアプリケーションの StreamingHub インターフェイスの例です。クライアントはメッセージの受信や参加、退出イベントを送るレシーバーインターフェースを持っています。

// A hub must inherit `IStreamingHub<TSelf, TReceiver>`.
public interface IChatHub : IStreamingHub<IChatHub, IChatHubReceiver>
{
ValueTask JoinAsync(string roomName, string userName);
ValueTask LeaveAsync();
ValueTask SendMessageAsync(string message);
}

public interface IChatHubReceiver
{
void OnJoin(string userName);
void OnLeave(string userName);
void OnSendMessage(string userName, string message);
}

StreamingHub が提供するメソッドを Hub メソッド と呼びます。Hub メソッドはクライアントから呼び出されるメソッドで、戻り値の型は ValueTask, ValueTask<T>, Task, Task<T>, void のいずれかである必要があります。Unary サービスとは異なることに注意が必要です。

クライアントがメッセージを受け取る口となるレシーバーインターフェースもまたメソッドを持ちます。これらを レシーバーメソッド と呼びます。レシーバーメソッドはサーバーからメッセージを受けたときに呼び出されるメソッドです。レシーバーメソッドの戻り値は void である必要があります。クライアント結果を使用する場合を除き、原則として void を指定します。

サーバープロジェクトで StreamingHub を実装する

サーバー上にクライアントから呼び出せる StreamingHub を実装する必要があります。サーバー実装は StreamingHubBase<THub, TReceiver> を継承し、定義した StreamingHub インターフェイスを実装する必要があります。

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
public async ValueTask JoinAsync(string roomName, string userName)
=> throw new NotImplementedException();

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

初めにチャットルームに参加するメソッド JoinAsync を実装します。このメソッドは指定された名前のルームに指定されたユーザー名で参加します。

Group.AddAsync メソッドでグループを作成し、そのグループへの参照を StreamingHub に保持してこの後の処理で使用します。グループの All プロパティーを介してグループに参加しているクライアントのレシーバーインターフェースを得られるので OnJoin メソッドを呼び出して参加を通知します。

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

Client プロパティーを使用するとその SteramingHub に接続しているクライアントのみを呼び出すこともできます。ここでは接続してきたクライアントにのみウェルカムメッセージを送信してみましょう。

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);

Client.OnMessage("System", $"Welcome, hello {userName}!");
}

public async ValueTask LeaveAsync()
=> throw new NotImplementedException();

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

次に退出するメソッド LeaveAsync も実装します。退出時にはグループからクライアントを削除します。これは Group.RemoveAsync メソッドを使用して行います。RemoveAsync メソッドには StreamingHubContext クラスのオブジェクト (Context プロパティー)を渡します。グループからクライアントが削除されるとグループを介したメッセージがそのクライアントには届かなくなります。

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
{
room.All.OnLeave(Context.ConnectionId);
await room.RemoveAsync(Context);
}

public async ValueTask SendMessageAsync(string message)
=> throw new NotImplementedException();
}

最後にクライアントからメッセージを受け取ったらグループに配信する SendMessageAsync メソッドを実装します。このメソッドではグループの All プロパティーを介してグループに参加しているクライアントの OnMessage メソッドを呼び出して通知します。

public class ChatHub : StreamingHubBase<IChatHub, IChatHubReceiver>, IChatHub
{
IGroup<IChatHubReceiver>? room;
string userName = "unknown";

public async ValueTask JoinAsync(string roomName, string userName)
{
this.room = await Group.AddAsync(roomName);
this.userName = userName;
room.All.OnJoin(userName);
}

public async ValueTask LeaveAsync()
{
room.All.OnLeave(Context.ConnectionId);
await room.RemoveAsync(Context);
}

public async ValueTask SendMessageAsync(string message)
{
room.All.OnMessage(userName, message);
}
}

クライアントプロジェクトで StreamingHub レシーバーを実装する

クライアントプロジェクトで StreamingHub のレシーバーインターフェースを実装します。このインターフェースはクライアント側でメッセージを受け取り、処理したい型に実装します。

ここではシンプルな ChatHubReceiver 型を作成し、レシーバーインターフェース IChatHubReceiver を実装します。それぞれのメソッドはサーバーから送信されたメッセージを受け取りコンソールにメッセージを出力します。

class ChatHubReceiver : IChatHubReceiver
{
public void OnJoin(string userName)
=> Console.WriteLine($"{userName} joined.");
public void OnLeave(string userName)
=> Console.WriteLine($"{userName} left.");
public void OnMessage(string userName, string message)
=> Console.WriteLine($"{userName}: {message}");
}

クライアントから StreamingHub に接続してメソッドを呼び出す

クライアントから StreamingHub に接続するには StreamingHubClient.ConnectAsync メソッドを使用します。このメソッドは接続を確立し、クライアントプロキシーを返します。

ConnectAsync メソッドには接続先の GrpcChannel オブジェクトとレシーバーインターフェースのインスタンスを渡します。接続が確立されるとクライアントプロキシーが返されます。サーバーから受信したメッセージはここで渡したレシーバーのインスタンスのメソッド呼び出しとなります。

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var receiver = new ChatHubReceiver();
var client = await StreamingHubClient.ConnectAsync<IChatHub, IChatHubReceiver>(channel, receiver);

await client.JoinAsync("room", "user1");
await client.SendMessageAsync("Hello, world!");