C# で UDP ってみた

必要だったので。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;

System.Net 名前空間System.Net.Sockets 名前空間と、 System.Threading 名前空間を追加してある。
ということでコンストラクタは華麗にスルーして次。

        /// <summary>
        /// ウィジェットの Text プロパティを設定するためのデリゲートです。
        /// </summary>
        /// <param name="s"></param>
        delegate void text_property_setter(string s);
        /// <summary>
        /// ウィジェットをメソッド内であれこれするためのデリゲートです。
        /// </summary>
        delegate void action();

Control#Invoke メソッドで使うためのデリゲート。
どっちのデリゲートも System 名前空間Action デリゲートとか使えばいいんだろうけど めんどいので新しく作った。
ということで、次のメソッドがそれぞれデリゲートの型をどーてらこーてらしてる。

        /// <summary>
        /// tbResult.Text に result を付け足します。
        /// </summary>
        /// <remarks>
        /// 呼び出し側が別スレッドで動いている場合、ウィジェット(コントロール)にアクセスすることはできないので、
        /// Invoke メソッドを使う。
        /// そのためのメソッド。
        /// </remarks>
        /// <param name="result"></param>
        private void result_append_to_textbox(string result) {
            StringBuilder text_builder = new StringBuilder( tbResult.Text );

            text_builder.AppendLine( result );
            tbResult.Text = text_builder.ToString();
        }


        /// <summary>
        /// 「送信」ボタンを有効にします。
        /// </summary>
        private void enable_send_button() {
            if ( !btnSend.Enabled )
                btnSend.Enabled = true;
        }

ということで、次は受信後のコールバック。

        /// <summary>
        /// 非同期受信に成功した場合に呼び出されます。
        /// </summary>
        /// <param name="ar"></param>
        private void async_receive_callback(IAsyncResult ar) {
            UDPRecord record = (UDPRecord)ar.AsyncState;

            if ( record.client.Client != null && record.client.Client.Connected ) {
                byte[] received_bytes = record.client.EndReceive( ar, ref record.endpoint );
                record.client.Close();

                tbResult.Invoke( new text_property_setter( result_append_to_textbox ),
                                 new object[] { Encoding.UTF8.GetString( received_bytes ) }
                                 );
            }

            btnSend.Invoke( new action( enable_send_button ) );
        }

この関数は、受信に成功した場合に呼びだされるよ。
で、送信側が送信しなかった場合は呼びだされない。
ようやくデータの送信に。

        /// <summary>
        /// 「送信」ボタンが押された場合に呼び出されます。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ergs"></param>
        private void btnSend_Click(object sender, EventArgs ergs) {
            int src_port_numer = Convert.ToInt32( tbSrcPortNumber.Text );
            int dst_port_number = Convert.ToInt32( tbDstPortNumber.Text );

            this.udp_sender_ = new UdpClient( src_port_numer );
            this.udp_sender_.DontFragment = true;
            this.udp_sender_.EnableBroadcast = true;

            try {
                // 送信先に接続します。
                this.udp_sender_.Connect( tbDstIpAddress.Text, dst_port_number );

                string send_text = string.Format( "<request><get version=\"{0}\">/</get><host>{1}</host></request>",
                                                  1.1,
                                                  tbDstIpAddress.Text
                                                  );

                // 送信データを UTF-8 でエンコードしてバイト配列にします。
                byte[] send_bytes = Encoding.UTF8.GetBytes( send_text );
                int sended_bytes_amount = this.udp_sender_.Send( send_bytes, send_bytes.Length );

                Thread.Sleep( 1 );

                if ( sended_bytes_amount > 0 ) {
                    IPEndPoint remote_endpoint = new IPEndPoint( IPAddress.Any, 0 );

                    //tbResult.Text = Encoding.UTF8.GetString( udp_sender.Receive( ref remote_endpoint ) );
                    IAsyncResult ar = this.udp_sender_.BeginReceive( async_receive_callback,
                                                                     new UDPRecord( this.udp_sender_,
                                                                                    remote_endpoint
                                                                                    )
                                                                     );
                    btnSend.Enabled = false;

                    udpSenderTimer.Start();

                } else {
                    this.udp_sender_.Close();
                }

            } catch ( SystemException se ) {
                MessageBox.Show( se.ToString(),
                                 se.Source,
                                 MessageBoxButtons.OK,
                                 MessageBoxIcon.Warning
                                 );
                this.udp_sender_.Close();
            }
        }

接続して、文字列からバイト列にエンコードしてー、送信の後、1 ミリ秒寝た後受信してる。
どんどん書くことが無くなってくけど気にしない方向で。
次はえーっと、送信したあとに受信出来るまで送信ボタンが無効になるんだけど、ずーっと受信出来ないとアプリを再起動させないといけないので、
5 秒くらいで送信ボタンを有効にして接続を閉じる処理。

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ergs"></param>
        private void udpSenderTimer_Tick(object sender, EventArgs ergs) {
            if ( this.tick_ > 50 ) {
                udpSenderTimer.Stop();
                this.udp_sender_.Close();

                MessageBox.Show( "応答がありませんでした。接続を閉じます。",
                                 this.Text,
                                 MessageBoxButtons.OK,
                                 MessageBoxIcon.Information
                                 );

                return;
            }
            ++this.tick_;
        }

ということで、全文はここに載ってるよ〜