DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

UDPによりデータの送受信を行う

ここでは、UDP(User Datagram Protocol)を使ってデータの送受信を行う方法について、基本的な事柄を説明します。

UDPはTCPのように接続の確立の必要がなく、相手にデータを一方的に送りつけます。そのため、サーバーも必要ありません。

.NET FrameworkでUDPを扱うには、UdpClientクラスを使用すると便利です。このクラスを使用すると、Socketクラスを直接使用するより、多少簡単になります。

以下にUdpClientクラスを使ってデータを送受信するサンプル(コンソールアプリケーション)を示します。データを受信するだけのアプリケーションと、データを送信するだけのアプリケーションの2つに分かれています。

まずは、データを受信するだけのアプリケーションのサンプルです。受信したデータ受信したデータをすべてコンソールに出力しています。「exit」という文字列を受信すると、受信を終了します。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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」と入力するまで繰り返しデータを送信できます。上記受信専用アプリケーションを起動後、実行してください。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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する

データを受信する時、すべての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を作成して、バインドするまでの部分のみです。

VB.NET
コードを隠すコードを選択
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)
C#
コードを隠すコードを選択
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に受信した文字列が表示されます。

UDPを使ったチャットアプリケーションの例

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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();
    }
}
  • 履歴:
  • 2015/4/13 サンプルを、受信と送信に分けた。「すべてのIPアドレスをListenする」と「非同期的に送受信を行う」を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。