C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步

上傳人:痛*** 文檔編號(hào):153181375 上傳時(shí)間:2022-09-17 格式:PPT 頁(yè)數(shù):144 大?。?16.52KB
收藏 版權(quán)申訴 舉報(bào) 下載
C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步_第1頁(yè)
第1頁(yè) / 共144頁(yè)
C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步_第2頁(yè)
第2頁(yè) / 共144頁(yè)
C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步_第3頁(yè)
第3頁(yè) / 共144頁(yè)

下載文檔到電腦,查找使用更方便

10 積分

下載資源

還剩頁(yè)未讀,繼續(xù)閱讀

資源描述:

《C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步》由會(huì)員分享,可在線閱讀,更多相關(guān)《C應(yīng)用程序設(shè)計(jì)教程 第10章 多線程和Socket編程初步(144頁(yè)珍藏版)》請(qǐng)?jiān)谘b配圖網(wǎng)上搜索。

1、第10章 多線程和Socket編程初步 Socket編程技術(shù)廣泛用于即時(shí)通信系統(tǒng)(如QQ、MSN等)、網(wǎng)絡(luò)游戲、BT下載、Internet視頻直播等C/S結(jié)構(gòu)客戶端網(wǎng)絡(luò)程序,是一個(gè)程序員必須掌握的技術(shù),本章介紹Socket編程初步知識(shí)。在Socket編程中,必須使用多線程技術(shù),因此在本章首先介紹多線程,然后再介紹Socket編程。10.1 創(chuàng)建線程 如果在一個(gè)程序中,有多個(gè)工作要同時(shí)做,可以采用多線程。在Windows操作系統(tǒng)中可以運(yùn)行多個(gè)程序,把一個(gè)運(yùn)行的程序叫做一個(gè)進(jìn)程。一個(gè)進(jìn)程又可以有多個(gè)線程,所有程序的線程輪流共同占用CPU的運(yùn)行時(shí)間,Windows操作系統(tǒng)將時(shí)間分為時(shí)間片,每個(gè)線程分

2、配一個(gè)時(shí)間片,一個(gè)線程用完一個(gè)時(shí)間片后,操作系統(tǒng)將此線程掛起,將另一個(gè)線程喚醒,使其使用下一個(gè)時(shí)間片,操作系統(tǒng)不斷的把線程掛起,喚醒,再掛起,再喚醒,如此反復(fù),由于現(xiàn)在CPU的速度比較快,給人的感覺(jué)象是多個(gè)線程同時(shí)執(zhí)行。Windows操作系統(tǒng)中有很多這樣的例子,例如復(fù)制文件時(shí),一方面在進(jìn)行磁盤(pán)的讀寫(xiě)操作,同時(shí)一張紙不停的從一個(gè)文件夾飄到另一個(gè)文件夾,這個(gè)飄的動(dòng)作實(shí)際上是一段動(dòng)畫(huà),兩個(gè)動(dòng)作是在不同線程中完成的,就像兩個(gè)動(dòng)作是同時(shí)進(jìn)行的。又如Word程序中的拼寫(xiě)檢查也是在另一個(gè)線程中完成的。每個(gè)進(jìn)程最少有一個(gè)線程,叫主線程,是進(jìn)程自動(dòng)創(chuàng)建的,每進(jìn)程可以創(chuàng)建多個(gè)線程。本節(jié)介紹線程類(lèi)(Thread)的

3、屬性和方法以及如何創(chuàng)建線程。10.1.1 線程類(lèi)(Thread)的屬性和方法 線程類(lèi)在命名空間System.Threading中定義的,因此如果要?jiǎng)?chuàng)建多線程,必須引入命名空間System.Threading。Thread類(lèi)的常用屬性和方法如下:屬性Priority:設(shè)置線程優(yōu)先級(jí),有5種優(yōu)先級(jí)類(lèi)別:AboveNormal(稍高)、BelowNormal(稍低)、Normal(中等,默認(rèn)值)、Highest(最高)和Lowest(最低)。例如語(yǔ)句myThread.Priority=ThreadPriority.Highest設(shè)置線程myThread的優(yōu)先級(jí)為最高。一個(gè)線程的優(yōu)先權(quán)并不是越高越好,

4、應(yīng)考慮到整個(gè)進(jìn)程中所有線程以及其他進(jìn)程的情況做出最優(yōu)選擇。優(yōu)先級(jí)相同的線程按照時(shí)間片輪流運(yùn)行。優(yōu)先級(jí)高的線程先運(yùn)行,只有優(yōu)先級(jí)高的線程停止、休眠或暫停時(shí),低優(yōu)先級(jí)的線程才能運(yùn)行。構(gòu)造函數(shù):New(new ThreadStart(線程中要執(zhí)行的無(wú)參數(shù)方法名),參數(shù)中指定的方法需要程序員自己定義,這個(gè)方法完成線程所要完成的任務(wù),退出該方法,線程結(jié)束。該方法必須為公有void類(lèi)型的方法,無(wú)參數(shù)。如果希望有參數(shù),可使用VB.Net2.0中新構(gòu)造函數(shù):New(new ParameterizedThreadStart(線程中要執(zhí)行的只能有一個(gè)參數(shù)的方法名)。方法Start():建立線程類(lèi)對(duì)象后,線程處于未

5、啟動(dòng)狀態(tài),這個(gè)方法使線程改變?yōu)榫途w狀態(tài),如果能獲的CPU運(yùn)行時(shí)間,線程變?yōu)檫\(yùn)行狀態(tài)。方法IsAlive():判斷線程對(duì)象是否存在,=true,線程存在。方法Abort():撤銷(xiāo)線程對(duì)象。不能撤銷(xiāo)一個(gè)已不存在的線程對(duì)象,因此在撤銷(xiāo)一個(gè)線程對(duì)象前,必須用方法IsAlive()判斷線程對(duì)象是否存在。靜態(tài)方法Sleep():線程休眠參數(shù)設(shè)定的時(shí)間,單位為毫秒,此時(shí)線程處于休眠狀態(tài)。線程休眠后,允許其他就緒線程運(yùn)行。休眠指定時(shí)間后,線程變?yōu)榫途w狀態(tài)。方法Suspend()和Resume():Suspend()方法使線程變?yōu)閽炱馉顟B(tài)。Resume方法使掛起線程變?yōu)榫途w狀態(tài),如能獲的CPU的運(yùn)行時(shí)間,線程變

6、為運(yùn)行狀態(tài)。如線程多次被掛起,調(diào)用一次Resume()方法就可以把線程喚醒。由于不安全建議不使用這兩個(gè)函數(shù)。10.1.2 創(chuàng)建線程例子【例10.1】本例使用線程類(lèi)Thread創(chuàng)建一個(gè)新的線程,在標(biāo)簽控件中顯示該線程運(yùn)行的時(shí)間。在窗體放置2個(gè)按鈕,單擊按鈕完成新建和停止線程的功能。(1)新建項(xiàng)目。在窗體中放置2個(gè)按鈕和1個(gè)標(biāo)簽控件(label1)。button1的屬性Text=新線程,Enabled=true。button2的屬性Text=撤銷(xiāo),Enabled=false。(2)在Form1.cs頭部增加語(yǔ)句:using System.Threading(3)為Form1類(lèi)中聲明一個(gè)委托類(lèi)dFu

7、n、定義一個(gè)類(lèi)dFun的變量和線程類(lèi)變量:/dFun類(lèi)可代表無(wú)返回值有一個(gè)string參數(shù)方法delegate void dFun(string text);/dFun類(lèi)變量dFun dFun1;/線程類(lèi)變量private Thread thread;(4)為標(biāo)題為“新線程”的按鈕(button1)增加單擊事件處理函數(shù)如下:private void button1_Click(object sender,EventArgs e)/生成線程類(lèi)對(duì)象,fun為自定義方法名稱 thread=new Thread(new ThreadStart(fun);Label1.Text=0“運(yùn)行時(shí)間從0開(kāi)始 /

8、線程變?yōu)榫途w狀態(tài),如能獲的CPU運(yùn)行時(shí)間,thread.Start()/線程變?yōu)檫\(yùn)行狀態(tài) /標(biāo)題為“新線程”的按鈕,創(chuàng)建線程后,Button1.Enabled=False /不允許再創(chuàng)建線程 /標(biāo)題為“撤銷(xiāo)”的按鈕,允許對(duì)運(yùn)行狀態(tài)的線程撤銷(xiāo) /Button2.Enabled=True(5)為標(biāo)題為“撤銷(xiāo)”的按鈕(button2)增加單擊事件處理函數(shù)如下:private void button2_Click(object sender,EventArgs e)if(thread.IsAlive)thread.Abort();/撤銷(xiāo)線程對(duì)象 button1.Enabled=true;button2

9、.Enabled=false;(6)C#線程模型允許將任何一個(gè)公有過(guò)程(靜態(tài)或非靜態(tài))作為線程過(guò)程,因此允許在任何一個(gè)類(lèi)(不要求這個(gè)類(lèi)是某個(gè)類(lèi)的子類(lèi))中定義線程過(guò)程,而且同一個(gè)類(lèi)中可以定義多個(gè)線程過(guò)程。C#不允許在此過(guò)程中直接修改線程外控件屬性,這是防止多個(gè)線程同時(shí)修改同一控件的同一屬性發(fā)生錯(cuò)誤,必須使用控件的Invoke方法修改線程外控件屬性,Invoke方法有兩個(gè)參數(shù),參數(shù)1是修改控件屬性的方法的委托,參數(shù)2是object數(shù)組,是傳遞給參數(shù)1代表的方法的參數(shù)。為Form1類(lèi)定義一個(gè)線程方法如下:/C#1.x中在線程中執(zhí)行的方法,退出該方法,線程結(jié)束public void fun()/必須為

10、公有void類(lèi)型方法,無(wú)參數(shù) while(true)/這里是死循環(huán),線程將一直運(yùn)行 /允許得到線程外控件屬性值 int x=Convert.ToInt32(label1.Text);x+;string s=Convert.ToString(x);/dFun1代表修改label1.Text的方法 label1.Invoke(dFun1,new objects);/線程休眠1秒鐘,休眠一次,線程運(yùn)行了1秒鐘 Thread.Sleep(1000);(7)為Form1類(lèi)定義一個(gè)修改label1.Text的方法如下:private void SetText(string text)label1.Text

11、=text;(8)在Form1類(lèi)的Load事件函數(shù)的最后增加如下語(yǔ)句:dFun1=new dFun(SetText);(9)在關(guān)閉程序之前,必須撤銷(xiāo)線程對(duì)象。為主窗體的FormClosing事件增加事件處理函數(shù)如下:private void Form1_FormClosing(object sender,FormClosingEventArgs e)if(thread.IsAlive)thread.Abort();(10)編譯運(yùn)行,單擊標(biāo)題為新線程的按鈕,新線程開(kāi)始,計(jì)數(shù)器從0開(kāi)始計(jì)數(shù)。單擊標(biāo)題為撤銷(xiāo)的按鈕,線程對(duì)象被撤銷(xiāo),計(jì)數(shù)器停止計(jì)數(shù)?!纠?0.2】本例重做例9.19,查找文件在另一個(gè)線程

12、中進(jìn)行,當(dāng)單擊“停止搜索”按鈕后,停止搜索線程,以便停止查找文件。本例修改例9.19。請(qǐng)同學(xué)課后自己完成。10.2 多個(gè)線程互斥多個(gè)線程同時(shí)修改共享數(shù)據(jù)可能發(fā)生錯(cuò)誤。假設(shè)2個(gè)線程分別監(jiān)視2個(gè)入口進(jìn)入的人數(shù),每當(dāng)有人通過(guò)入口,線程用C#語(yǔ)句對(duì)總?cè)藬?shù)變量執(zhí)行加1操作。一條C#語(yǔ)句可能包含若干機(jī)器語(yǔ)言語(yǔ)句,假設(shè)C#語(yǔ)句加1操作包含的機(jī)器語(yǔ)言語(yǔ)句是:取總?cè)藬?shù),加1,再存回。操作系統(tǒng)可以在一條機(jī)器語(yǔ)言語(yǔ)句結(jié)束后,掛起運(yùn)行的線程。如當(dāng)前總?cè)藬?shù)為5,線程1運(yùn)行,監(jiān)視到有人通過(guò)入口,取出總?cè)藬?shù)(=5)后,線程1時(shí)間用完掛起。線程2喚醒,也監(jiān)視到有人通過(guò)入口,并完成了總?cè)藬?shù)加1并送回的操作,總?cè)藬?shù)為6,線程2掛

13、起。線程1喚醒,對(duì)已取出的總?cè)藬?shù)(此時(shí)為5)加1,存回去,總?cè)藬?shù)應(yīng)為7,實(shí)為6,少算一個(gè)。為了防止此類(lèi)錯(cuò)誤,在一個(gè)線程修改共享資源(例如上例的總?cè)藬?shù)變量)時(shí),不允許其他線程對(duì)同一共享資源進(jìn)行修改,這叫線程的互斥。這樣的實(shí)例很多,例如計(jì)算機(jī)中的許多外設(shè),網(wǎng)絡(luò)中的打印機(jī)等都是共享資源,只允許一個(gè)進(jìn)程或線程使用。10.2.1 多個(gè)線程同時(shí)修改共享數(shù)據(jù)可能發(fā)生錯(cuò)誤【例10.3】下邊的例子模擬2個(gè)線程同時(shí)修改同一個(gè)共享數(shù)據(jù)時(shí)可能發(fā)生的錯(cuò)誤。(1)新建項(xiàng)目。在Form1.cs頭部增加語(yǔ)句:using System.Threading;(2)為Form1類(lèi)定義2個(gè)Thread線程類(lèi)變量:thread1,th

14、read2。定義整形變量:num=0。(3)在窗體中放置一個(gè)標(biāo)簽和按鈕控件,按鈕的事件處理函數(shù)如下:private void button1_Click(object sender,EventArgs e)label1.Text=num.ToString();(4)為Form1類(lèi)構(gòu)造函數(shù)增加語(yǔ)句如下:thread1=new Thread(new ThreadStart(Fun1);thread2=new Thread(new ThreadStart(Fun2);thread1.Start();thread2.Start();(5)為Form1類(lèi)中定義Fun1()方法如下:public void

15、 Fun1()int k,n;for(k=0;k4;k+)n=num;/取出num,可以把把num想象為總?cè)藬?shù) n+;/加1 Thread.Sleep(20);/模擬復(fù)雜的費(fèi)時(shí)運(yùn)算 num=n;/存回num Thread.Sleep(50);/退出該方法,線程結(jié)束public void Fun2()int k,n;for(k=0;k4;k+)n=num;n+;Thread.Sleep(10);num=n;Thread.Sleep(100);(6)編譯運(yùn)行,單擊按鈕,標(biāo)簽控件應(yīng)顯示8,實(shí)際運(yùn)行多次,顯示的數(shù)要小于8。10.2.2 用Lock語(yǔ)句實(shí)現(xiàn)互斥Lock語(yǔ)句的形式如下:lock(e)訪問(wèn)共

16、享資源的代碼。其中e指定要鎖定的對(duì)象,鎖定該對(duì)象內(nèi)所有臨界區(qū),必須是引用類(lèi)型,一般為this。Lock語(yǔ)句將訪問(wèn)共享資源的代碼標(biāo)記為臨界區(qū)。臨界區(qū)的意義是:假設(shè)線程1正在執(zhí)行e對(duì)象的臨界區(qū)中的代碼時(shí),如其他線程也要求執(zhí)行這個(gè)e對(duì)象的任何臨界區(qū)中代碼,將被阻塞,一直到線程1退出臨界區(qū)?!纠?0.4】用C#語(yǔ)句Lock實(shí)現(xiàn)互斥。修改例10.2中的Fun1()和Fun2()方法如下:public void Fun1()int k,n;for(k=0;k4;k+)lock(this)/這里的this是Form1類(lèi)的對(duì)象 n=num;/這對(duì)大括號(hào)中代碼為this的臨界區(qū) /this的臨界區(qū)包含兩部分,n

17、+;/函數(shù)Fun1和Fun2中的臨界區(qū) Thread.Sleep(10);num=n;Thread.Sleep(50);/退出該方法,線程結(jié)束public void Fun2()int k,n;for(k=0;k4;k+)lock(this)/如有線程進(jìn)入此臨界區(qū),n=num;/其他線程就不能進(jìn)入這個(gè)臨界區(qū) /this的臨界區(qū)包含兩部分,n+;/函數(shù)Fun1和Fun2中的臨界區(qū) Thread.Sleep(10);num=n;Thread.Sleep(100);/退出該方法,線程結(jié)束編譯運(yùn)行,單擊按鈕標(biāo)簽控件應(yīng)顯示8。10.3 TCP/IP協(xié)議和Socket本節(jié)首先介紹TCP/IP協(xié)議的基礎(chǔ)知識(shí)

18、,然后介紹Socket類(lèi)的基本概念。10.3.1 TCP/IP協(xié)議協(xié)議 把分布在不同地理區(qū)域的計(jì)算機(jī)和網(wǎng)絡(luò)設(shè)備利用通信設(shè)備互連,使各個(gè)計(jì)算機(jī)之間能夠相互通信,實(shí)現(xiàn)信息和資源共享,就組成了計(jì)算機(jī)網(wǎng)絡(luò)。網(wǎng)絡(luò)的目的是為了通信,共享資源。通信即傳輸數(shù)據(jù),為了傳輸數(shù)據(jù)各個(gè)網(wǎng)絡(luò)系統(tǒng)應(yīng)遵守一定規(guī)則,這個(gè)規(guī)則叫網(wǎng)絡(luò)傳輸協(xié)議。當(dāng)前廣泛采用的網(wǎng)絡(luò)協(xié)議是TCP/IP協(xié)議。網(wǎng)絡(luò)中有成千上萬(wàn)臺(tái)計(jì)算機(jī),應(yīng)允許任何兩臺(tái)計(jì)算機(jī)之間進(jìn)行通信,為了區(qū)分不同的計(jì)算機(jī),必須給每一臺(tái)連網(wǎng)計(jì)算機(jī)一個(gè)唯一的編號(hào),這個(gè)編號(hào)在TCP/IP協(xié)議中叫計(jì)算機(jī)的IP地址,它是一個(gè)32位二進(jìn)制數(shù),用四個(gè)十進(jìn)制數(shù)表示,中間用點(diǎn)隔開(kāi),每個(gè)十進(jìn)制數(shù)允許值為0

19、-255(一個(gè)字節(jié)),例如,202.112.10.105,這種記錄方法叫點(diǎn)數(shù)記法。一個(gè)計(jì)算機(jī)要和網(wǎng)絡(luò)中其他計(jì)算機(jī)連接,必須有自己的IP地址。C#語(yǔ)言使用IPAddress類(lèi)表示IP地址,用靜態(tài)方法Parse可將IP地址字符串轉(zhuǎn)換為IPAddress實(shí)例。例如:/127.0.0.1表示本機(jī)IP地址IPAddress ip=IPAddress.Parse(“127.0.0.1”);IPAddress類(lèi)提供了幾個(gè)靜態(tài)只讀字段,其中字段Any表示本地系統(tǒng)所有可用的IP地址,字段Broadcast表示本地網(wǎng)絡(luò)廣播地址。Dns類(lèi)提供了一系列靜態(tài)的方法,其中GetHostAddresses方法獲取指定主機(jī)的

20、IP地址,返回一個(gè)IPAddress類(lèi)型的數(shù)組(一臺(tái)計(jì)算機(jī)可能有多個(gè)IP地址)。例如獲得CCTV網(wǎng)站的所有IP地址:IPAddress ip=Dns.GetHostAddresses();Dns類(lèi)GetHostName方法,獲取本機(jī)主機(jī)名。string hostname=Dns.GetHostName();IPAddress ip=Dns.GetHostAddresses(hostname);一臺(tái)計(jì)算機(jī)上可能運(yùn)行多個(gè)網(wǎng)絡(luò)通信軟件,它們的IP地址是相同的。為了訪問(wèn)IP地址相同的不同網(wǎng)絡(luò)通信軟件,可為運(yùn)行的每個(gè)網(wǎng)絡(luò)通信軟件編號(hào),這個(gè)編號(hào)叫端口號(hào)。IPEndPoint類(lèi)包含了IP地址和端口信息,IP

21、EndPoint類(lèi)常用的構(gòu)造函數(shù)如下,第一個(gè)參數(shù)指定IP地址,第二個(gè)參數(shù)指定端口號(hào) public IPEndPoint(IPAddress,int);10.3.2 套接字(Socket)套接字可以理解為編寫(xiě)網(wǎng)絡(luò)通信軟件的函數(shù)庫(kù),在套接字中封裝了為進(jìn)行網(wǎng)絡(luò)通信而設(shè)計(jì)的一組公共函數(shù),網(wǎng)絡(luò)通信軟件通過(guò)調(diào)用這些公共函數(shù),完成和在網(wǎng)絡(luò)其他計(jì)算機(jī)中運(yùn)行的指定網(wǎng)絡(luò)通信軟件間的雙向通信。在.Net中,System.Net.Sockets 命名空間為開(kāi)發(fā)人員提供了開(kāi)發(fā)基于Socket套接字的網(wǎng)絡(luò)通信程序的一些類(lèi),包括Socket類(lèi)、TcpClient類(lèi)、TcpListener類(lèi)和UdpClient類(lèi),如果開(kāi)發(fā)基

22、于TCP/IP網(wǎng)絡(luò)協(xié)議網(wǎng)絡(luò)通信程序,可以使用TcpClient類(lèi)、TcpListener類(lèi)和UdpClient類(lèi),使用上比較簡(jiǎn)單,本書(shū)所有例子基本上都是使用這三個(gè)類(lèi)。如果為了提高效率或者采用其他網(wǎng)絡(luò)通信協(xié)議,可采用Socket類(lèi)。套接字有兩種不同的類(lèi)型:一種是流套接字,又稱面向連接的協(xié)議,如 TCP;另一種是數(shù)據(jù)報(bào)套接字,又稱無(wú)連接協(xié)議,例如 UDP?;诹魈捉幼值木W(wǎng)絡(luò)通信采用連接方式,通信前要進(jìn)行網(wǎng)絡(luò)連接,一旦建立了這種連接,就可以在設(shè)備之間可靠的傳輸數(shù)據(jù),建立連接后數(shù)據(jù)以流的形式在被連接的兩個(gè)計(jì)算機(jī)中運(yùn)行程序間進(jìn)行流動(dòng)。這有些像打電話?;诹魈捉幼值木W(wǎng)絡(luò)通信一般采用客戶機(jī)/服務(wù)器模式?;?/p>

23、數(shù)據(jù)報(bào)套接字,采用不連接方式,兩個(gè)計(jì)算機(jī)中運(yùn)行程序間使用單個(gè)信息包進(jìn)行數(shù)據(jù)傳輸,這種方式類(lèi)似郵局,不保證數(shù)據(jù)包按照發(fā)送順序傳送,也可能丟失。以下簡(jiǎn)單介紹Socket類(lèi)的用法,后續(xù)章節(jié)將詳細(xì)介紹TcpClient類(lèi)、TcpListener類(lèi)和UdpClient類(lèi)的使用。Socket類(lèi)的構(gòu)造方法定義如下,其中,addressFamily 參數(shù)指定 Socket 使用的尋址方案,socketType 參數(shù)指定 Socket 的類(lèi)型,protocolType 參數(shù)指定 Socket 使用的協(xié)議。public Socket(AddressFamily addressFamily,SocketType s

24、ocketType,ProtocolType protocolType);生成基于 TCP協(xié)議的Socket類(lèi)對(duì)象的例子如下:Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);一旦創(chuàng)建 基于 TCP協(xié)議連接的Socket類(lèi)對(duì)象,在客戶端將通過(guò)Connect方法連接到指定的服務(wù)器,通過(guò)Send/SendTo方法向遠(yuǎn)程服務(wù)器發(fā)送數(shù)據(jù),通過(guò)Receive/ReceiveFrom從服務(wù)端接收數(shù)據(jù);而在服務(wù)器端,需要使用Bind方法將Socket對(duì)象綁定到本地指定的IP地址和端口號(hào),并通過(guò)

25、Listen方法偵聽(tīng)該接口上的請(qǐng)求,當(dāng)偵聽(tīng)到用戶端的連接時(shí),調(diào)用Accept完成連接的操作,創(chuàng)建新的Socket以處理傳入的連接請(qǐng)求。使用完 Socket 后,使用 Shutdown 方法禁用 Socket,并使用 Close 方法關(guān)閉 Socket。生成基于 UDP協(xié)議的Socket類(lèi)對(duì)象的例子如下:Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);由于UDP不存在固定連接,所以可直接使用SendTo方法發(fā)送數(shù)據(jù),用ReceiveFrom方法接收數(shù)據(jù),如不再使用 Socket對(duì)象

26、,用 Shutdown 方法禁用Socket對(duì)象,用 Close 方法關(guān)閉 Socket對(duì)象。10.4 基于TCP協(xié)議的Socket編程本節(jié)詳細(xì)介紹編寫(xiě)基于基于TCP協(xié)議的Socket程序方法和步驟。在System.Net.Sockets命名空間下,TcpClient類(lèi)與TcpListener類(lèi)是兩個(gè)專(zhuān)門(mén)用于TCP協(xié)議編程的類(lèi)。這兩個(gè)類(lèi)封裝了底層的套接字,并分別提供了對(duì)Socket進(jìn)行封裝后的同步和異步操作的方法,降低了TCP應(yīng)用編程的難度。TcpClient類(lèi)用于連接、發(fā)送和接收數(shù)據(jù)。TcpListener類(lèi)則用于監(jiān)聽(tīng)是否有傳入的連接請(qǐng)求?;赥CP協(xié)議的網(wǎng)絡(luò)通信一般采用客戶機(jī)/服務(wù)器模式,

27、因此必須分別建立客戶機(jī)和服務(wù)器程序。10.4.1 TcpClient類(lèi) 利用TcpClient類(lèi)提供的方法,可以通過(guò)網(wǎng)絡(luò)進(jìn)行連接、發(fā)送和接收網(wǎng)絡(luò)數(shù)據(jù)流。該類(lèi)的構(gòu)造函數(shù)有四種重載形式,常用屬性方法如下:無(wú)參數(shù)構(gòu)造函數(shù):創(chuàng)建一個(gè)TcpClient類(lèi)對(duì)象,該對(duì)象自動(dòng)選擇客戶端IP地址和尚未使用的端口號(hào)。創(chuàng)建該對(duì)象后,即可用Connect方法與服務(wù)器端進(jìn)行連接。例如:TcpClient tcpClient=new TcpClient();tcpClient.Connect(,51888);構(gòu)造函數(shù)New(AddressFamily family):創(chuàng)建的TcpClient類(lèi)對(duì)象也能自動(dòng)選擇客戶端IP地

28、址和尚未使用的端口號(hào),但是使用AddressFamily枚舉指定了使用哪種網(wǎng)絡(luò)協(xié)議。創(chuàng)建該對(duì)象后,即可用Connect方法與服務(wù)器端進(jìn)行連接。例如:TcpClient tcpClient=new TcpClient(AddressFamily.InterNetwork);tcpClient.Connect(,51888);構(gòu)造函數(shù)New(IPEndPoint iep):iep是IPEndPoint類(lèi)的對(duì)象,iep指定了客戶端的IP地址與端口號(hào)。當(dāng)客戶端的主機(jī)有一個(gè)以上的IP地址時(shí),可使用此構(gòu)造函數(shù)選擇要使用的客戶端主機(jī)IP地址。例如:IPAddress address=Dns.GetHostA

29、ddresses(Dns.GetHostName();IPEndPoint iep=new IPEndPoint(address0,51888);TcpClient tcpClient=new TcpClient(iep);tcpClient.Connect(,51888);構(gòu)造函數(shù)New(string hostname,int port):這是使用最方便的一種構(gòu)造函數(shù)。該構(gòu)造函數(shù)可直接指定服務(wù)器端域名和端口號(hào),而且不需使用connect方法??蛻舳酥鳈C(jī)的IP地址和端口號(hào)則自動(dòng)選擇。例如:TcpClient tcpClient=new TcpClient(,51888);方法Connect()

30、:和服務(wù)器進(jìn)行連接,參數(shù)分別是服務(wù)器IP地址和端口號(hào)。方法Close():釋放此 TcpClient 實(shí)例,而不關(guān)閉基礎(chǔ)連接。方法GetStream():返回用于發(fā)送和接收數(shù)據(jù)的 NetworkStream。見(jiàn)后邊例子。屬性SendTimeout和ReceiveTimeout:等待發(fā)送和接收成功完成時(shí)間,超過(guò)這個(gè)時(shí)間,將產(chǎn)生SocketException異常。屬性SendBufferSize和ReceiveBufferSize:發(fā)送和接收緩沖區(qū)大小。屬性Connected:是否已和服務(wù)器連接。屬性Client:TcpClient類(lèi)對(duì)象使用的Socket類(lèi)對(duì)象。10.4.2 TcpListene

31、r類(lèi)TcpListener類(lèi)用于監(jiān)聽(tīng)和接收傳入的連接請(qǐng)求。該類(lèi)的構(gòu)造函數(shù)及常用函數(shù)如下:TcpListener(IPEndPoint iep):該構(gòu)造函數(shù)通過(guò)IPEndPoint類(lèi)型的對(duì)象在指定的IP地址與端口監(jiān)聽(tīng)客戶端連接請(qǐng)求。TcpListener(IPAddress localAddr,int port):建立TcpListener對(duì)象,在參數(shù)中直接指定本機(jī)IP地址和端口,并通過(guò)指定的本機(jī)IP地址和端口號(hào)監(jiān)聽(tīng)傳入的連接請(qǐng)求。AcceptTcpClient():等待連接,直到有新的連接,獲取并返回一個(gè)用來(lái)接收和發(fā)送數(shù)據(jù)的套接字對(duì)象后,才執(zhí)行后續(xù)語(yǔ)句。這種方式稱作同步阻塞方式。AcceptS

32、ocket:在同步阻塞方式下獲取并返回一個(gè)可以用來(lái)接收和發(fā)送數(shù)據(jù)的封裝了Socket的TcpClient對(duì)象。Start():?jiǎn)?dòng)監(jiān)聽(tīng),其構(gòu)造函數(shù)為:Start(int backlog):整型參數(shù)backlog為請(qǐng)求隊(duì)列的最大長(zhǎng)度,即最多允許的客戶端連接個(gè)數(shù)。Stop():停止監(jiān)聽(tīng)請(qǐng)求。10.4.3 服務(wù)器程序 使用TCP和流套接字建立服務(wù)器,服務(wù)器將等待來(lái)自客戶機(jī)的連接請(qǐng)求。在接到請(qǐng)求后,服務(wù)器建立和客戶機(jī)的連接,利用這個(gè)連接,服務(wù)器和客戶機(jī)實(shí)現(xiàn)通信。IE瀏覽器(客戶機(jī))和Web服務(wù)器就是一個(gè)典型的客戶機(jī)/服務(wù)器模式,IE瀏覽器向Web服務(wù)器請(qǐng)求網(wǎng)頁(yè),Web服務(wù)器接到請(qǐng)求,發(fā)送請(qǐng)求的網(wǎng)頁(yè)到I

33、E瀏覽器。VB.Net語(yǔ)言使用TCP和流套接字建立服務(wù)器需要五步。具體步驟如下:(1)System.Net.Sockets命名空間的TcpListener類(lèi)對(duì)象用來(lái)等待來(lái)自客戶機(jī)的連接請(qǐng)求,TcpListener類(lèi)采用TCP協(xié)議。創(chuàng)建TcpListener類(lèi)對(duì)象例子如下:/采用本機(jī)IP地址,端口號(hào)為1300 TcpListener server=new TcpListener(1300);客戶端程序必須知道服務(wù)器的IP地址和端口號(hào),才能和服務(wù)器建立連接。使用如下方法獲得IP地址和端口號(hào),IPEndPoint和IPAddress在System.Net命名空間。IPEndPoint iPEndPo

34、int=server.LocalEndpoint;IPAddress iPAddress=iPEndPoint.Address;int port=iPEndPoint.Port;(2)使用TcpListener類(lèi)方法Start()開(kāi)始等待來(lái)自客戶機(jī)的連接請(qǐng)求,代碼如下:Server.Start()或者采用下條語(yǔ)句Server.Start(200)參數(shù)是允許的最大的連接客戶機(jī)數(shù)(3)使用TcpListener類(lèi)方法AcceptSocket()等待來(lái)自客戶機(jī)的連接請(qǐng)求,如果沒(méi)有客戶機(jī)的連接請(qǐng)求,程序?qū)⒈蛔枞?,既不能?zhí)行這條語(yǔ)句的后續(xù)語(yǔ)句。如果有一個(gè)客戶機(jī)的連接請(qǐng)求,將返回一個(gè)Socket或TcpC

35、lient類(lèi)對(duì)象,將繼續(xù)執(zhí)行后續(xù)語(yǔ)句。代碼如下:/返回Socket類(lèi)對(duì)象,然后執(zhí)行后續(xù)語(yǔ)句 Socket socket=server.AcceptSocket();/或采用本語(yǔ)句返回TcpClient類(lèi)對(duì)象TcpClient tcpClient=server.AcceptTcpClient();得到Socket或TcpClient類(lèi)對(duì)象,已經(jīng)和客戶機(jī)建立了連接,就可以和客戶機(jī)進(jìn)行通信。在通信時(shí),將不再偵聽(tīng)其他客戶機(jī)的連接要求。很多服務(wù)器是不允許這種情況發(fā)生的,例如Web服務(wù)器必須隨時(shí)等待眾多的瀏覽器的訪問(wèn)。解決的方法是建立一個(gè)線程用來(lái)和這個(gè)客戶機(jī)進(jìn)行通信,而TcpListener類(lèi)對(duì)象serv

36、er將繼續(xù)偵聽(tīng)其他客戶機(jī)的連接要求。(4)如果使用server.AcceptSocket方法建立連接,返回的Socket類(lèi)對(duì)象,就可以使用Socket類(lèi)的Send方法發(fā)送數(shù)據(jù)(返回TcpClient用法見(jiàn)下節(jié))。代碼如下:byte msg=Encoding.UTF8.GetBytes(This is a test);int i=socket.Send(msg);/i為發(fā)送數(shù)據(jù)的字節(jié)數(shù) 可以使用Socket類(lèi)的方法Receive接收數(shù)據(jù)(TcpClient用法見(jiàn)下節(jié)),代碼如下:byte bytes=new byte256;i=socket.Receive(bytes);/i為發(fā)送數(shù)據(jù)的字節(jié)數(shù)(

37、5)最后,如果不再通信,使用Socket類(lèi)的Close方法終止連接,代碼如下:Socket.Close()10.4.4 客戶機(jī)程序 網(wǎng)絡(luò)中的計(jì)算機(jī)可以運(yùn)行客戶機(jī)端網(wǎng)絡(luò)程序訪問(wèn)服務(wù)器,例如,通過(guò)IE瀏覽器(客戶機(jī))可以訪問(wèn)Internet中的Web服務(wù)器,瀏覽網(wǎng)頁(yè)。編寫(xiě)運(yùn)行于客戶機(jī)端的網(wǎng)絡(luò)程序程序需要四個(gè)步驟。具體步驟如下:(1)創(chuàng)建System.Net.Sockets命名空間的TcpClient類(lèi)對(duì)象用來(lái)和服務(wù)器建立連接,代碼如下:/自動(dòng)選擇最合適的本地 IP 地址和端口號(hào) TcpClient tcpClient=new TcpClient();/和本機(jī)的服務(wù)器連接tcpClient.Conn

38、ect(localhost,1300);Connect方法的第一個(gè)參數(shù)也可以是遠(yuǎn)程服務(wù)器的域名,例如,“”。如果知道遠(yuǎn)程服務(wù)器的IP地址,可以采用如下代碼:TcpClient tcpClient=new TcpClient();/參數(shù)為遠(yuǎn)程服務(wù)器的IP地址IPAddress ServerIP=IPAddress.Parse(202.206.96.204);tcpClient.Connect(ServerIP,1300)(2)使用TcpClient類(lèi)的GetStream方法得到一個(gè)NetworkStream類(lèi)對(duì)象,用來(lái)對(duì)服務(wù)器進(jìn)行讀寫(xiě)。NetworkStream netStream=tcpCli

39、ent.GetStream();(3)使用NetworkStream類(lèi)對(duì)象讀寫(xiě)服務(wù)器數(shù)據(jù)代碼如下:if(netStream.CanWrite)Byte sendBytes=Encoding.UTF8.GetBytes(Is anybody there?);if(netStream.CanRead)byte bytes=new bytetcpClient.ReceiveBufferSize;netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);string returndata=Encoding.UTF8.GetString(bytes

40、);(4)關(guān)閉NetworkStream類(lèi)對(duì)象后,關(guān)閉和服務(wù)器的連接。netStream.Close()tcpClient.Close()10.4.5 TCP協(xié)議Socket實(shí)例 本節(jié)首先實(shí)現(xiàn)一個(gè)時(shí)間服務(wù)器,客戶端訪問(wèn)這個(gè)時(shí)間服務(wù)器系統(tǒng),可以得到時(shí)間服務(wù)器系統(tǒng)所在地點(diǎn)的時(shí)間,在例子中時(shí)間服務(wù)器直接使用偵聽(tīng)線程和客戶機(jī)通信,因此本例僅支持客戶機(jī)順序訪問(wèn)和多次訪問(wèn),但由于服務(wù)器發(fā)送時(shí)間的代碼很少,很快能夠完成,所以客戶機(jī)程序感覺(jué)沒(méi)有延遲很快就能得到時(shí)間。這是一個(gè)最簡(jiǎn)單的基于TCP協(xié)議的Socket程序?qū)嵗ㄟ^(guò)這個(gè)例子讀者可以清楚地理解Socket編程的基本步驟。實(shí)際服務(wù)器要比這個(gè)時(shí)間服務(wù)器復(fù)雜的

41、多,一般情況下,服務(wù)器和客戶機(jī)通信也許需要較多的時(shí)間,例如客戶機(jī)訪問(wèn)文件下載服務(wù)器下載文件,服務(wù)器直接使用偵聽(tīng)線程和客戶機(jī)通信顯然不能實(shí)現(xiàn)多客戶機(jī)同時(shí)訪問(wèn)服務(wù)器功能。例10.7和例10.8實(shí)現(xiàn)了一個(gè)文件下載系統(tǒng),該系統(tǒng)實(shí)現(xiàn)了多客戶機(jī)同時(shí)訪問(wèn)服務(wù)器功能。【例10.5】本例實(shí)現(xiàn)一個(gè)時(shí)間服務(wù)器,客戶端訪問(wèn)這個(gè)時(shí)間服務(wù)器系統(tǒng),可以得到時(shí)間服務(wù)器系統(tǒng)所在地點(diǎn)的時(shí)間。這是一個(gè)最簡(jiǎn)單的Scoket編程實(shí)例。具體實(shí)現(xiàn)步驟如下:(1)建立一個(gè)新的Windows應(yīng)用項(xiàng)目。在Form1.cs頭部增加命名空間引用:using System.Net;using System.Net.Sockets;using Syst

42、em.Threading;(2)為Form1類(lèi)增加變量:Thread thread;/線程類(lèi)變量 bool ifStop=true;/是否停止時(shí)間服務(wù)器 /負(fù)責(zé)偵聽(tīng)是否有客戶機(jī)訪問(wèn)服務(wù)器 TcpListener server;/服務(wù)器端和客戶機(jī)連接的Socket類(lèi)對(duì)象Socket socket;(3)修改構(gòu)造函數(shù)如下:public Form1()InitializeComponent();/建立偵聽(tīng)線程,TimeThread是線程執(zhí)行的方 /法名稱,退出該方法,線程結(jié)束 thread=new Thread (new ThreadStart(TimeThread);thread.Start();

43、/線程啟動(dòng) ifStop=false;/變量表示是否退出線程,false不退出Text=時(shí)間服務(wù)器;/Form1窗體的標(biāo)題欄內(nèi)容(4)偵聽(tīng)工作不能在主線程中進(jìn)行,否則當(dāng)偵聽(tīng)工作被阻塞后,將不能執(zhí)行其他任何語(yǔ)句,程序看起來(lái)就像死了一樣,不能執(zhí)行任何動(dòng)作。因此偵聽(tīng)工作必須在另一線程中進(jìn)行。在線程為Form1類(lèi)定義一個(gè)偵聽(tīng)線程方法如下,采用本機(jī)IP地址,端口號(hào)為1300。public void TimeThread()try server=new TcpListener(1300);server.Start();/開(kāi)始偵聽(tīng)是否有客戶機(jī)連接服務(wù)器 catch MessageBox.Show(不能建立服

44、務(wù)器,提示,MessageBoxButtons.OK);Return;/原因可能是端口號(hào)1300被占用 /或網(wǎng)絡(luò)不可用,退出線程 while(!ifStop)/如退出while語(yǔ)句,線程結(jié)束 Try /下句等待客戶端的連接 aSocket=server.AcceptSocket()/阻塞 /得到用字符串表示的時(shí)間 string s=DateTime.Now.ToString();/將時(shí)間字符串轉(zhuǎn)換為字節(jié)數(shù)組 byte msg=Encoding.UTF8.GetBytes(s);/本例發(fā)送時(shí)間方法Send和偵聽(tīng)方法/AcceptSocket()在同一線程。在發(fā)送時(shí)間時(shí)不能/繼續(xù)偵聽(tīng)是否有客戶機(jī)連

45、接服務(wù)器。本例發(fā)送/數(shù)據(jù)較少,發(fā)送后很快開(kāi)始偵聽(tīng),基本不影響/其他客戶機(jī)的連接。本方法支持客戶機(jī)順序訪/問(wèn)和多次訪問(wèn)。如果發(fā)送數(shù)據(jù)較多占用較多時(shí)/間或者客戶機(jī)要長(zhǎng)時(shí)間和服務(wù)器連接,必須建/立新線程用來(lái)發(fā)送數(shù)據(jù)使偵聽(tīng)可以繼續(xù),見(jiàn)后/續(xù)例子。發(fā)送時(shí)間到客戶機(jī),完成之前被阻塞/完成后執(zhí)行后續(xù)語(yǔ)句。aSocket.Send(msg)aSocket.Close()送出時(shí)間后關(guān)閉和客戶機(jī)的連接/退出前,要使ifStop=true,關(guān)閉socket和server,/如果這兩個(gè)對(duì)象正在使用必定產(chǎn)生異常,執(zhí)行catch中/語(yǔ)句,繼續(xù)while循環(huán),由于ifStop=true,將退出while/循環(huán)語(yǔ)句,即退出T

46、imeThread方法結(jié)束線程。/如果僅僅是在程序運(yùn)行時(shí),socket=/server.AcceptSocket()或socket.Send(msg)語(yǔ)句發(fā)生/異常,由于ifStop=false,僅僅重新開(kāi)始偵聽(tīng)。catch if(socket!=null)socket.Close();/關(guān)閉關(guān)閉和客戶機(jī)的連接 if(socket!=null)/運(yùn)行到此,線程將結(jié)束 socket.Close();/關(guān)閉關(guān)閉和客戶機(jī)的連接 server.Stop();/關(guān)閉TcpListener類(lèi)對(duì)象取消偵聽(tīng)/運(yùn)行到此,線程將結(jié)束,要關(guān)閉所有建立的對(duì)象 (5)在關(guān)閉程序之前,必須撤銷(xiāo)線程對(duì)象。為主窗體的Clos

47、ing事件增加事件處理函數(shù)如下:private void Form1_FormClosing(object sender,FormClosingEventArgs e)ifStop=true;if(socket!=null)socket.Close();if(server!=null)server.Stop();if(thread!=null&thread.IsAlive)thread.Abort();(6)編譯得到可執(zhí)行文件。請(qǐng)注意,所建立的時(shí)間服務(wù)器必須在另一個(gè)線程中運(yùn)行,而不能在主線程中,否則主線程將不會(huì)響應(yīng)用戶的任何動(dòng)作,包括關(guān)閉程序。這是由于函數(shù)TimeThread()中包括一個(gè)死循

48、環(huán),如在主線程中運(yùn)行,將占用主線程的所有時(shí)間,沒(méi)有時(shí)間去運(yùn)行其他代碼。讀者可以試驗(yàn)一下,修改上例,首先去掉構(gòu)造函數(shù)中自己增加的語(yǔ)句,然后增加一個(gè)按鈕,為按鈕增加單擊事件處理函數(shù),在函數(shù)中,調(diào)用函數(shù)TimeThread(),編譯運(yùn)行后,單擊按鈕,程序可以得到時(shí)間,但是將不能使用關(guān)閉按鈕關(guān)閉程序?!纠?0.6】本例實(shí)現(xiàn)客戶機(jī)從例10.5的時(shí)間服務(wù)器得到時(shí)間并顯示。具體步驟如下:(1)建立一個(gè)新的Windows應(yīng)用項(xiàng)目。在Form1.cs頭部增加命名空間引用:using System.Net;using System.Net.Sockets;(2)為Form1類(lèi)增加變量:TcpClient tcpC

49、lient;/客戶機(jī)類(lèi)對(duì)象/網(wǎng)絡(luò)流對(duì)象,流的概念參見(jiàn)第9章NetworkStream netStream;(3)在窗體Form1中放置1個(gè)Label控件用來(lái)顯示時(shí)間,增加1個(gè)Button控件,標(biāo)題為“得到時(shí)間”,按鈕的單擊事件函數(shù)如下。接受應(yīng)在另一線程,為了簡(jiǎn)單接受也在主線程(按鈕的單擊事件函數(shù)中),為防止無(wú)限等待,設(shè)定超時(shí)時(shí)間,超時(shí)發(fā)異常,5秒未接到時(shí)間數(shù)據(jù),引發(fā)異常。下句自動(dòng)選擇本地 IP 地址和端口號(hào)。private void button1_Click(object sender,EventArgs e)aTcpClient=New TcpClient()aTcpClient.Rece

50、iveTimeout=5000/設(shè)定超時(shí)時(shí)間5秒/localhost表示程序所在計(jì)算機(jī)的服務(wù)器,這樣設(shè)置服務(wù)/器和客戶機(jī)在同一臺(tái)計(jì)算機(jī),連接成功之前被阻塞,/成功后執(zhí)行下條語(yǔ)句,5秒后仍未連接成功,拋出異常。/1300為時(shí)間服務(wù)器端口號(hào) try tcpClient.Connect(localhost,1300);netStream=tcpClient.GetStream();if(netStream.CanRead)/判斷數(shù)據(jù)否支持讀取 /發(fā)來(lái)的是字節(jié)數(shù)組,定義字節(jié)數(shù)組保存接收的數(shù)據(jù) byte bytes=new bytetcpClient.ReceiveBufferSize;/開(kāi)始讀服務(wù)器發(fā)

51、回的時(shí)間,接受成功前被阻塞,/成功后執(zhí)行下條語(yǔ)句,5秒未讀出數(shù)據(jù)拋出異常 netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);label1.Text=Encoding.UTF8.GetString(bytes);catch label1.Text=連接超時(shí),連接不成功;Finally if(netStream!=null)netStream.Close();tcpClient.Close();(4)首先運(yùn)行時(shí)間服務(wù)器程序,再運(yùn)行客戶機(jī)程序,單擊客戶機(jī)程序的標(biāo)題為“得到時(shí)間”按鈕,顯示當(dāng)前時(shí)間。關(guān)閉時(shí)間服務(wù)器程序,再一次單擊客戶機(jī)程序

52、的標(biāo)題為“得到時(shí)間”按鈕,顯示連接超時(shí),連接不成功。在網(wǎng)絡(luò)應(yīng)用程序中,經(jīng)常傳送文件。從上邊的例子可以看到,在網(wǎng)路中使用字節(jié)數(shù)組進(jìn)行傳送,因此傳送文件,首先要把文件變?yōu)樽止?jié)數(shù)組,接收文件,則必須把字節(jié)數(shù)組變?yōu)槲募?。文件變?yōu)樽止?jié)數(shù)組的具體步驟如下:FileStream fs=參數(shù)1是要傳輸?shù)奈募?New FileStream(d:/g1.bin,FileMode.Open)byte data=new bytefs.Length;/將文件讀到字節(jié)數(shù)組data中,n為所讀字節(jié)數(shù)long n=fs.Read(data,0,(int)fs.Length);fs.Close();字節(jié)數(shù)組變?yōu)槲募唧w步驟如下

53、:FileStream fs=/參數(shù)1是保存文件全路徑 new FileStream(d:/g1.bin,FileMode.Create)/寫(xiě)data字節(jié)數(shù)組中的所有數(shù)據(jù)到文件fs.Write(data,0,data.Length)fs.Close()如果創(chuàng)建一個(gè)文件下載服務(wù)器,客戶機(jī)就可以訪問(wèn)文件下載服務(wù)器下載文件。下載文件的時(shí)間一般比較長(zhǎng),因此當(dāng)客戶機(jī)和文件下載服務(wù)器建立連接后,下載文件的工作必須在另一個(gè)線程中進(jìn)行,以便文件下載服務(wù)器可以繼續(xù)偵聽(tīng)工作,等待其他客戶機(jī)的連接,使文件下載服務(wù)器允許多個(gè)客戶機(jī)同時(shí)下載文件。在客戶機(jī)中,接收文件下載服務(wù)器傳輸?shù)奈募脖仨氃诹硪痪€程中,否則,接收過(guò)程

54、將占用主線程的所有時(shí)間,主線程不能響應(yīng)其他任何事件,包括關(guān)閉窗體。Socket類(lèi)讀寫(xiě)緩沖區(qū)的大小是一定的,而文件大小可能超過(guò)讀寫(xiě)緩沖區(qū)的大小,如使用Socket類(lèi)Send方法發(fā)送文件,可能要寫(xiě)多次才能完成,很不方便。可以使用NetworkStream的Write方法寫(xiě)文件,調(diào)用一次Write方法就完成文件文件傳送。【例10.7】本例實(shí)現(xiàn)文件下載服務(wù)器,客戶機(jī)可以訪問(wèn)文件下載服務(wù)器下載文件。為了簡(jiǎn)單,客戶機(jī)和下載文件服務(wù)器建立連接后,立刻傳遞一個(gè)指定文件到客戶端。步驟如下:(1)建立一個(gè)新的Windows應(yīng)用項(xiàng)目。在Form1.vb頭部增加命名空間引用:using System.Net;usin

55、g System.Net.Sockets;using System.Threading;using System.IO;/讀寫(xiě)文件必須引用的命名空間(2)為Form1類(lèi)增加變量:/線程類(lèi)變量,分別引用偵聽(tīng)線程和下載線程private Thread Listenerthread,DownLoadthread;bool ifStop=true;/是否停止下載服務(wù)器/負(fù)責(zé)偵聽(tīng)是否有客戶機(jī)訪問(wèn)服務(wù)器TcpListener server;/服務(wù)器端和客戶機(jī)連接的TcpClient類(lèi)對(duì)象TcpClient socket;/服務(wù)器端和客戶機(jī)連接后,得到TcpClient類(lèi)對(duì)/象,將創(chuàng)建下載線程和流對(duì)象程序關(guān)

56、閉前,必/須關(guān)閉TcpClient類(lèi)對(duì)象、下載線程和流對(duì)象,/本結(jié)構(gòu)用來(lái)記錄這些信息public struct DownLoadthreadObject public Thread thread;/下載線程public TcpClient tcpClient;/TcpClient類(lèi)對(duì)象public NetworkStream networkStream;/流對(duì)象/記錄所有服務(wù)器端和客戶機(jī)連接信息List downLoadthreadObjectS;(3)修改構(gòu)造函數(shù)如下:public Form1()/建立偵聽(tīng)線程,ListenerthreadMethod是線程執(zhí) /行的方法名稱,退出該方法,線

57、程結(jié)束 Listenerthread=new Thread(new ThreadStart(ListenerthreadMethod);Listenerthread.Start();/偵聽(tīng)線程啟動(dòng) ifStop=false;/為false表示不退出偵聽(tīng)線程 Text=文件下載服務(wù)器“;/窗體的標(biāo)題欄內(nèi)容 downLoadthreadObjectS=new List();(4)為Form1類(lèi)定義一個(gè)線程方法如下:public void ListenerthreadMethod()/線程執(zhí)行的方法 Try /下句采用本機(jī)IP地址,端口號(hào)為1300 server=New TcpListener(13

58、00)server.Start()/開(kāi)始偵聽(tīng)是否有客戶機(jī)連接服務(wù)器 catch MessageBox.Show(不能建立服務(wù)器,提示,MessageBoxButtons.OK);Return;while(!ifStop)try /未偵聽(tīng)到客戶機(jī)前被阻塞,成功后執(zhí)行后續(xù)語(yǔ)句 aSocket=server.AcceptTcpClient();DownLoadthread=New Thread(下載線程 New ThreadStart(AddressOf ClientThreadF);DownLoadthread.Start();Thread.Sleep(100)/等待ClientThreadF正常

59、工作 catch /不處理異常,退出線程方法 if(socket!=null)socket.Close();server.Stop();/關(guān)閉TcpListener類(lèi)對(duì)象取消偵聽(tīng) (5)為Form1類(lèi)定義ClientThreadF如下,每連接一個(gè)客戶,建立一個(gè)下載線程。下載線程負(fù)責(zé)發(fā)送文件。public void ClientThreadF()FileStream fs=null;DownLoadthreadObject downLoadthreadObject=new DownLoadthreadObject();downLoadthreadObject.thread=DownLoadthr

60、ead;downLoadthreadObject.tcpClient=socket;try/下條語(yǔ)句得到網(wǎng)路流對(duì)象 NetworkStream netStream=downLoadthreadObject.tcpClient.GetStream();downLoadthreadOworkStream=netStream;lock(this)downLoadthreadObjectS.Add(downLoadthreadObject);/下句參數(shù)1是要傳輸?shù)奈募?fs=new FileStream(d:/g1.txt,FileMode.Open);byte data=new bytefs.Len

61、gth;fs.Read(data,0,(int)fs.Length);/讀文件到字節(jié)數(shù)組 fs.Close();if(netStream.CanWrite)netStream.Write(data,0,data.Length);catch/不處理異常,退出線程方法 finally if(fs!=null)fs.Close();if(downLoadthreadOworkStream!=null)downLoadthreadOworkStream.Close();if(downLoadthreadObject.tcpClient!=null)downLoadthreadObject.tcpCli

62、ent.Close();lock(this)downLoadthreadObjectS.Remove(downLoadthreadObject);(6)在關(guān)閉程序之前,必須撤銷(xiāo)線程對(duì)象。為主窗體的Closing事件增加事件處理函數(shù)如下:private void Form1_FormClosing(object sender,FormClosingEventArgs e)ifStop=true;foreach(DownLoadthreadObject DLO in downLoadthreadObjectS)if(DLO.networkStream!=null)DLO.networkStream

63、.Close();if(DLO.tcpClient!=null)DLO.tcpClient.Close();if(DLO.thread!=null&DLO.thread.IsAlive)DLO.thread.Abort();if(server!=null)server.Stop();if(Listenerthread!=null&Listenerthread.IsAlive)Listenerthread.Abort();(7)編譯得到可執(zhí)行文件?!纠?0.8】本例實(shí)現(xiàn)客戶機(jī),從例10.7的文件下載服務(wù)器下載文件。具體步驟如下:(1)建立一個(gè)新的Windows應(yīng)用項(xiàng)目。在Form1.cs頭部增加

64、命名空間引用:using System.Net;using System.Net.Sockets;using System.Threading;using System.IO;(2)為Form1類(lèi)增加變量:TcpClient tcpClient;/客戶機(jī)類(lèi)對(duì)象NetworkStream netStream;/網(wǎng)絡(luò)流對(duì)象Thread DownLoadthread;/下載線程類(lèi)變量(3)在窗體Form1中放置1個(gè)Button控件,標(biāo)題為“下載文件”,事件函數(shù)如下:private void button1_Click(object sender,EventArgs e)tcpClient=new T

65、cpClient();DownLoadthread=new Thread(new ThreadStart(ClientThreadF);/下載線程DownLoadthread.Start();(4)為Form1類(lèi)定義ClientThreadF如下,負(fù)責(zé)接收文件。public void ClientThreadF()/字節(jié)數(shù)組保存接收的數(shù)據(jù) byte bytes=new ytetcpClient.ReceiveBufferSize;FileStream fs=null;List data=new List();int n=0;try /localhost表示程序所在計(jì)算機(jī)的服務(wù)器,這樣設(shè) /置服

66、務(wù)器和客戶機(jī)在同一臺(tái)計(jì)算機(jī),連接成功之前 /被阻塞,成功后執(zhí)行后續(xù)語(yǔ)句。/1300為時(shí)間服務(wù)器端口號(hào) aTcpClient.Connect(localhost,1300)netStream=aTcpClient.GetStream()if(netStream.CanRead)/判斷數(shù)據(jù)是否可讀 do/不知文件大小,多次讀入數(shù)據(jù),直到讀完所有數(shù)據(jù) n=netStream.Read(bytes,0,(int)tcpClient.ReceiveBufferSize);if(n!=0)/如果讀入的字節(jié)數(shù)不為0 data.AddRange(bytes);/保存這次讀入的數(shù)據(jù) while(netStream.DataAvailable);/是否還有數(shù)據(jù) fs=new FileStream(d:/g2.txt,FileMode.Create);byte bytes1=data.ToArray();/轉(zhuǎn)換為數(shù)組 fs.Write(bytes1,0,bytes1.Length);/寫(xiě)入文件 catch MessageBox.Show(下載失敗,提示,MessageBoxButtons.OK);final

展開(kāi)閱讀全文
溫馨提示:
1: 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
2: 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
3.本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
5. 裝配圖網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

相關(guān)資源

更多
正為您匹配相似的精品文檔
關(guān)于我們 - 網(wǎng)站聲明 - 網(wǎng)站地圖 - 資源地圖 - 友情鏈接 - 網(wǎng)站客服 - 聯(lián)系我們

copyright@ 2023-2025  zhuangpeitu.com 裝配圖網(wǎng)版權(quán)所有   聯(lián)系電話:18123376007

備案號(hào):ICP2024067431號(hào)-1 川公網(wǎng)安備51140202000466號(hào)


本站為文檔C2C交易模式,即用戶上傳的文檔直接被用戶下載,本站只是中間服務(wù)平臺(tái),本站所有文檔下載所得的收益歸上傳人(含作者)所有。裝配圖網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)上載內(nèi)容本身不做任何修改或編輯。若文檔所含內(nèi)容侵犯了您的版權(quán)或隱私,請(qǐng)立即通知裝配圖網(wǎng),我們立即給予刪除!