[C#網(wǎng)絡(luò)編程系列]專題六:UDP編程

引言:

成都創(chuàng)新互聯(lián)公司專業(yè)為企業(yè)提供蒙陰網(wǎng)站建設(shè)、蒙陰做網(wǎng)站、蒙陰網(wǎng)站設(shè)計、蒙陰網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、蒙陰企業(yè)網(wǎng)站模板建站服務(wù),十年蒙陰做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

前一個專題簡單介紹了TCP編程的一些知識,UDP與TCP地位相當(dāng)?shù)牧硪粋€傳輸層協(xié)議,它也是當(dāng)下流行的很多主流網(wǎng)絡(luò)應(yīng)用(例如QQ、MSN和Skype等一些即時通信軟件傳輸層都是應(yīng)用UDP協(xié)議的)底層的傳輸基礎(chǔ),所以在本專題中就簡單介紹下UDP的工作原理和UDP編程的只是,希望可以對剛接觸網(wǎng)絡(luò)編程的朋友起到入門的作用。

一、UDP介紹

UDP和TCP都是構(gòu)建在IP層之上傳輸層的協(xié)議,但UDP是一種簡單、面向數(shù)據(jù)報(Sock_Dgram)無連接協(xié)議,提供的是不一定可靠的傳輸服務(wù)。

然而TCP是一種面向連接、可靠的,面向字節(jié)流(Sock_Stream)的傳輸協(xié)議,對于“無連接”是指在正式通信前不必與對方先建立連接,不管對方狀態(tài)如何都可以直接發(fā)送過去(就如QQ中通過QQ號查看好友后發(fā)送添加好友請求,此間不需要考慮對方的狀態(tài)如何,都照樣發(fā)送請求)。從UDP和TCP的定義中就可以看出它們兩者的區(qū)別了,(1)UDP的可靠性不如TCP,因為TCP傳輸前要首先建立連接,這樣就增加了TCP傳輸?shù)目煽啃?,所以UDP也被稱為不可靠的傳輸協(xié)議,關(guān)于TCP的介紹可以看我上一篇博客的介紹。

TCP和UDP還有另外一個區(qū)別。(2)UDP不能保證有序傳輸。即UDP不能確保數(shù)據(jù)的發(fā)送和接收順序。

下面就來看看UDP協(xié)議的工作原理,對UDP的工作原理有一個好的理解,對后面介紹的UDP編程也是一個好的基礎(chǔ)。

1.1 UDP的工作原理

UDP將網(wǎng)絡(luò)數(shù)據(jù)流量壓縮成數(shù)據(jù)報的形式,每一個數(shù)據(jù)報用8個字節(jié)(8 X 8位=64位)描述報頭信息,剩余字節(jié)包含具體的傳輸數(shù)據(jù)。UDP報頭(只有8個字節(jié))相當(dāng)于TCP的報頭(至少20個字節(jié))很短,UDP報頭由4個域組成,每個域各占2個字節(jié),具體為源端口、目的端口、用戶數(shù)據(jù)報長度和校驗和,

具體結(jié)構(gòu)見下圖(下面也貼出了TCP報文的結(jié)構(gòu)圖,與UDP數(shù)據(jù)報做一個對比的作用):

[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程 [C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

UDP協(xié)議和TCP協(xié)議都使用端口號為不同的應(yīng)用保留其各自的數(shù)據(jù)傳輸通道這一機(jī)制,數(shù)據(jù)發(fā)送方將UDP數(shù)據(jù)報通過源端口發(fā)送出去,而數(shù)據(jù)接收方則通過目標(biāo)端口接收數(shù)據(jù)。

1.2 UDP的優(yōu)勢

前面介紹中說UDP相對于TCP是不可靠的,不能保證有序傳輸的傳輸協(xié)議,然而UDP協(xié)議相對于TCP協(xié)議的優(yōu)勢在哪里呢?,

UDP相對于TCP的優(yōu)勢主要有三個方面的:

(1)UDP速度比TCP快。

由于UDP不需要先與對方建立連接,也不需要傳輸確認(rèn),因此其數(shù)據(jù)的傳輸速度比TCP快很多。對于一些著重傳輸性能而不是傳輸完整性的應(yīng)用(網(wǎng)絡(luò)音頻播放、視頻點(diǎn)播和網(wǎng)絡(luò)會議等),使用UDP協(xié)議更加適合,因為它傳輸速度快,使通過網(wǎng)絡(luò)播放的視頻音質(zhì)好、畫面清晰。

(2)UDP有消息邊界。

通過UDP協(xié)議進(jìn)行傳輸?shù)陌l(fā)送方對應(yīng)用程序交下來的報文,在添加首部后就向下直接交付給IP層。既不拆分也不合并,而是保留這些報文的邊界,所以使用UDP協(xié)議不需要像TCP那樣考慮消息邊界的問題,這樣就使得UDP編程相對于TCP在接收到的數(shù)據(jù)處理方面要簡單的多。(對于TCP消息邊界的問題可以查看相關(guān)的文檔,在這里我就不列出來了)

(3)UDP可以一對多傳輸

由于傳輸數(shù)據(jù)部建立連接,也就不需要維護(hù)連接狀態(tài),因此一臺服務(wù)器可以同時向多個客戶端發(fā)送相同的信息。利用UDP可以使用廣播或者組播的方式同時向子網(wǎng)的所有客戶端進(jìn)程發(fā)送信息,廣播和組播的介紹放到后面TCP編程中介紹。

上面介紹了UDP協(xié)議相對于TCP協(xié)議的優(yōu)勢,其中速度快是UDP的最重要的優(yōu)勢,也是像一些網(wǎng)絡(luò)會議、即時通信軟件傳輸層選擇UDP協(xié)議進(jìn)行傳輸?shù)脑蛩凇?/p>

 

二、.net平臺對UDP編程的支持

介紹完UDP相對于TCP的優(yōu)勢后,當(dāng)然很希望在.net平臺下開發(fā)一個基于UDP協(xié)議的一個應(yīng)用了,然后.net平臺下對UDP編程也做了很好的支持,為我們開發(fā)基于UDP協(xié)議的網(wǎng)絡(luò)應(yīng)用提供很多方便之處,下面就簡單介紹.net平臺下對UDP編程的支持(主要介紹提供的類來對UDP協(xié)議進(jìn)行編程)。

.net類庫中的UdpClient類對基礎(chǔ)的Socket進(jìn)行了封裝,這樣就在發(fā)送和接受數(shù)據(jù)時不需要考慮底層套接字的收發(fā)時處理的一些細(xì)節(jié)問題,這樣為UDP編程提供了方便,也可以提高開發(fā)效率(感覺net就是做這樣的事情的,對一些底層的實(shí)現(xiàn)進(jìn)行封裝,方便我們的調(diào)用,這也體現(xiàn)了面向?qū)ο笳Z言的封裝特性)對于這個的具體的使用我就不做過多的介紹的,在后面的UDP編程的實(shí)現(xiàn)部分將會對該類中主要方法的使用,大家可以查看MSDN來查看該類中其他成員的使用: http://msdn.microsoft.com/zh-cn/library/System.Net.Sockets.UdpClient.aspx

三、UDP編程的具體實(shí)現(xiàn)

由于UDP進(jìn)程在通信之前是不需要建立連接,消息接收方可能并不知道是誰給它發(fā)的消息,因此UDP編程分為兩種模式:一種“實(shí)名發(fā)送”,即接收方可以由收到的消息得知發(fā)送方進(jìn)程端口,另外一種則為“匿名發(fā)送”,即接收方并不知道發(fā)給它信息的遠(yuǎn)程進(jìn)程究竟來自哪個端口。下面通過一個winform 程序來演示下UDP的編程:

實(shí)現(xiàn)代碼:

[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程
using System;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Windows.Forms;
namespace UDPClient
{
public partial class frmUdp : Form
    {
private UdpClient sendUdpClient;
private UdpClient receiveUpdClient;
public frmUdp()
        {
            InitializeComponent();
            IPAddress[] ips= DNS.GetHostAddresses("");
            tbxlocalip.Text= ips[3].ToString();
   int port = 51883;
            tbxlocalPort.Text= port.ToString();
            tbxSendtoIp.Text= ips[3].ToString();
            tbxSendtoport.Text= port.ToString();
        }

// 接受消息
        private void btnReceive_Click(object sender, EventArgs e)
        {
   // 創(chuàng)建接收套接字
            IPAddress localIp = IPAddress.Parse(tbxlocalip.Text);
            IPEndPoint localIpEndPoint= new IPEndPoint(localIp, int.Parse(tbxlocalPort.Text));
            receiveUpdClient= new UdpClient(localIpEndPoint);


            Thread receiveThread= new Thread(ReceiveMessage);
            receiveThread.Start();
        }

// 接收消息方法
        private void ReceiveMessage()
        {
            IPEndPoint remoteIpEndPoint= new IPEndPoint(IPAddress.Any, 0);
   while (true)
            {
       try
                {
           // 關(guān)閉receiveUdpClient時此時會產(chǎn)生異常
                    byte[] receiveBytes = receiveUpdClient.Receive(ref remoteIpEndPoint);

           string message = Encoding.Unicode.GetString(receiveBytes);

           // 顯示消息內(nèi)容
                    ShowMessageforView(lstbxMessageView, string.Format("{0}[{1}]", remoteIpEndPoint, message));
                }
       catch
                {
           break;
                }
            }
        }

// 利用委托回調(diào)機(jī)制實(shí)現(xiàn)界面上消息內(nèi)容顯示
        delegate void ShowMessageforViewCallBack(ListBox listbox, string text);
private void ShowMessageforView(ListBox listbox, string text)
        {
   if (listbox.InvokeRequired)
            {
                ShowMessageforViewCallBack showMessageforViewCallback= ShowMessageforView;
                listbox.Invoke(showMessageforViewCallback,new object[] { listbox, text });
            }
   else
            {
                lstbxMessageView.Items.Add(text);
                lstbxMessageView.SelectedIndex= lstbxMessageView.Items.Count - 1;
                lstbxMessageView.ClearSelected();
            }
        }
private void btnSend_Click(object sender, EventArgs e)
        {
   if (tbxMessageSend.Text == string.Empty)
            {
                MessageBox.Show("發(fā)送內(nèi)容不能為空","提示");
       return;
            }

   // 選擇發(fā)送模式
            if (chkbxAnonymous.Checked == true)
            {
       // 匿名模式(套接字綁定的端口由系統(tǒng)隨機(jī)分配)
                sendUdpClient = new UdpClient(0);
            }
   else
            {
       // 實(shí)名模式(套接字綁定到本地指定的端口)
                IPAddress localIp = IPAddress.Parse(tbxlocalip.Text);
                IPEndPoint localIpEndPoint= new IPEndPoint(localIp, int.Parse(tbxlocalPort.Text));
                sendUdpClient= new UdpClient(localIpEndPoint);
            }

            Thread sendThread= new Thread(SendMessage);
            sendThread.Start(tbxMessageSend.Text);
        }

// 發(fā)送消息方法
        private void SendMessage(object obj)
        {
   string message = (string)obj;
   byte[] sendbytes = Encoding.Unicode.GetBytes(message);
            IPAddress remoteIp= IPAddress.Parse(tbxSendtoIp.Text);
            IPEndPoint remoteIpEndPoint= new IPEndPoint(remoteIp, int.Parse(tbxSendtoport.Text));
            sendUdpClient.Send(sendbytes, sendbytes.Length, remoteIpEndPoint);
          
            sendUdpClient.Close();
           
   // 清空發(fā)送消息框
            ResetMessageText(tbxMessageSend);
        }

// 采用了回調(diào)機(jī)制
// 使用委托實(shí)現(xiàn)跨線程界面的操作方式
        delegate void ResetMessageCallback(TextBox textbox);
private void ResetMessageText(TextBox textbox)
        {
   // Control.InvokeRequired屬性代表
   // 如果控件的處理與調(diào)用線程在不同線程上創(chuàng)建的,則為true,否則為false
            if (textbox.InvokeRequired)
            {
                ResetMessageCallback resetMessagecallback= ResetMessageText;
                textbox.Invoke(resetMessagecallback,new object[] { textbox });
            }
   else
            {
                textbox.Clear();
                textbox.Focus();
            }
        }

// 停止接收
        private void btnStop_Click(object sender, EventArgs e)
        {
            receiveUpdClient.Close();
        }

// 清空接受消息框
        private void btnClear_Click(object sender, EventArgs e)
        {
   this.lstbxMessageView.Items.Clear();
        }
    }
}
[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

運(yùn)行結(jié)果:

實(shí)名發(fā)送:

在本地運(yùn)行本程序的三個進(jìn)程(分別為A,B,C),把進(jìn)程C做為接受進(jìn)程,進(jìn)程A和進(jìn)程B都向進(jìn)程C發(fā)信息,進(jìn)程A和進(jìn)程分別綁定端口號為11883和21883,發(fā)送到端口都為51883,配置界面如下:

[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

首先不勾選“匿名”復(fù)選框,在進(jìn)程C中點(diǎn)擊“接收”按鈕開啟接受線程,在A進(jìn)程和B進(jìn)程中發(fā)送消息框里分別輸入你好,我是1和你好,我是2,然后點(diǎn)擊發(fā)送按鈕,此時在進(jìn)程中就可以看到進(jìn)程A和進(jìn)程B發(fā)來的消息,如下圖:

[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

從圖中可以看出每條消息之前都顯示了消息的準(zhǔn)確來源(包括消息進(jìn)程鎖在的Ip地址和端口號)

匿名發(fā)送:

下面把“匿名”復(fù)選框勾上后,再按照前面的步驟將得到下面的結(jié)果:

[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

 

從圖中結(jié)果可以看出此時列表中顯示的消息來源的進(jìn)程端口號分別為49439和49440,而不是發(fā)送消息進(jìn)程的真實(shí)端口(11883和21883)

這種UDP只能辨別消息源主機(jī)的Ip地址,而無法知道發(fā)消息的進(jìn)程究竟是哪個端口稱為“匿名發(fā)送”。正如我們平時發(fā)手機(jī)短信一樣,如果我們把認(rèn)識的名字和電話號碼預(yù)先存在通訊錄里,當(dāng)一發(fā)來信息,接受方馬上就可以從來電顯示中看到是誰發(fā)來的(實(shí)名模式);但是如果是陌生人發(fā)來信息或者廣告等信息時,僅看來電顯示,根本不知道對方是誰(匿名模式),QQ發(fā)消息也是一樣的道理。

 

四、UDP廣播和組播

前面UDP的實(shí)現(xiàn)中發(fā)送數(shù)據(jù)使用的都是一對一(單播)的通信方式,即只將數(shù)據(jù)發(fā)送到某一個進(jìn)程。前面提到UDP可以實(shí)現(xiàn)一對多的傳輸方式,即通過廣播和組播把數(shù)據(jù)發(fā)送給一組進(jìn)程。下面就介紹下UDP廣播和組播的相關(guān)知識。

4.1 廣播和組播的基本概念

雖然利用TCP協(xié)議可以保證數(shù)據(jù)的可靠、有序的傳輸,但是TCP僅支持一對以的傳輸,而且傳輸時需要在發(fā)送端和每一個接受端之間建立單獨(dú)的數(shù)據(jù)通信通道,如果需要實(shí)現(xiàn)網(wǎng)絡(luò)會議、網(wǎng)絡(luò)視頻的點(diǎn)播等功能時要向大量主機(jī)發(fā)送相同的數(shù)據(jù)包,如果采用單播方式逐個節(jié)點(diǎn)傳輸?shù)脑?,將會給發(fā)送方帶來網(wǎng)絡(luò)堵塞等問題,此時可以考慮實(shí)現(xiàn)UDP的多播方式——即廣播和組播來實(shí)現(xiàn)這樣的功能(一對多通信分為廣播和組播兩種形式)。

廣播是指同時向子網(wǎng)中的多臺計算機(jī)發(fā)送消息,并且所有子網(wǎng)中的計算機(jī)都可以接收到發(fā)送方發(fā)來的消息,每個廣播消息包含一個特殊的IP地址,這個IP的中子網(wǎng)內(nèi)主機(jī)標(biāo)志部分的二進(jìn)制都為1,例如,子網(wǎng)掩碼為255.255.255.0,對于子網(wǎng)192.168.0,則這個IP地址為192.168.0.255.

然后廣播消息又分為本地廣播和全球廣播兩種類型, 本地廣播是指向子網(wǎng)中的所有計算機(jī)發(fā)送廣播消息,其他網(wǎng)絡(luò)不會受到本地廣播的影響。

IP地址分為兩部分——網(wǎng)絡(luò)標(biāo)志部分和主機(jī)標(biāo)志部分,這兩部分是靠子網(wǎng)掩碼來區(qū)分的,主機(jī)標(biāo)記部分二進(jìn)制全部為1的地址成為本地廣播地址。例如:

A類網(wǎng)絡(luò)192.168.0.0,使用子網(wǎng)掩碼255.255.0.0,則本地廣播地址為:[C# 網(wǎng)絡(luò)編程系列]專題六:UDP編程

對于IPv4來說,全球廣播使用所有位全為1的IP地址,即255.255.255.255,這個廣播地址代表數(shù)據(jù)報的目的地是網(wǎng)絡(luò)上所有設(shè)備,但是由于路由器會自動過濾全球廣播,所以使用這個地址根本就沒有任何意義。

然后當(dāng)接收者分布于多個不同的子網(wǎng)時,廣播將不再適用,此時可以通過組播的方式來實(shí)現(xiàn),組播也叫多路廣播,組播是將信息從一臺計算機(jī)發(fā)送到本網(wǎng)或全網(wǎng)內(nèi)指定的計算機(jī)上,即發(fā)送到那些加入了指定組播組的計算機(jī)上,每臺計算機(jī)都可以通過程序隨時加入某個組播組中,也可以隨時退出來, 就像我們開網(wǎng)了會議一樣,可以隨時加入會議室進(jìn)行開會,會議結(jié)束和會議進(jìn)行中都可以隨意的退出來。

 

4.2 加入和退出組播組

組播組又稱為多路廣播組,組播地址的范圍在224.0.0.0到239.255.255.255的D類IP地址(至于這個概念大家可以百度百科里面就查看)。任何發(fā)送到組播地址的消息都會被發(fā)送到組內(nèi)所有成員設(shè)備上,組可以使永久的也可以是臨時,大多數(shù)我們使用的都是臨時的,僅在有成員的時候才存在。

使用組播時,注意生命周期(TTL,Time to live)的設(shè),TTL值表示允許路由器轉(zhuǎn)發(fā)的最大次數(shù),當(dāng)達(dá)到這個最大值時,數(shù)據(jù)包就會被丟棄,TTL的默認(rèn)值為1,設(shè)置為1時表明只能在子網(wǎng)中發(fā)送數(shù)據(jù)

加入組播組:

UdpClient類提供了JoinMulticastGroup方法,用于將UdpClient加入到使用指定的IPAddress的組播組中,調(diào)用該方法后,基礎(chǔ)的Socket會自動向路由器發(fā)送數(shù)據(jù)包,用于請求成為組播組的成員,如果成為組播組成員,就可以接收該組播組的數(shù)據(jù)報。至于具體方法的時候會在后面實(shí)現(xiàn)UDP廣播程序中會用到,另外大家也可以查看MSDN,所以這里我就不再列出來了,只是指出這個方法的作用,讓大家知道有這么個方法來調(diào)用。

退出組播組:

同樣利用UdpClient的DropMulticastGroup方法,可以退出組播組,調(diào)用該方法后,基礎(chǔ)Socket會自動向路由器發(fā)送數(shù)據(jù)包,用于請求從指定的組播組里退出,從組中回收UdpClient對象之后,將不再接受發(fā)送到該組播組的數(shù)據(jù)報。

 

五、總結(jié)

由于時間的關(guān)系,這篇文章就介紹到這里的,至于實(shí)現(xiàn)UDP廣播的程序放在后面一個專題里面的,前面也對廣播和組播的概念進(jìn)行了簡單的介紹,相信大家也對廣播和組播有了個簡單的認(rèn)識(廣播組和組播組說白了就是一個IP地址的集合,其實(shí)實(shí)現(xiàn)UDP廣播的程序和前面實(shí)現(xiàn)單播的程序差不多,只是前面綁定了一個IP地址當(dāng)然也只能發(fā)送到一個IP地址了,也就是所謂的單播,多播和廣播就是發(fā)送的IP地址是一個組,當(dāng)然也就實(shí)現(xiàn)了一對多的傳輸了)。UDP廣播程序的實(shí)現(xiàn)就放在下一個專題和大家分享的,因為我現(xiàn)在要去吃飯了,吃完飯再繼續(xù)和大家介紹,希望大家如果覺得有幫助的話,也可以推薦下,這給我繼續(xù)寫下去的動力,謝謝大家的支持

 

當(dāng)前標(biāo)題:[C#網(wǎng)絡(luò)編程系列]專題六:UDP編程
URL標(biāo)題:http://muchs.cn/article12/jpgcgc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化網(wǎng)站內(nèi)鏈、云服務(wù)器手機(jī)網(wǎng)站建設(shè)、小程序開發(fā)定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管