在 .NET有兩種類別可用於建立UDP Socket,一是Socket類別,第二個則是由Socket類別所衍生的UdpClient類別。因為同步比非同步簡單、衍生類別比基底類別簡單、UDP比TCP簡單,所以我把同步的UdpClient當成Socket程式入門的首篇實作文章。
在 .NET有兩種類別可用於建立UDP Socket,一是Socket類別,第二個則是由Socket類別所衍生的UdpClient類別。因為同步比非同步簡單、衍生類別比基底類別簡單、UDP比TCP簡單,所以我把同步的UdpClient當成Socket程式入門的首篇實作文章。
先來談談UDP的特性好了,以下是UDP與TCP一份簡單的比較表:
UDP | TCP | |
Socket Type﹝註﹞ | Dgram | Stream |
可靠性 | 較低 | 較高 |
速度 | 較快 | 較慢 |
需連結後通訊 | No | Yes |
點對點通訊 | Yes | Yes |
多點通訊 | Yes | No |
廣播通訊 | Yes | No |
﹝註﹞關於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類別來處理執行緒的問題,如果你對這個類別不熟可以參考之前的文章﹝多執行緒初探--使用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
至於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就是這麼容易上手,其實只要把上面兩個程式融會貫通,稍微變化一下,就可以寫成一個兩端互相通信的小應用程式,各位有興趣可以自行測試看看,有任何指教也煩請不吝留言。