こんにちは、ENGかぴです。
VB.NET(Visual Basic 2022)のデスクトップアプリでUDP通信を非同期受信を使って行う方法を紹介します。非同期受信のためアプリ受信待ちで固まることなく他の処理が行えます。
以下はVisual Basic 2022をVB2022とします。
非同期受信ではなくタスクを生成してUDP通信を行う方法を下記記事にまとめています。
VB.NET(VB2022)のUDP通信をタスク処理で行う方法
UDP通信を行う外部機器として下記記事のArduino UNO(以下Arduinoとする)を使用しています。Arduinoから送信されるUDP通信パケットを受信して表示します。
ArduinoのEthernetライブラリでUDPクライアントを実装する
VB.NET(VB2022)のデスクトップアプリで動作確認したことを下記リンクにまとめています。
VB.NET(VB2022)のデスクトップアプリ開発でできること
UDP通信を確認するアプリ
![本記事で作成するアプリの動作画面](https://smtengkapi.com/wp-content/uploads/2024/05/add01ce8cffc98e7820b081a30637bb3-3.jpg)
UDP通信を行う接続先のIPアドレスとポートを入力して「接続」ボタンをクリックするとUDP通信の接続を行い、「送信」ボタンで横のテキストボックスに入力した文字列を送信します。
接続先からパケットを受信すると受信したパケットデータをテキストボックスに表示して確認できるようにします。
コントロールの実装
各種コントロールの追加方法を下記記事にまとめています。
VB.net(VB2022)のボタンの実装とイベントの追加方法
VB.NET(VB2022)のTextBoxの実装とイベントの追加方法
VB.NET(VB2022)のタイマーの実装とイベントの追加方法
ツールボックスからコントロールを追加し、一部のプロパティを変更します。
![コントロールの追加](https://smtengkapi.com/wp-content/uploads/2024/05/3761835d69b6c31b1384e1753a82316f.jpg)
コントロールのサイズとロケーションは任意で問題ありませんが入力する文字が確認できる最低限のサイズを指定することを推奨します。
GroupBoxは複数のコントロールをまとめて管理する場合に使用します。GroupBox3を移動するとGroupBox3内のすべてのコントロールが同時に移動します。
GroupBoxに内部のコントロールはグループ化しているGroupBoxの左上端がロケーションの基準になります。例えばGroupBox2はGroupBox3の内部に配置しているためロケーションはGroupBox3の左上端になります。
GroupBox2の内部に配置したTextBox1~TextBox4はGroupBox2の左上端がロケーションの基準になりGroupBox2を移動すると同時に移動します。
GroupBox1のTextプロパティを「接続先」、GroupBox2のTextプロパティを「IPアドレス」、GroupBox3のTextプロパティを「操作盤」、GroupBox4~5のTextプロパティを「ポート」、GroupBox6のTextプロパティを「接続元」に変更します。
Button1はUDP接続に使用します。Textプロパティを「接続」に変更し、BackColorプロパティをWebタブからLightGreenに変更します。Button2はUDP接続の切断に使用します。Textプロパティを「切断」に変更し、BackColorプロパティをWebタブからLightBlueに変更します。
Button3はTextBox7のテキストのクリアに使用します。Textプロパティを「テキストクリア」に変更し、BackColorプロパティをWebタブからLightGreenに変更します。
変数の宣言
アプリで共通する変数を宣言する場合はフォームクラス内で変数を宣言する必要があります。VBの場合はDimでメモリに変数を割り当てます。
Imports System.Net
Imports System.Net.NetworkInformation
Imports System.Net.Sockets
Imports System.Text
Public Class Form1
Dim ClientUdp As UdpClient
End Class
Form1クラス直前でインポート(Imports)しているものはネームスペースを使用する意味です。7行目のClientUdpはSystem.Net.SocketsネームスペースのUdpClientクラスでインスタンス化する宣言になります。インポートしない場合はSystem.Net.Socketsと記述する必要がありますが、名前の簡素化に従って省略するとネームスペースが自動で追加されます。
例ではアプリ全体で使用するClientUdpをインスタンス化しています。クラス内で宣言した場合はアプリを終了しない限りメモリを割り当てた状態になります。ClientUdpはUDP接続やUDPパケットの送受信に使用します。
イベントの追加
コントロールに対してイベントを追加します。エディターを選択している状態でF7を押すか、右クリックで表示されるメニュー「コードの表示(C)」を選択するとコードの編集画面(Form1.vb)に遷移します。次のイベントを追加します。
- フォームを呼び出したときに一度だけ処理するLoadイベント
- Button1~3をクリックしたときに呼び出すClickイベント(UDP接続はUDP通信を行う方法参照)
- ComboBox1で選択したインデックス番号を取得するSelectedIndexChangedイベント
- ComboBox1をクリックしたときに呼び出すMouseClickイベント
1~3のイベントはフォームをダブルクリックすると追加されます。4のイベントはコードエディターで追加します。
![MouseClickイベントを追加する方法](https://smtengkapi.com/wp-content/uploads/2024/05/52e247b14b30ab7664c91642d2cd5aed.jpg)
コントロール選択でComboBox1を選択します。コントロール選択の横にあるイベント一覧からMouseClickを選択するとイベントが追加できます。イベントを選択すると自動でイベントの追加されます。
Private Sub ComboBox1_MouseClick(sender As Object, e As EventArgs) Handles ComboBox1.MouseClick
ComboBoxInit()
End Sub
自動生成されたサブルーチンの後方にHandles ComboBox1.MouseClickのように記述されている部分がイベントの発生要因になります。生成したイベントのサブルーチンに処理を追加します。例ではComboBoxを初期化する処理を追加しています。
UDP通信を行う方法
Button1をクリックしたときにUDP接続を行う処理を追加します。またButton2をクリックしたときはUDP接続を切断する処理を追加します。接続先からUDP通信のパケットを受信する際に使用するエンドポイント(パソコンなど)の指定に下記記事で取得したIPアドレスを使用します。
VB.NET(VB2022)でLANのIPアドレスを取得する方法
Button1とButton2は連動して動作するようにします。ButtonのBackColorプロパティがLightGreenであれば操作可能であることを示し、LightBlueであれば操作中であることを示します。UDP接続が確立している状態ではButton1のBackColorプロパティがLightBlueになります。
UDP接続を行う
System.Net.Socketsのネームスペースを使用します。TextBoxに入力したIPアドレスやポートを使用して接続先と接続を行います。Button1のクリックイベントに処理を追加します。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim remoteIp As String = "" '接続先のIPアドレスを生成
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)
ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp)
Timer1.Enabled = True
Button1.BackColor = Color.LightBlue
Button2.BackColor = Color.LightGreen
Catch ex As Exception
End Try
End If
End Sub
remoteIpは接続先のIPアドレスの文字列の生成に使用します。「192.168.11.100」のような文字列を生成するため文字列の&を使用して文字列を生成しています。localPortはUDP接続に使用する自局のポート番号です。remotePortは接続先で使用するポート番号です。
UdpClientクラスをローカルポートを指定し初期化してインスタンス化します。インスタンス化したClientUdpを使用してUDP通信の管理を行います。
接続に失敗した時アプリが強制終了しないようにTry Catch文で処理を行います。
Connect()メソッドで接続要求を出します。第1引数に接続先のIPアドレス、第2引数に接続先のポート番号を指定します。IPアドレスはホスト名を指定する方法もあります。
Connect()メソッドで接続が確立すると送信ができるようになります。受信は別途Receive()メソッドやBeginReceive()メソッドで待機する必要があります。本記事では非同期通信で受信するためBeginReceive()メソッドを使用します。
BeginReceive()メソッドは第1引数にコールバック関数を指定します。AddressOfで非同期でコールバックするAsyncCallback型のプロシージャ(サブルーチンを呼び出し可能にしたもの)を生成してサブルーチンを指定しています。第2引数にUDP接続に使用するオブジェクトを指定します。
Private Sub ReceiveCallback(AR As IAsyncResult)
Dim i As Integer
Try
' ソケット受信
Dim loc_ip As New IPEndPoint(localIp, localPort)
Dim getByte() As Byte = ClientUdp.EndReceive(AR, loc_ip)
For i = 0 To getByte.Length - 1
RxRingSet(getByte(i))
Next
' ソケット非同期受信(System.AsyncCallback)
ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp)
Catch ex As Exception
End Try
End Sub
IPEndPointクラスをローカルのIPアドレスとポートを指定しインスタンス化します。EndReceive()メソッドで非同期受信を停止します。第1引数に非同期操作の状態を格納する変数を指定します。第2引数にエンドポイントを指定します。指定したエンドポイントで受信が成功すると配列のデータが戻り値になります。
配列のデータをアプリで共通の配列に格納し、TextBox7の表示用に使用します。
サブルーチンを抜ける前にBeginRecive()メソッドで停止した非同期受信を再開します。BeginRecive()メソッドを再開しない場合は受信のメソッドが開始していないため受信処理ができなくなります。
PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
UDP接続を切断する
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If Button2.BackColor = Color.LightGreen Then
'//クライアントが存在しているか
If ClientUdp IsNot Nothing Then
ClientUdp.Close()
Button2.BackColor = Color.LightBlue
Button1.BackColor = Color.LightGreen
End If
End If
End Sub
Button2をクリックした時にUDP接続のクライアントが存在している場合、Close()メソッドで切断の処理を行います。
クライアントが存在しない場合にClose()メソッドを使用するとアプリが強制終了するため、クライアントが存在を確認してから切断処理を行います。
UDPパケットを送信する
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
Button3をクリックした時にUDP接続のクライアントが存在している場合、TextBox6に入力している文字列を「UTF-8」でエンコーディングしてSend()メソッドで送信します。第1引数に送信するデータのアドレス(配列)、第2引数に送信するデータのサイズを指定します。
クライアントが存在しない場合にSend()メソッドを使用するとアプリが強制終了するため、クライアントが存在を確認してから切断処理を行います。
動作確認(デバッグ)
![動作確認の全体構成](https://smtengkapi.com/wp-content/uploads/2024/05/e984bb3a3478aaf71841377406e08920-1.jpg)
パソコンと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に表示します。
動作確認のためデバッグを行います。デバッグの開始はエディタ上部の「▶開始」をクリックすると開始します。
![動作確認の結果](https://smtengkapi.com/wp-content/uploads/2024/05/add01ce8cffc98e7820b081a30637bb3-2.jpg)
デバッグを開始するとComboBox1に使用可能なイーサーネットが表示されています。接続先グループのTextBoxにArduinoのIPアドレス及びポートを指定します。Arduinoはポート10002を指定しているので接続元のポートは10002になります。
「接続」ボタンをクリックするとUDP接続を行います。接続に成功するとArduinoから受信したUDPパケットのデータをTextBox7に表示します。TextBox7に湿度と温度のデータが表示されているのでUDPパケットが受信できていることが分かります。
次に、パソコンからArduinoにUDPパケットのデータを送信します。TextBox6に「あいうえお」を入力して「送信」ボタンをクリックします。
![Arduinoのシリアルモニターの結果](https://smtengkapi.com/wp-content/uploads/2024/05/87c212c1661d74ff04c2a6c874c99f54.jpg)
Arduinoのシリアルモニターを確認するとパソコンのIPアドレス及び文字列で「あいうえお」が表示されておりUDPパケットが受信できていることが分かります。
ComboBox1のMouseClickイベントの確認のため外付けのUSBハブを追加で接続するとComboBox1をクリックしたときに候補が更新されることも確認しています。
コードのデバッグを行う場合は、コードエディタでブレークポイントを置くことで一時的にプログラムを停止させることができます。
![ブレークポイントでデバッグする方法](https://smtengkapi.com/wp-content/uploads/2024/02/4e37b9e1468903a743690b93eacb9d8e.jpg)
ブレークポイントはコードエディターの最左部分をクリックして●マークが表示されれば設置できています。例では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 UdpCloseFlg As Boolean = False
Dim ClientUdp As UdpClient
Dim LocalNet() As NET_INTERFACE
Dim localIp As IPAddress
Dim localPort As Integer
Dim remotePort As Integer
Dim buf(100) As Byte
Dim lifcnt As Integer
Dim RxRing As TYP_COMRING '受信したデータを管理
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) 'UDP接続
ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp)'受信待ちの発行
Timer1.Enabled = True
Button1.BackColor = Color.LightBlue
Button2.BackColor = Color.LightGreen
Catch ex As Exception
End Try
End If
End Sub
Private Sub ReceiveCallback(AR As IAsyncResult)
Dim i As Integer
Try
' ソケット受信
Dim loc_ip As New IPEndPoint(localIp, localPort)
Dim getByte() As Byte = ClientUdp.EndReceive(AR, loc_ip)
For i = 0 To getByte.Length - 1
RxRingSet(getByte(i))
Next
' ソケット非同期受信(System.AsyncCallback)
ClientUdp.BeginReceive(AddressOf ReceiveCallback, ClientUdp)
Catch ex As Exception
End Try
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
'//クライアントのインスタンスが有ったら
If ClientUdp IsNot Nothing Then
ClientUdp.Close()
Button2.BackColor = Color.LightBlue
Button1.BackColor = Color.LightGreen
End If
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言語開発環境の作り方までをまとめています。
PR:無料トライアル実施中【PC専用】AIスライド資料作成ツールの利用:イルシル
最後まで、読んでいただきありがとうございました。
Send()メソッドを使用すると直ぐにパケットの送信が始まります。余程大量のデータを送信しない限りアプリが待機して固まる可能性が低いため、非同期送信のSendAsync()メソッドを使用する頻度が少ない印象です。