ここでは、UDP(User Datagram Protocol)を使ってデータの送受信を行う方法について、基本的な事柄を説明します。
UDPはTCPのように接続の確立の必要がなく、相手にデータを一方的に送りつけます。そのため、サーバーも必要ありません。
.NET FrameworkでUDPを扱うには、UdpClientクラスを使用すると便利です。このクラスを使用すると、Socketクラスを直接使用するより、多少簡単になります。
以下にUdpClientクラスを使ってデータを送受信するサンプル(コンソールアプリケーション)を示します。データを受信するだけのアプリケーションと、データを送信するだけのアプリケーションの2つに分かれています。
まずは、データを受信するだけのアプリケーションのサンプルです。受信したデータ受信したデータをすべてコンソールに出力しています。「exit」という文字列を受信すると、受信を終了します。
Public Class UdpReceiver Public Shared Sub Main() 'バインドするローカルIPとポート番号 Dim localIpString As String = "127.0.0.1" Dim localAddress As System.Net.IPAddress = _ System.Net.IPAddress.Parse(localIpString) Dim localPort As Integer = 2002 'UdpClientを作成し、ローカルエンドポイントにバインドする Dim localEP As New System.Net.IPEndPoint(localAddress, localPort) Dim udp As New System.Net.Sockets.UdpClient(localEP) While True 'データを受信する Dim remoteEP As System.Net.IPEndPoint = Nothing Dim rcvBytes As Byte() = udp.Receive(remoteEP) 'データを文字列に変換する Dim rcvMsg As String = System.Text.Encoding.UTF8.GetString(rcvBytes) '受信したデータと送信者の情報を表示する Console.WriteLine("受信したデータ:{0}", rcvMsg) Console.WriteLine("送信元アドレス:{0}/ポート番号:{1}", _ remoteEP.Address, remoteEP.Port) '"exit"を受信したら終了 If rcvMsg.Equals("exit") Then Exit While End If End While 'UdpClientを閉じる udp.Close() Console.WriteLine("終了しました。") Console.ReadLine() End Sub End Class
using System; public class UdpReceiver { static void Main() { //バインドするローカルIPとポート番号 string localIpString = "127.0.0.1"; System.Net.IPAddress localAddress = System.Net.IPAddress.Parse(localIpString); int localPort = 2002; //UdpClientを作成し、ローカルエンドポイントにバインドする System.Net.IPEndPoint localEP = new System.Net.IPEndPoint(localAddress, localPort); System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient(localEP); for (; ; ) { //データを受信する System.Net.IPEndPoint remoteEP = null; byte[] rcvBytes = udp.Receive(ref remoteEP); //データを文字列に変換する string rcvMsg = System.Text.Encoding.UTF8.GetString(rcvBytes); //受信したデータと送信者の情報を表示する Console.WriteLine("受信したデータ:{0}", rcvMsg); Console.WriteLine("送信元アドレス:{0}/ポート番号:{1}", remoteEP.Address, remoteEP.Port); //"exit"を受信したら終了 if (rcvMsg.Equals("exit")) { break; } } //UdpClientを閉じる udp.Close(); Console.WriteLine("終了しました。"); Console.ReadLine(); } }
次はデータを送信するサンプルです。コンソールに文字列を入力してエンターキーを押すと、データを送信します。「exit」と入力するまで繰り返しデータを送信できます。上記受信専用アプリケーションを起動後、実行してください。
Public Class UdpSender Public Shared Sub Main() 'データを送信するリモートホストとポート番号 Dim remoteHost As String = "127.0.0.1" Dim remotePort As Integer = 2002 'UdpClientオブジェクトを作成する Dim udp As New System.Net.Sockets.UdpClient() While True '送信するデータを作成する Console.WriteLine("送信する文字列を入力してください。") Dim sendMsg As String = Console.ReadLine() Dim sendBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(sendMsg) 'リモートホストを指定してデータを送信する udp.Send(sendBytes, sendBytes.Length, remoteHost, remotePort) 'または、 'udp = New UdpClient(remoteHost, remotePort) 'として、 'udp.Send(sendBytes, sendBytes.Length) '"exit"と入力されたら終了 If sendMsg.Equals("exit") Then Exit While End If End While 'UdpClientを閉じる udp.Close() Console.WriteLine("終了しました。") Console.ReadLine() End Sub End Class
using System; public class UdpSender { static void Main() { //データを送信するリモートホストとポート番号 string remoteHost = "127.0.0.1"; int remotePort = 2002; //UdpClientオブジェクトを作成する System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient(); for (; ; ) { //送信するデータを作成する Console.WriteLine("送信する文字列を入力してください。"); string sendMsg = Console.ReadLine(); byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(sendMsg); //リモートホストを指定してデータを送信する udp.Send(sendBytes, sendBytes.Length, remoteHost, remotePort); //または、 //udp = new UdpClient(remoteHost, remotePort); //として、 //udp.Send(sendBytes, sendBytes.Length); //"exit"と入力されたら終了 if (sendMsg.Equals("exit")) { break; } } //UdpClientを閉じる udp.Close(); Console.WriteLine("終了しました。"); Console.ReadLine(); } }
データを受信する時、すべてのIPアドレスをListenする方法は、「TCPクライアント・サーバープログラムを作成する」で説明している方法とほぼ同じです。
上記の例で「localAddress」を「IPAddress.Any」とすると、利用可能な全てのIPv4アドレスをListenします。または、UdpClientコンストラクタを呼び出す時、ポート番号だけを渡す(つまり、「UdpClient(localPort)」とする)か、ポート番号とInterNetworkを渡す(つまり、「UdpClient(localPort, AddressFamily.InterNetwork)」とする)ことでも同じことができます。
上記の例で「localAddress」を「IPAddress.IPv6Any」とすると、利用可能な全てのIPv6アドレスをListenします。または、UdpClientコンストラクタを呼び出す時、ポート番号とInterNetworkV6を渡す(つまり、「UdpClient(localPort, AddressFamily.InterNetworkV6)」とする)ことでも同じことができます。
IPv4とIPv6の両方を可能にするには、IPAddress.IPv6Anyを使用し、さらにSocket.SetSocketOptionメソッドでIPv6Onlyを0にします。そして、その後バインドを行います。
以下にその例を示します。UdpClientを作成して、バインドするまでの部分のみです。
Dim udp As New System.Net.Sockets.UdpClient( _ System.Net.Sockets.AddressFamily.InterNetworkV6) udp.Client.SetSocketOption( _ System.Net.Sockets.SocketOptionLevel.IPv6, _ System.Net.Sockets.SocketOptionName.IPv6Only, 0) Dim localEP As New System.Net.IPEndPoint( _ System.Net.IPAddress.IPv6Any, localPort) udp.Client.Bind(localEP)
System.Net.Sockets.UdpClient udp = new System.Net.Sockets.UdpClient( System.Net.Sockets.AddressFamily.InterNetworkV6); udp.Client.SetSocketOption(System.Net.Sockets.SocketOptionLevel.IPv6, System.Net.Sockets.SocketOptionName.IPv6Only, 0); System.Net.IPEndPoint localEP = new System.Net.IPEndPoint(System.Net.IPAddress.IPv6Any, localPort); udp.Client.Bind(localEP);
上で使用したReceiveとSendメソッドは同期メソッドですので、呼び出されると処理が終了するまでスレッドをブロックします。つまり、例えばReceiveメソッドでデータの受信を待ち続けると、その間アプリケーションはフリーズしたようになり、何もできなくなってしまいます。
データの送受信を非同期的に行うには、BeginReceiveメソッドとBeginSendメソッドを使います。
以下に、これらのメソッドを使用して非同期的にデータを送受信する例を示します。この例はWindowsフォームアプリケーションで、文字列の送受信(簡単なテキストチャット)を行うものです。フォームに4つのTextBoxコントロール(TextBox1、TextBox2、TextBox3、TextBox4)と2つのButtonコントロール(Button1、Button2)、1つのRichTextBoxコントロール(RichTextBox1)を配置してから、このコードをフォームクラス内に記述してください。
データ受信を待機する時は、TextBox1にローカルポート番号を入力して、Button1を押します。データを送信する時は、TextBox2に送信先のホスト名、TextBox3に送信先のポート番号、TextBox4に送信する文字列を入力して、Button2を押します。データを受信すると、RichTextBox1に受信した文字列が表示されます。
Private udpClient As System.Net.Sockets.UdpClient = Nothing 'フォームのLoadイベントハンドラ Private Sub Form1_Load(sender As Object, e As EventArgs) _ Handles MyBase.Load Button1.Text = "受信" Button2.Text = "送信" End Sub 'Button1のClickイベントハンドラ 'データ受信の待機を開始する Private Sub Button1_Click(sender As Object, e As EventArgs) _ Handles Button1.Click If udpClient IsNot Nothing Then Return End If DirectCast(sender, Button).Enabled = False 'UdpClientを作成し、指定したポート番号にバインドする Dim localEP As New System.Net.IPEndPoint( _ System.Net.IPAddress.Any, Integer.Parse(TextBox1.Text)) udpClient = New System.Net.Sockets.UdpClient(localEP) '非同期的なデータ受信を開始する udpClient.BeginReceive(AddressOf ReceiveCallback, udpClient) End Sub 'データを受信した時 Private Sub ReceiveCallback(ar As IAsyncResult) Dim udp As System.Net.Sockets.UdpClient = _ DirectCast(ar.AsyncState, System.Net.Sockets.UdpClient) '非同期受信を終了する Dim remoteEP As System.Net.IPEndPoint = Nothing Dim rcvBytes As Byte() Try rcvBytes = udp.EndReceive(ar, remoteEP) Catch ex As System.Net.Sockets.SocketException Console.WriteLine("受信エラー({0}/{1})", ex.Message, ex.ErrorCode) Return Catch ex As ObjectDisposedException 'すでに閉じている時は終了 Console.WriteLine("Socketは閉じられています。") Return End Try 'データを文字列に変換する Dim rcvMsg As String = System.Text.Encoding.UTF8.GetString(rcvBytes) '受信したデータと送信者の情報をRichTextBoxに表示する Dim displayMsg As String = String.Format("[{0} ({1})] > {2}", _ remoteEP.Address, remoteEP.Port, rcvMsg) RichTextBox1.BeginInvoke( _ New Action(Of String)(AddressOf ShowReceivedString), displayMsg) '再びデータ受信を開始する udp.BeginReceive(AddressOf ReceiveCallback, udp) End Sub 'RichTextBox1にメッセージを表示する Private Sub ShowReceivedString(str As String) RichTextBox1.Text = str & vbCrLf & RichTextBox1.Text End Sub 'Button2のClickイベントハンドラ 'データを送信する Private Sub Button2_Click(sender As Object, e As EventArgs) _ Handles Button2.Click Button1.Enabled = False '送信するデータを作成する Dim sendBytes As Byte() = _ System.Text.Encoding.UTF8.GetBytes(TextBox4.Text) 'UdpClientを作成する If udpClient Is Nothing Then udpClient = New System.Net.Sockets.UdpClient() End If '非同期的にデータを送信する udpClient.BeginSend(sendBytes, sendBytes.Length, TextBox2.Text, _ Integer.Parse(TextBox3.Text), AddressOf SendCallback, udpClient) End Sub 'データを送信した時 Private Sub SendCallback(ar As IAsyncResult) Dim udp As System.Net.Sockets.UdpClient = _ DirectCast(ar.AsyncState, System.Net.Sockets.UdpClient) '非同期送信を終了する Try udp.EndSend(ar) Catch ex As System.Net.Sockets.SocketException Console.WriteLine("送信エラー({0}/{1})", ex.Message, ex.ErrorCode) Catch ex As ObjectDisposedException 'すでに閉じている時は終了 Console.WriteLine("Socketは閉じられています。") End Try End Sub 'フォームのFormClosedイベントハンドラ Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) _ Handles MyBase.FormClosed 'UdpClientを閉じる If udpClient IsNot Nothing Then udpClient.Close() End If End Sub
private System.Net.Sockets.UdpClient udpClient = null; //フォームのLoadイベントハンドラ private void Form1_Load(object sender, EventArgs e) { Button1.Text = "受信"; Button2.Text = "送信"; } //Button1のClickイベントハンドラ //データ受信の待機を開始する private void Button1_Click(object sender, EventArgs e) { if (udpClient != null) { return; } ((Button)sender).Enabled = false; //UdpClientを作成し、指定したポート番号にバインドする System.Net.IPEndPoint localEP = new System.Net.IPEndPoint( System.Net.IPAddress.Any, int.Parse(TextBox1.Text)); udpClient = new System.Net.Sockets.UdpClient(localEP); //非同期的なデータ受信を開始する udpClient.BeginReceive(ReceiveCallback, udpClient); } //データを受信した時 private void ReceiveCallback(IAsyncResult ar) { System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState; //非同期受信を終了する System.Net.IPEndPoint remoteEP = null; byte[] rcvBytes; try { rcvBytes = udp.EndReceive(ar, ref remoteEP); } catch(System.Net.Sockets.SocketException ex) { Console.WriteLine("受信エラー({0}/{1})", ex.Message, ex.ErrorCode); return; } catch (ObjectDisposedException ex) { //すでに閉じている時は終了 Console.WriteLine("Socketは閉じられています。"); return; } //データを文字列に変換する string rcvMsg = System.Text.Encoding.UTF8.GetString(rcvBytes); //受信したデータと送信者の情報をRichTextBoxに表示する string displayMsg=string.Format("[{0} ({1})] > {2}", remoteEP.Address, remoteEP.Port, rcvMsg); RichTextBox1.BeginInvoke( new Action<string>(ShowReceivedString), displayMsg); //再びデータ受信を開始する udp.BeginReceive(ReceiveCallback, udp); } //RichTextBox1にメッセージを表示する private void ShowReceivedString(string str) { RichTextBox1.Text = str + "\r\n" + RichTextBox1.Text; } //Button2のClickイベントハンドラ //データを送信する private void Button2_Click(object sender, EventArgs e) { Button1.Enabled = false; //送信するデータを作成する byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(TextBox4.Text); //UdpClientを作成する if (udpClient == null) { udpClient = new System.Net.Sockets.UdpClient(); } //非同期的にデータを送信する udpClient.BeginSend(sendBytes, sendBytes.Length, TextBox2.Text, int.Parse(TextBox3.Text), SendCallback, udpClient); } //データを送信した時 private void SendCallback(IAsyncResult ar) { System.Net.Sockets.UdpClient udp = (System.Net.Sockets.UdpClient)ar.AsyncState; //非同期送信を終了する try { udp.EndSend(ar); } catch (System.Net.Sockets.SocketException ex) { Console.WriteLine("送信エラー({0}/{1})", ex.Message, ex.ErrorCode); } catch (ObjectDisposedException ex) { //すでに閉じている時は終了 Console.WriteLine("Socketは閉じられています。"); } } //フォームのFormClosedイベントハンドラ private void Form1_FormClosed(object sender, FormClosedEventArgs e) { //UdpClientを閉じる if (udpClient != null) { udpClient.Close(); } }