PR

VB.NET(VB2022)のUDP通信をタスク処理で行う方法

VB.NET
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

VB.NET(Visual Basic 2022)のデスクトップアプリでUDP通信をタスクを生成して受信する方法を紹介します。アプリのメイン処理と別のタスクを生成して受信処理を行うためアプリ受信待ちで固まることなく処理が行えます。

UDP通信を非同期受信で行う方法を下記記事で紹介していますが、本記事は同期受信をタスクを生成して同期受信する方法をまとめています。以下はVisual Basic 2022をVB2022とします。

VB.NET(VB2022)のUDP通信を非同期処理で行う方法

外部機器として下記記事のArduino UNO(以下Arduinoとする)を使用しています。Arduinoから送信されるUDP通信パケットを受信して表示します。

ArduinoのEthernetライブラリでUDPクライアントを実装する

VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。

VB.NET(VB2022)のデスクトップアプリ開発でできること

UDP通信を確認するアプリ

本記事で作成するアプリの動作画面
本記事で作成するアプリの動作画面

UDP通信を行う接続先のIPアドレスとポートを入力して「接続」ボタンをクリックするとUDP通信の接続を行い、「送信」ボタンで横のテキストボックスに入力した文字列を送信します。

接続先からパケットを受信すると受信したパケットデータをテキストボックスに表示して確認できるようにします。コントロールの実装やイベントの追加は下記記事と同様です。

VB.NET(VB2022)のUDP通信を非同期処理で行う方法

各種コントロールの配置とデザイン名は以下の通りです。

コントロールの配置とデザイン名
コントロールの配置とデザイン名

タスク処理で使用する変数について説明します。

Imports System.Net
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Text

Public Class Form1
    Dim cts As Threading.CancellationTokenSource
End Class

Form1クラス直前でインポート(Imports)しているものはネームスペースを使用する意味です。7行目のctsはSystem.ThreadingネームスペースのCancellationTokenSourceクラスでインスタンス化する宣言になります。

例ではアプリ全体で使用するctsをインスタンス化しています。クラス内で宣言した場合はアプリを終了しない限りメモリを割り当てた状態になります。ctsはUDP接続の受信待ちのタスクをキャンセルする場合に使用します。

スポンサーリンク

UDP通信をタスクを使って行う方法

Button1をクリックするとUDP接続と受信待ちのタスクの生成を行います。またButton2をクリックすると受信待ちのタスクのキャンセルとUDP接続を切断します。

タスク処理ではUDP受信のエンドポイント(パソコンのイーサネットのIPアドレス及び受信ポート)の指定に下記記事で取得したIPアドレスを使用します。

VB.NET(VB2022)でLANのIPアドレスを取得する方法

広告

UDP接続を行う

System.Net.Socketsのネームスペースを使用します。TextBoxに入力したIPアドレスやポートを使用して接続先と接続を行います。Button1のクリックイベントに処理を追加します。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
       Dim remoteIp As String = ""

       If Button1.BackColor = Color.LightGreen Then
           remoteIp = TextBox1.Text & "." &
                      TextBox2.Text & "." &
                      TextBox3.Text & "." &
                      TextBox4.Text

           localPort = TextBox8.Text
           remotePort = TextBox5.Text

           Try
               ClientUdp = New UdpClient(localPort)’ポートを指定して初期化
               ClientUdp.Connect(remoteIp, remotePort)
       'タスクの生成
               Task.Run(Sub()
                            RecvWait()
                        End Sub)
           Catch ex As Exception

           End Try
       End If
End Sub

remoteIpは接続先のIPアドレスの文字列の生成に使用します。「192.168.11.2」のような文字列を生成するため文字列の&を使用して文字列を生成しています。localPortはUDP接続に使用する自局のポート番号です。remotePortは接続先で使用するポート番号です。

UdpClientクラスをローカルポートを指定し初期化してインスタンス化します。インスタンス化したClientUdpを使用してUDP通信の管理を行います。

接続に失敗した時アプリが強制終了しないようにTry Catch文で処理を行います。

Connect()メソッドで接続要求を出します。第1引数に接続先のIPアドレス、第2引数に接続先のポート番号を指定します。IPアドレスはホスト名を指定する方法もあります。

Connect()メソッドで接続が確立すると送信ができるようになります。受信は非同期のタスクを生成して別のスレッドで行います。タスクはSystem.Threading.tasks.taskクラスのRun()メソッドの引数に処理を追加して生成します。例では自作のRecvWait()関数に遷移するようにしています。

  Private Sub RecvWait()
      Dim remoteIp As String = ""

      Invoke(
          Sub()
              TextBox7.Text = "スタート "
          End Sub
      )
      taskstop = False
      Dim getByte() As Byte
      cts = New Threading.CancellationTokenSource()

      While cts.Token.IsCancellationRequested = False
          Try
              Dim loc_ip As IPEndPoint = New IPEndPoint(IPAddress.Loopback, localPort)
              getByte = ClientUdp.Receive(loc_ip)

              For i = 0 To getByte.Length - 1
                  RxRingSet(getByte(i))
              Next
          Catch ex As Exception

          End Try
      End While

      Invoke(
          Sub()
              TextBox7.Text &= "接続停止"
          End Sub
      )
  End Sub

RecvWait()関数はメイン処理とは別のタスク(スレッド)で動作します。そのためメイン処理のコントロールに直接アクセスすることができません。メイン処理のコントロールにアクセスする場合はInvoke()メソッドで生成したデリゲート(メイン処理とスレッドを繋げる門)を使用します。例ではTextBox7のTextプロパティに「スタートと「接続停止」」を指定しています。

Threading.CancellationTokenSource()メソッドでタスクのキャンセルに使用する変数(cts)をインスタンス化します。

タスクをキャンセルせずに次の接続を行うと終了していないタスクが残ってしまい不要なタスクが生成されたままになってしまいます。

UDPパケットの受信はWhile処理で繰り返して行います。タスクがキャンセルされない限りループを抜けずにUDPパケットの受信を行うようにします。タスクのキャンセルやソケットの消失でアプリが強制終了しないようにTry catch文を使用して同期受信を行います。

IPEndPointクラスをローカルのIPアドレスとポートを指定しインスタンス化し、Receive()メソッドの引数にエンドポイントを指定すると同期受信スタートします。UDPパケットを受信すると配列にデータが格納されます。

配列のデータをアプリで共通の配列に格納し、TextBox7の表示用に使用します。

Receive()メソッドは同期受信するためメイン処理で使用するとアプリが占有されフリーズしたように固まってしまいますが、非同期のタスクで同期受信させるためメイン処理に影響を与えることなくUDPデータを受信することができます。

広告

タスクをキャンセルと接続切断

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    If Button2.BackColor = Color.LightGreen Then
        cts.Cancel() 'タスクをキャンセル
        ClientUdp.Close()
    End If
End Sub

Button2はタスクのキャンセルとUDP接続の切断を行います。タスクのキャンセルはインスタンス化したctsのCancel()メソッドを使用します。

ctsのTokenプロパティはキャンセルの要求を受け付けるとIsCancellationRequestedがTrueになります。タスクの同期受信はタスクのキャンセルされない限り繰り返すようにしていますが、キャンセル要求によりTrueになるとループから抜け出しText7のTextプロパティに「接続停止」を表示します。

UdpClientのClose()メソッドでUDP接続を切断します。

スポンサーリンク

動作確認(デバッグ)

動作確認の全体構成
動作確認の全体構成

パソコンとArduino UNO+Ehternet Shield Rev2をLANケーブル(クロス)で接続します。ArduinoのIPアドレスを192.168.11.2にしているのでパソコンのLANポートを同じクラスのIPアドレスとして192.168.11.100にしています。パソコン及びArduinoのLANポートは10002を使用します。

Arduinoは電源を入れると5秒間隔でUDPパケットを送信します。アプリでArduinoのIPアドレスとポートを指定して接続するとArduinoから受信したUDPパケットのデータをTextBox7に表示します。

動作確認のためデバッグを行います。デバッグの開始はエディタ上部の「▶開始」をクリックすると開始します。

動作確認の結果
動作確認の結果

デバッグを開始するとComboBox1に使用可能なイーサーネットが表示されています。接続先グループのTextBoxにArduinoのIPアドレス及びポートを指定します。Arduinoはポート10002を指定しているので接続元のポートは10002になります。

デバッグを繰り返し行う場合TextBoxのTextプロパティに直接IPアドレスやポート番号を指定しておくと入力する手間が省けるためスムーズにデバッグが行えます。

「接続」ボタンをクリックするとUDP接続を行います。接続に成功するとTextBox7にタスクが生成されたことを示す「スタート」を表示します。Arduinoからデータを受信するとパケットのデータを表示します。

タスクの生成を確認
タスクの生成を確認

UDP接続を行った後でブレークなどでアプリを一時停止するとエディターのタスクの欄に生成中のタスクが表示されます。タスクをキャンセルしない場合は不要なタスクが残ったままになりますが、キャンセルできているのでタスクが1つになっています。

「切断」ボタンをクリックするとタスクをキャンセルし「接続停止」を表示します。同時にUDP接続を切断します。

TextBox7に湿度と温度のデータが表示されているのでUDPパケットが受信できていることが分かります。

コードのデバッグを行う場合は、コードエディタでブレークポイントを置くことで一時的にプログラムを停止させることができます。

ブレークポイントでデバッグする方法
ブレークポイントでデバッグする方法

ブレークポイントはコードエディターの最左部分をクリックして●マークが表示されれば設置できています。例ではButton1がクリックされた時に発生したイベントの先頭部分にブレークポイントを置いています。この状態でButton1をクリックするとブレークポイントでプログラムが一時停止します。

F11を押すとステップ実行になるため1行ずつ内容を確認しながらデバッグを行うことができます。上部の「▶続行(C)」をクリックするとブレークポイント状態から通常の動作に復帰します。

PR:(即戦力のスキルを身に着ける:DMM WEBCAMP 学習コース(はじめてのプログラミングコース))

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

Imports System.Net
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Text

Public Class Form1

    Structure NET_INTERFACE
        Dim name As String
        Dim localip As IPAddress
    End Structure

    Const RXRGSZ As Integer = 256

    Structure TYP_COMRING
        Dim rp As Integer
        Dim wp As Integer
        Dim dat() As Byte
    End Structure

    Dim ClientUdp As UdpClient
    Dim LocalNet() As NET_INTERFACE
    Dim localIp As IPAddress
    Dim localPort As Integer
    Dim remotePort As Integer
    Dim RxRing As TYP_COMRING '受信したデータを管理
    Dim cts As Threading.CancellationTokenSource

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim remoteIp As String = ""

        If Button1.BackColor = Color.LightGreen Then
            remoteIp = TextBox1.Text & "." &
                       TextBox2.Text & "." &
                       TextBox3.Text & "." &
                       TextBox4.Text

            localPort = TextBox8.Text
            remotePort = TextBox5.Text

            Try
                ClientUdp = New UdpClient(localPort)
                ClientUdp.Connect(remoteIp, remotePort)

                Task.Run(Sub()
                             RecvWait()
                         End Sub)

                Timer1.Enabled = True
                Button1.BackColor = Color.LightBlue
                Button2.BackColor = Color.LightGreen
            Catch ex As Exception

            End Try

        End If
    End Sub

    Private Sub RecvWait()
        Dim remoteIp As String = ""

        Invoke(
            Sub()
                TextBox7.Text = "スタート "
            End Sub
        )

        Dim getByte() As Byte
        cts = New Threading.CancellationTokenSource()

        While cts.Token.IsCancellationRequested = False
            Try
                Dim loc_ip As IPEndPoint = New IPEndPoint(localPort, localPort)
                getByte = ClientUdp.Receive(loc_ip)

                For i = 0 To getByte.Length - 1
                    RxRingSet(getByte(i))
                Next
            Catch ex As Exception

            End Try
        End While

        Invoke(
            Sub()
                TextBox7.Text &= "接続停止"
            End Sub
        )
    End Sub

    Private Sub GetNetworkList()
        Dim netinterface = NetworkInterface.GetAllNetworkInterfaces
        Dim wk(netinterface.Length - 1) As NET_INTERFACE
        Dim i As Integer
        Dim j As Integer = 0
        Dim cnt As Integer = 0

        For Each netin In netinterface
            If netin.OperationalStatus = OperationalStatus.Up AndAlso
               netin.NetworkInterfaceType = NetworkInterfaceType.Ethernet Then

                wk(i).name = netin.Name
                cnt += 1

                Dim ipprperties As IPInterfaceProperties = netin.GetIPProperties

                If ipprperties IsNot Nothing Then
                    For Each ipadrs In ipprperties.UnicastAddresses
                        wk(i).localip = ipadrs.Address
                    Next
                End If
            End If

            i += 1
        Next

        ReDim LocalNet(cnt - 1)

        For i = 0 To wk.Length - 1
            If wk(i).name <> "" Then
                LocalNet(j) = wk(i)
                j += 1
            End If
        Next

    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ComboBoxInit()
        RxRingInit()
    End Sub

    Private Sub ComboBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles ComboBox1.MouseClick
        ComboBoxInit()
    End Sub

    Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
        localIp = LocalNet(ComboBox1.SelectedIndex).localip
    End Sub

    Private Sub ComboBoxInit()
        Dim i As Integer
        Dim str As String = ""

        GetNetworkList()
        ComboBox1.Items.Clear()

        For i = 0 To LocalNet.Length - 1
            str = LocalNet(i).name & " " & "IP:" & LocalNet(i).localip.ToString
            ComboBox1.Items.Add(str)
        Next

    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        Dim data() As Byte = Encoding.GetEncoding("utf-8").GetBytes(TextBox6.Text)

        If ClientUdp IsNot Nothing Then
            Try
                ClientUdp.Send(data, data.Length)
            Catch ex As Exception

            End Try
        End If

    End Sub

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Dim sz As Integer
        Dim i As Integer

        sz = RxRing.wp - RxRing.rp

        If sz < 0 Then
            sz += RxRing.dat.Length
        End If

        If sz > 0 Then
            For i = 0 To sz - 1
                TextBox7.Text &= Chr(RxRing.dat(RxRing.rp))
                RingRpAdd()
            Next

        End If

    End Sub

    'RxRingの初期化を行う
    Private Sub RxRingInit()
        RxRing.wp = 0
        RxRing.rp = 0
        ReDim RxRing.dat(RXRGSZ - 1)
    End Sub

    'RxRingのwpを更新する
    Public Sub RxRingSet(dat As Byte)

        RxRing.dat(RxRing.wp) = dat

        RxRing.wp += 1
        If RxRing.wp = RxRing.dat.Length Then
            RxRing.wp = 0
        End If
    End Sub

    'RxRingのrpを更新する
    Private Sub RingRpAdd()

        RxRing.rp += 1
        If RxRing.rp >= RxRing.dat.Length Then
            RxRing.rp = 0
        End If

    End Sub

    Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
        TextBox7.Clear()
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        If Button2.BackColor = Color.LightGreen Then
            cts.Cancel() 'タスクをキャンセル
            ClientUdp.Close()
            Button2.BackColor = Color.LightBlue
            Button1.BackColor = Color.LightGreen
        End If
    End Sub
End Class

TextBox1~4、5、8は数値以外の文字列を受け付けるため数値以外の入力を許可しないようにKeyPressイベントを追加実装しても良いと思います。KeyPressイベントについては下記記事を参考にしてください。

VB.NET(VB2022)のTextBoxの実装とイベントの追加方法

コントロールの番号やイベントハンドラーなどのプロパティ名などを変更している場合はソースコードのイベントハンドラーをお使いのイベントハンドラーに置き換えてください。

関連リンク

VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。

VB.NET(VB2022)のデスクトップアプリ開発でできること

マイクロソフトはVisual Studio以外にもVSCodeという便利なエディターを提供しています。下記リンクではVSCodeのインストールの仕方からC言語開発環境の作り方までをまとめています。

VSCodeをインストールしてC/C++の開発環境を作る

PR: 未経験OK、20代の理系に特化した就職、転職サービス UZUZ(ウズウズ)

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました