實作同步 .NET Socket UDP《使用UdpClient類別》

在 .NET有兩種類別可用於建立UDP Socket,一是Socket類別,第二個則是由Socket類別所衍生的UdpClient類別。因為同步比非同步簡單、衍生類別比基底類別簡單、UDP比TCP簡單,所以我把同步的UdpClient當成Socket程式入門的首篇實作文章。

       在 .NET有兩種類別可用於建立UDP Socket,一是Socket類別,第二個則是由Socket類別所衍生的UdpClient類別。因為同步比非同步簡單、衍生類別比基底類別簡單、UDP比TCP簡單,所以我把同步的UdpClient當成Socket程式入門的首篇實作文章。

       先來談談UDP的特性好了,以下是UDP與TCP一份簡單的比較表:

 UDPTCP
Socket Type﹝註﹞DgramStream
可靠性較低較高
速度較快較慢
需連結後通訊NoYes
點對點通訊YesYes
多點通訊YesNo
廣播通訊YesNo

﹝註﹞關於Socket Type可以參考MSDN文件庫[SocketType 列舉型別]

       不可免俗地,還是要來簡單談談建構函式,我把六個建構函式分為四類,如下表:

1自動給本機Port號UdpClient ()
UdpClient(AddressFamily)
2指定本機Port號UdpClient(Int32)
UdpClient(Int32, AddressFamily)
3依IPEndPoint決定本機Port號UdpClient(IPEndPoint)
4指定遠端主機Hostname與Port號UdpClient(String, Int32)

﹝註﹞現在都以IPv4的討論為主

第一類的建構函式很適合用於撰寫純Client端時使用,因為此時我們不用管是從哪個Port發出封包,系統自己會幫我們決定好可用的Port。
第二類的建構函式適用於撰寫UDP的Server端程式,試想如果用系統自由發揮的方法,那Client怎會知道要連到哪個Port呢?
第三類很有意思的是它綜合了前兩類的用法,原因在於宣告IPEndPoint執行個體時,可以宣告為固定或非固定:
    (3-1)固定IP與Port:Dim myIPEndpoint As New IPEndPoint(IPAddress.Parse("192.168.11.3"), 6666)
    (3-2)固定IP,但Port由系統決定:Dim myIPEndpoint As New IPEndPoint(IPAddress.Parse("192.168.11.3"), 0)
    (3-3)固定Port,用所有可用IP:Dim myIPEndpoint As New IPEndPoint(IPAddress.Any, 6666)
    (3-4)用所有可用IP且Port由系統決定: Dim myIPEndpoint As New IPEndPoint(IPAddress.Any, 0)
第四類事實上是宣告一個第一類的UDPClient再加上UDPClient.Connect方法連結到指定主機,千萬不要和前三類弄混了,以為這也是指定本機,這一點千萬要記住。

       講了這麼多,當然要舉個實例來用用,首先是建立一個可以當Server端的UDP程式,在這個程式中會使用BackgroundWorker類別來處理執行緒的問題,如果你對這個類別不熟可以參考之前的文章﹝SyncUDP02多執行緒初探--使用BackgroundWorker(1)多執行緒初探--使用BackgroundWorker(2)   ﹞。為何要使用多執行緒呢?因為這種程式多半會用到無窮迴圈,讓這種玩意兒在主執行緒繞來繞去實在不是一件好主意。一般其實個人是常用Thread 或是 ThreadPool來做,不過因為這兩樣我還沒貼文,所以先用BackgroundWorker來當做示範。畫面上的控制項非常簡單,使用Button控制啟動或停止,NumericUpDown設定UDPclient的Port;至於DataGridView則是當收到訊號後將對方的IP、Port與資料顯示出來。

 

       Server端程式的主要步驟如下 :   

步驟一:先宣告一個自訂類別 CSState,這個類別的執行個體將會用來在各事件中傳來傳去。
   Private Class CSState
        Public RemoteIpEndPoint As IPEndPoint
        Public myUDPClient As UdpClient
        Public ReceiveBytes() As Byte
    End Class

步驟二:宣告一個DataTable來做為資料的儲存
    Private myDatatable As New DataTable

步驟三:在Button1.Click事件中產生UdpClient執行個體,並呼叫 BackgroundWorker.RunWorkerAsync方法進入接收資料狀態。
    If BackgroundWorker1.IsBusy = True Then
         MessageBox.Show("Socket已啟動")
    Else
         Dim iPort As Integer
         iPort = NumericUpDown1.Value
         Dim myObj As New CSState
         myObj.myUDPClient = New UdpClient(iPort)
         myObj.RemoteIpEndPoint = New IPEndPoint(IPAddress.Any, 0) <==這是Server接收的一個重點,要用(IPAddress.Any, 0)的原因在於Server端程式一開始無法預測會是哪個IP從哪個Port傳給它﹝除非有其它原因要鎖住來源﹞。
         BackgroundWorker1.RunWorkerAsync(myObj)
         Label2.ForeColor = Color.Blue
         Label2.Text = "UDP運作中"
     End If

 步驟四:在BackgroundWorker1.DoWork事件中撰寫接收資料的程序,並於收到資料後呼叫BackgroundWorker1.ReportProgress以處理畫面展現。
    Dim myObj As CSState = CType(e.Argument, CSState)
     While True
         Try
           myObj.ReceiveBytes = myObj.myUDPClient.Receive(myObj.RemoteIpEndPoint)
             BackgroundWorker1.ReportProgress(0, myObj)
             If BackgroundWorker1.CancellationPending = True Then
                 myObj.myUDPClient.Close()
                 Exit While
             End If
         Catch ex As Exception
             MessageBox.Show(ex.ToString)
         End Try
     End While

步驟五:在BackgroundWorker1.ProgressChanged事件中處理DataTable的資料,使其能展現於DataGridView上
       Dim myObj As CSState = CType(e.UserState, CSState)
       Dim xRow As DataRow = myDatatable.NewRow()
       xRow.Item(0) = myObj.RemoteIpEndPoint.Address.ToString()
       xRow.Item(1) = myObj.RemoteIpEndPoint.Port.ToString()
       xRow.Item(2) = Encoding.GetEncoding(950).GetString(myObj.ReceiveBytes)
       myDatatable.Rows.Add(xRow)

以上Server端的完整程式碼可以在此下載[VB2005]:SyncUDPClient_1.rar

SyncUDP03

       至於Client端﹝表單畫面如上圖﹞的程式就更簡單了, 只有以下短短幾行,這邊使用UdpClient.Send (Byte[], Int32, IPEndPoint) 多載方法來傳送資料

   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
       Dim myUDPClient As New UdpClient()
       Dim ServerIpAddress As IPAddress
       Try
           ServerIPaddress = IPAddress.Parse(TextBox1.Text)
       Catch ex As Exception
           MessageBox.Show("Server IP設定錯誤")
           Exit Sub
       End Try
       Dim iPort As Integer
       iPort = NumericUpDown1.Value
       Dim RemoteIpEndPoint As New IPEndPoint(ServerIPaddress, iPort)
       Dim myBytes As Byte()
       myBytes = Encoding.GetEncoding(950).GetBytes(Trim(TextBox2.Text))
       If myBytes.Length > 0 Then
           myUDPClient.Send(myBytes, myBytes.Length, RemoteIpEndPoint)
       Else
           MessageBox.Show("無資料可傳送!!")
       End If
   End Sub

  以上Client端的完整程式碼可以在此下載[VB2005]:SyncUDPClient_2.rar

         UdpClient就是這麼容易上手,其實只要把上面兩個程式融會貫通,稍微變化一下,就可以寫成一個兩端互相通信的小應用程式,各位有興趣可以自行測試看看,有任何指教也煩請不吝留言。