C#でSignalRを使用した通信を行いたい その6(クライアントがサーバにデータを貰う) | プログラミングがわからなすぎる

プログラミングがわからなすぎる

気が向いた時の備忘録。プログラミングは好きなのに物凄く単純なものしか理解できないからメモしていくうちに覚えられたらいいな

クライアントがサーバに呼び掛けて、返事をもらう処理のサンプルを

作って動かしてみようと思う

つまりサーバ書かれた戻り値ありのメソッドを、クライアントから呼び出したい

 

クライアント側のプロジェクト 

部品をいくつか追加した

 

今回新たに使う画面部品の名前はこの通り

(チャットとして発言できるようにするためにメッセージ入力枠も作ったけど

実装はまた今度かんがえます)

 

 ClientForm.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace SignalRClientTest
{
    public partial class ClientForm : Form
    {
        public ClientForm()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 接続ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ConnectButton_Click(object sender, EventArgs e)
        {
            bool result = SignalRClient.Connect();
            if (result)
                MessageBox.Show("接続成功");
            else
                MessageBox.Show("接続失敗");
        }

        /// <summary>
        /// 切断ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisConnectButton_Click(object sender, EventArgs e)
        {
            SignalRClient.DisConnect();
        }

        /// <summary>
        /// 設定ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SendNameButton_Click(object sender, EventArgs e)
        {
            SignalRClient.SendName(NameTextBox.Text);
        }

        /// <summary>
        /// 参加者の確認ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void GetNameButton_Click(object sender, EventArgs e)
        {
            List<string> names = await SignalRClient.GetNames();

            string nameStr = "";
            foreach (string name in names)
            {
                if (nameStr == "")
                    nameStr = name;
                else
                    nameStr += "、" + name;
            }

            if (names.Count > 0)
                TalkRichTextBox.Text += nameStr + "が参加中です。\r\n";
            else
                TalkRichTextBox.Text += "参加者は居ません。\r\n";
        }


        /// <summary>
        /// 退出ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void ExitButton_Click(object sender, EventArgs e)
        {
            bool result = await SignalRClient.ExitRoom(NameTextBox.Text);
            if (result)
                TalkRichTextBox.Text += "退出しました\r\n";
            else
                TalkRichTextBox.Text += "退出に失敗しました\r\n";
        }

    }
}

「参加者の確認」ボタンをクリックするとSignalRClientクラスのGetNames()が呼ばれます

GetNames()の戻り値であるList<string>は参加者の名前が設定されたListです

 

「退出」ボタンをクリックするとExitRoom()に名前テキストボックスの値を

渡して、実行結果のboolを受け取ります

 

 SignalRClient.cs

using Microsoft.AspNet.SignalR.Client;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SignalRClientTest
{
    class SignalRClient
    {
        private static HubConnection hub;
        private static IHubProxy proxy;
        private const string URL = "http://localhost:8088";
        private const string HUB_CLASS = "TestHub";

        /// <summary>
        /// 接続処理
        /// </summary>
        /// <returns>結果 true:成功 false:失敗</returns>
        public static bool Connect()
        {
            try
            {
                hub = new HubConnection(URL);
                proxy = hub.CreateHubProxy(HUB_CLASS);
                hub.Start().Wait(); // 接続実行
            }
            catch
            {
                // 接続失敗
                return false;
            }
            return true;
        }

        /// <summary>
        /// 切断処理
        /// </summary>
        public static void DisConnect()
        {
            hub?.Stop();
            hub?.Dispose();
        }

        /// <summary>
        /// 名前送信
        /// </summary>
        /// <param name="name"></param>
        public static void SendName(string name)
        {
            proxy.Invoke("AnnounceSendName");
            proxy.Invoke("GetName", name);
        }

        /// <summary>
        /// 参加者の取得処理
        /// </summary>
        /// <returns></returns>
        public static async Task<List<string>> GetNames()
        {
            List<string> namelist = await proxy.Invoke<List<string>>("SendOurNames");
            return namelist;
        }

        /// <summary>
        /// 退出処理
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static async Task<bool> ExitRoom(string name)
        {
            bool ret = await proxy.Invoke<bool>("ExitChatRoom", name);
            return ret;
        }

    }
}

GetNames()で、サーバ側の"SendOurNames"メソッドを呼んでもらいます

ExitRoom()ではサーバ側の"ExitChatRoom"メソッドに名前を送って、結果のboolを受け取ってもらいます

 

サーバ側のプロジェクト 

見た目は前回とあんまり変わりはない

(画面とテキストボックスの横幅をちょっと広くしておいた。ログが見やすくなった!)

 

 FormSet.cs

using System.Collections.Generic;

namespace SignalRServerTest
{
    public static class FormSet
    {
        /// <summary>
        /// フォーム
        /// </summary>
        public static ServerForm form { get; set; }

        /// <summary>
        /// 接続中クライアント一覧
        /// </summary>
        public static Dictionary<string, string> clients = new Dictionary<string, string>();

    }
}

接続中のクライアントを保持してもらうためのDictionaryを追加しました

ここに接続IDを参加者の名前を設定していきます

 

 ServerForm.cs ※前と変わりなし

using System;
using System.Windows.Forms;

namespace SignalRServerTest
{
    public partial class ServerForm : Form
    {
        private SignalRHost server;

        public ServerForm()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 開始ボタンクリック
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenButton_Click(object sender, EventArgs e)
        {
            server = SignalRHost.signalRHost;
            server.Start();
        }

        /// <summary>
        /// フォームを閉じる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ServerForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            server.Stop();
        }

        /// <summary>
        /// メッセージ表示
        /// </summary>
        /// <param name="msg"></param>
        public void ShowMessage(string msg)
        {
            MessageTextBox.Text += msg + "\r\n";
        }
    }
}

 

 SignalRHost.cs ※前と変わりなし

using Microsoft.Owin.Hosting;
using System;

namespace SignalRServerTest
{
    class SignalRHost
    {
        public static SignalRHost signalRHost = new SignalRHost();
        //private readonly IHubContext ihubcontext;
        private IDisposable server;

        //public SignalRHost()
        //{
        //    ihubcontext = GlobalHost.ConnectionManager.GetHubContext<TestHub>();
        //}

        /// <summary>
        /// 開始
        /// </summary>
        public void Start()
        {
            server = WebApp.Start<Startup>("http://localhost:8088");
        }

        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            server?.Dispose();
        }
    }
}

 

 Startup.cs ※前と変わりなし

using Microsoft.AspNet.SignalR;
using Owin;

namespace SignalRServerTest
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR(new HubConfiguration());
        }
    }
}

 

 TestHub.cs

using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SignalRServerTest
{
    public class TestHub : Hub
    {
        public void AnnounceSendName()
        {
            FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), 
                "名前が送信されました");
        }

        public void GetName(string name)
        {
            FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), 
                "名前:" + name);
            FormSet.clients.Add(Context.ConnectionId, name);
        }

        public List<string> SendOurNames()
        {
            List<string> names = new List<string>(FormSet.clients.Values);
            return names;
        }

        public bool ExitChatRoom(string name)
        {
            if (FormSet.clients[Context.ConnectionId] == name)
            {
                FormSet.clients.Remove(Context.ConnectionId);
                FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), 
                    name + "さんが退出しました。");
                return true;
            }
            else
            {
                FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), 
                    name + "さんの退出処理に失敗しました。");
                return false;
            }
        }


        public override Task OnConnected()
        {
            object a = FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), Context.ConnectionId + "が接続しました");
            return base.OnConnected();
        }
        public override Task OnDisconnected(bool stopCalled)
        {
            FormSet.form.Invoke(new Action<string>(FormSet.form.ShowMessage), Context.ConnectionId + "が切断しました");
            return base.OnDisconnected(stopCalled);
        }
    }
}

GetName()メソッドはクライアント側で名前を設定したときに呼び出されるメソッドです

これが呼び出されたときにFormSetクラスに追加した"clients"に

 キー:接続ID

 値 :名前

で保持してもらいます

 

SendOurNames()メソッドは、クライアントから呼ばれたらFormSetのclientsの

値だけListに設定して、クライアントに送ります

 

ExitChatRoom()メソッドは、FormSetのclientsに

呼び出してきたクライアントの接続IDと、送られてきた名前が一致したら

参加者一覧clientsから消すことにします

ちゃんと消せたら、クライアントにtrueを返却します

 

動かしてみる 

まずサーバを実行して、「開始」ボタンを押しておきます

クライアントは複数人いてほしいので

bin\Debug配下に出来ているexeをクリックして

画面を複数開いておきます

 

左のクライアントの接続をしてみます。

前までに作ってあったところなのでちゃんと動いてるみたいです

 

参加者の確認ボタンをクリックしてみます

今はだれも名前を送信していないので、ちゃんと参加者は居ないことになってます

 

左のクライアントの名前に「いぬ」を入力して送信ボタンをクリックします

これでサーバ側のclientsに「いぬ」が追加されたはず

 

確かめるために「参加者の確認」ボタンをクリックしてみます

ちゃんと「いぬ」がclientsに追加されて、サーバから名前を受け取れたみたいです

 

右の画面も接続をはじめます

 

右の画面は「ねこ」で名前を送信します

今clientsには「いぬ」と「ねこ」が設定されているはず

 

「いぬ」の画面で「参加者の確認」ボタンを押してみると

いぬとねこが受け取れたようです

 

「いぬ」の画面で「退出」ボタンをクリックして

いぬを退出させてみます

今clientsはねこだけになってるはず

 

「ねこ」の画面で「参加者の確認」ボタンをクリックしてみると

参加者はねこだけになっているので、clientsからちゃんといぬが消されてます

 

クライアントからサーバのメソッドを呼び出すメモ※戻り値あり 

 

パラメータを渡さない場合:

 戻り値変数 = await proxy.Invoke<戻り値の型>("呼び出すサーバのメソッド名")

 

パラメータを渡す場合:

 戻り値変数 = await proxy.Invoke<戻り値の型>("呼び出すサーバのメソッド名", 送りたい値);

 

複数パラメータを渡す場合:

 戻り値変数 = proxy.Invoke<戻り値の型>("呼び出すサーバのメソッド名", 送りたい値1, 送りたい値2)

※proxy:IHubProxy
 
次のような書き方もできるみたい
 戻り値変数 = proxy.Invoke<戻り値の型>("呼び出すサーバのメソッド名", 送りたい値).Result;
この書き方だと、メソッドにasyncを付けなくていいのだけど、
作っててちょっと良くわからない問題がおきたのであんまり使ってません…

 

参考: