.NET Freamwork4.7を使ったUDP通信をするためのコンポーネント作成

UDP通信をするために、お決まり文句を毎回書くのは面倒で嫌なので、ツールボックスから簡単にフォームにドロップするだけで使えるようコンポーネントとして作成しました。

作成方法

<< Visual Studio 2022にて新しいプロジェクトを作成する

新しいプロジェクトの作成でWindows フォームコントロールライブラリ(.NET Framework)を選ぶ。
プロジェクト名はとりあえずNonFmUDPControlとして作成する。
Timerコンポのようにデザイナの非表示領域に追加されるコントロールにする。(GUIはいらない)
そのために、最初からあるComponentクラスは使わないことにして新たに作成する。

    << 新しいコンポーネントクラスを作成する

    ソリューションエクスプローラーのプロジェクト名の所で「右クリック」→「追加」→「新しい項目」→「コンポーネントクラス」を選んでCS名をUDPCompo.csを作成する。
    プロジェクト作成のときに最初からあるUserControl.csファイルは削除する。
    新規作成したUDPCompo.cs[デザイン]も使わないのだが何もしないでそのままにしてよい。

    << 実際のUDPCompoクラスの記述

    以下本体。最初はコピペで結構です。まずは記述して動作確認してみましょう。

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Windows.Forms;
    
    namespace NonFmUDPControl
    {
        public partial class UDPCompo : Component
        {
            // 各種定義
    
            //受信メッセージ格納用
            public string rcvMsg = "";
    
            //送信用クライアント
            private UdpClient udpForSend = null;
            //受信用クライアント
            public UdpClient udpForReceive = null;
            // 文末追加かどうか
            private bool bottomAppend;
            //送信フラグ
            private bool SendBool = false;
    
            // デフォルト定数
            private const int rcvPort = 2020;//とりあえず用意する受信ポート番号
    
            //コンポと外部とのデータのやり取りに使うリッチテキストボックスを用意
            // 受信文字の出力先コンポ
            public RichTextBox rcvRichText;
            // 送信用入力のTextBox
            public RichTextBox sndRichText;
    
    
    
            // 以下 コンポ本体
    
            public UDPCompo()
            {
                InitializeComponent();
            }
    
            public UDPCompo(IContainer container)
            {
                container.Add(this);
    
                InitializeComponent();
            }
    
            /// <summary>
            /// 
            /// 初期化
            /// 
            /// コンポ内と外のリッチテキストボックスを結びつけて、UDP通信初期化 と 受信開始
            /// RichTextBoxはrefを付けて参照渡しにする必要がある
            /// 
            /// </summary>
            /// <param name="rv_textBox"> 受信の吐き出し先のリッチテキストボックス名 </param>
            /// <param name="tx_textBox"> 送信文字記入用リッチテキストボックス名 </param>
            /// <param name="btmAppend">  受信の吐き出しはリッチテキストボックス 最下行に追加(true)
            ///                                                               最上部に追加(false) </param>
            /// <param name="port_rcv"> 受信ポート デフォルトはrcvPort</param>
            /// 
            public bool Init(ref RichTextBox rv_textBox,
                             ref RichTextBox tx_textBox,
                             Boolean btmAppend = true,
                             int port_rcv = rcvPort)
            {
                //UDP設定(送受信用ポートを開きつつ受信用スレッドを生成)
                try
                {
                    rcvRichText = rv_textBox;
                    sndRichText = tx_textBox;
                    bottomAppend = btmAppend;
    
                    if (udpForReceive != null)
                    {
                        udpForReceive.Close();
                        udpForReceive = null;
                    }
                    udpForReceive = new UdpClient(port_rcv); //受信用ポート
                    udpForReceive.BeginReceive(ReceiveCallback, udpForReceive);//非同期的なデータ受信を開始する
    
                    return true;
                }
                catch
                {
                    return false;
                }
            }
    
            /// <summary>
            /// 
            /// 終了処理
            ///     FormClosing等で実施すること
            ///     
            /// </summary>
            public void End()
            {
                if (udpForReceive != null) udpForReceive.Close();
                if (udpForSend != null) udpForSend.Close();
            }
    
    
            public void Send(string remoteHost, int send_Port, int send_To, string sendMsg) //文字列を送信用ポートから送信先ポートに送信
            {
                try
                {
                    SendBool = true;
                    // 送信した文字RichTextBoxに送って表示する
                    ShowReceivedString("<<" + sendMsg);
    
                    byte[] sendBytes = Encoding.UTF8.GetBytes(sendMsg);
    
                    // 送信先の作成
                    IPEndPoint Ep = new IPEndPoint(IPAddress.Parse(remoteHost), send_To);
    
                    udpForSend = new UdpClient(send_Port);// 送信元のPORT指定
    
                    // send_Toへ送信!!
                    udpForSend.Send(sendBytes, sendBytes.Length, remoteHost, send_To);
                    // 送信閉じる
                    udpForSend.Close();
                }
                catch
                {
                    MessageBox.Show("通信開始がされていないため送信出来ません!", "お知らせ");
                }
    
            }
    
            //データを受信した時
            private void ReceiveCallback(IAsyncResult ar)
            {
                //非同期受信を一旦終了する
                IPEndPoint remoteEP = null;
    
                byte[] rcvBytes;
                try
                {
                    if (ar.AsyncState is UdpClient udp)
                    {
                        rcvBytes = udp.EndReceive(ar, ref remoteEP);
    
                        //データを文字列に変換する
                        string rcvMsgStr = System.Text.Encoding.UTF8.GetString(rcvBytes);
    
                        //受信したデータと送信者の情報をRichTextBoxに表示する
                        if (remoteEP != null)
                        {
                            string displayMsg = $"[{remoteEP.Address} ({remoteEP.Port})] > \r\n{rcvMsgStr}";
                            if (rcvRichText != null)
                            {
                                SendBool = false;
                                rcvRichText.BeginInvoke(
                                    new Action<string>(ShowReceivedString), displayMsg);
                            }
                        }
                        // 呼び出し文字列が送信されていたら音を出す
                        if (rcvMsgStr == "SEND_LOGON") ShowSoundOn();
    
                        //再びデータ受信を開始する
                        udp.BeginReceive(ReceiveCallback, udp);
    
                    }
                }
                catch (SocketException)
                {
                    MessageBox.Show("受信エラー");
                    return;
                }
                catch (ObjectDisposedException)
                {
                    //すでに閉じている時は何もしない
                    return;
                }
    
            }
    
            //RichTextBox1にメッセージを表示する
            public void ShowReceivedString(string str)
            {
                // それ以外の文字は表示する
                rcvRichText.SelectionLength = 0;//選択状態を解除しておく
    
                if (bottomAppend)// 最下行に追加なら
                {
                    if (SendBool)
                    {
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
    
                    rcvRichText.Focus();
                    rcvRichText.AppendText(str + "\r\n");
                    sndRichText.Focus();
                }
                else// 先頭に追加なら
                {
                    rcvRichText.Text = rcvRichText.Text.Insert(0, str + "\r\n");
    
                    if (SendBool)
                    {
                        int found = -1;
                        found = rcvRichText.Find(str, found + 1, RichTextBoxFinds.MatchCase);
                        rcvRichText.SelectionStart = found;
    
                        rcvRichText.SelectionLength = str.Length;
    
                        // そのまま表示では芸が無いので色を変える
                        rcvRichText.SelectionColor = Color.MediumSlateBlue;
                    }
                    else
                    {
                        rcvRichText.SelectionColor = Color.Black;
                    }
                }
            }
    
    
            /// <summary>
            /// 
            /// 以後、呼び出し音を再生する仕組み
            /// UDP通信とは関係ないが、相手の呼び出しに使う音源。いらないなら以下を削除して OK
            /// 
            /// 音源を用意するのが手間なのでWindowsのシステム音を使っている
            /// Flagsを使いビット演算できるよう、数値は被らないように値を設定してある。
            /// </summary>
    
            [Flags]
            public enum PlaySoundFlags : int
            {
                SND_SYNC = 0x0000,
                SND_ASYNC = 0x0001,
                SND_NODEFAULT = 0x0002,
                SND_MEMORY = 0x0004,
                SND_LOOP = 0x0008,
                SND_NOSTOP = 0x0010,
                SND_NOWAIT = 0x00002000,
                SND_ALIAS = 0x00010000,
                SND_ALIAS_ID = 0x00110000,
                SND_FILENAME = 0x00020000,
                SND_RESOURCE = 0x00040004,
                SND_PURGE = 0x0040,
                SND_APPLICATION = 0x0080
            }
            // DLL読み込み指定
            [System.Runtime.InteropServices.DllImport("winmm.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    
            private static extern bool PlaySound(string pszSound, IntPtr hmod, PlaySoundFlags fdwSound);
    
            // 呼び出し音を鳴らす
            public void ShowSoundOn()
            {
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
    
                /* <他の音の見本 参考まで>
                //メッセージ(情報)音を鳴らす
                PlaySound("SystemAsterisk", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //一般の警告音を鳴らす
                PlaySound(".Default", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(警告)音を鳴らす
                PlaySound("SystemExclamation", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //システムエラー音を鳴らす
                PlaySound("SystemHand", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //メッセージ(問い合わせ)音を鳴らす
                PlaySound("SystemQuestion", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの起動音を鳴らす
                PlaySound("SystemStart", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsの終了音を鳴らす
                PlaySound("SystemExit", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオン音を鳴らす
                PlaySound("WindowsLogon", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                //Windowsログオフ音を鳴らす
                PlaySound("WindowsLogoff", IntPtr.Zero,
                    PlaySoundFlags.SND_ALIAS | PlaySoundFlags.SND_NODEFAULT);
                */
    
            }
    
            //
            // ここまで音を再生する仕組み
            //
    
        }
    }
    

    記述が終了したら

    << ビルドしてDLLを作成

    ビルド実施。エラーが無ければツールボックスに登録出来るDLLが作成された。

    << ツールボックスへ登録

    登録の仕方は「ツール」→「ツールボックス アイテムの選択」を選び
    「.NET FreameWorkコンポーネント」のタブで同じ名前のコンポーネントが無いことをかくにんしてから「参照」ボタンを押し、先程作成したNonFmUDPControl.dllを指定すればツールボックスに表示するようになる。

    使い方

    UDP通信をするフォームに、このコンポーネントをドロップしてください。

    フォームに送信用と受信用の2つのRichTextBoxを貼り付け
    nonIF_UDPCompo1.Init(ref richTextBox1, ref richTextBox2, false, int.Parse(RevUDPName.Text));
    のようにコンポのInitにrichTextBoxを参照渡し(ref 必須)で初期化してください。
    richTextBox1は受信用のrichTextBox
    richTextBox2は送信用のrichTextBox
    次のfalseはrichTextBoxの先頭に受信文字が追加される、trueなら末尾に受信文字が追加される
    次の数値は受信用のポート番号です。無指定で2020になります。
    送信用のrichTextBoxは、内部フォーカスの移動に使い、それ以外では何もしていません。
    送信はSend(string remoteHost, int send_Port, int send_To, string sendMsg)を使って明示的に送信してください。
    End();で終了します。

    簡単なアプリは機会があれば後日掲載します。

    あとがき

    コードはGitHubあたりに載せれば良いのだろうけれど、特別な教育を受けているわけでも無い素人には何となく敷居が高いですねぇ….気まぐれで偶然来た方の参考になれば程度で良いのです。

    関連記事

    前回作成したUDP通信コンポーネントでチャットソフトを作ってみる

    C# Windowsショートカットをキーボード入力ではなくボタンのクリックで送る方法

    C# 既定のブラウザでURLを開けずエラーの対処

    QRCodeMaker(QRコード作成、読取りソフト)

    LinuxでUSB接続のストレージをsambaで共有する

    ホスト名(ドメイン)からIPアドレスを取得する方法 覚書