C應用程序設計教程 第10章 多線程和Scket編程初步

上傳人:仙*** 文檔編號:48282479 上傳時間:2022-01-02 格式:PPT 頁數(shù):144 大?。?16.52KB
收藏 版權申訴 舉報 下載
C應用程序設計教程 第10章 多線程和Scket編程初步_第1頁
第1頁 / 共144頁
C應用程序設計教程 第10章 多線程和Scket編程初步_第2頁
第2頁 / 共144頁
C應用程序設計教程 第10章 多線程和Scket編程初步_第3頁
第3頁 / 共144頁

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

10 積分

下載資源

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

資源描述:

《C應用程序設計教程 第10章 多線程和Scket編程初步》由會員分享,可在線閱讀,更多相關《C應用程序設計教程 第10章 多線程和Scket編程初步(144頁珍藏版)》請在裝配圖網上搜索。

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

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

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

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

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

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

7、m1類中聲明一個委托類dFun、定義一個類dFun的變量和線程類變量:/dFun類可代表無返回值有一個string參數(shù)方法delegate void dFun(string text);/dFun類變量dFun dFun1; /線程類變量private Thread thread; (4)為標題為“新線程”的按鈕(button1)增加單擊事件處理函數(shù)如下:private void button1_Click(object sender, EventArgs e) /生成線程類對象,fun為自定義方法名稱 thread=new Thread(new ThreadStart(fun); Label

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

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

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

11、ivate void SetText(string text) label1.Text = text; (8)在Form1類的Load事件函數(shù)的最后增加如下語句: dFun1=new dFun(SetText);(9)在關閉程序之前,必須撤銷線程對象。為主窗體的FormClosing事件增加事件處理函數(shù)如下:private void Form1_FormClosing(object sender, FormClosingEventArgs e) if(thread.IsAlive) thread.Abort(); (10)編譯運行,單擊標題為新線程的按鈕,新線程開始,計數(shù)器從0開始計數(shù)。單擊標

12、題為撤銷的按鈕,線程對象被撤銷,計數(shù)器停止計數(shù)。【例10.2】本例重做例9.19,查找文件在另一個線程中進行,當單擊“停止搜索”按鈕后,停止搜索線程,以便停止查找文件。本例修改例9.19。請同學課后自己完成。10.2 多個線程互斥多個線程同時修改共享數(shù)據(jù)可能發(fā)生錯誤。假設2個線程分別監(jiān)視2個入口進入的人數(shù),每當有人通過入口,線程用C#語句對總人數(shù)變量執(zhí)行加1操作。一條C#語句可能包含若干機器語言語句,假設C#語句加1操作包含的機器語言語句是:取總人數(shù),加1,再存回。操作系統(tǒng)可以在一條機器語言語句結束后,掛起運行的線程。如當前總人數(shù)為5,線程1運行,監(jiān)視到有人通過入口,取出總人數(shù)(=5)后,線程

13、1時間用完掛起。線程2喚醒,也監(jiān)視到有人通過入口,并完成了總人數(shù)加1并送回的操作,總人數(shù)為6,線程2掛起。線程1喚醒,對已取出的總人數(shù)(此時為5)加1,存回去,總人數(shù)應為7,實為6,少算一個。 為了防止此類錯誤,在一個線程修改共享資源(例如上例的總人數(shù)變量)時,不允許其他線程對同一共享資源進行修改,這叫線程的互斥。這樣的實例很多,例如計算機中的許多外設,網絡中的打印機等都是共享資源,只允許一個進程或線程使用。10.2.1 多個線程同時修改共享數(shù)據(jù)可能發(fā)生錯誤【例10.3】下邊的例子模擬2個線程同時修改同一個共享數(shù)據(jù)時可能發(fā)生的錯誤。(1)新建項目。在Form1.cs頭部增加語句:using S

14、ystem.Threading;(2)為Form1類定義2個Thread線程類變量:thread1,thread2。定義整形變量:num=0。(3)在窗體中放置一個標簽和按鈕控件,按鈕的事件處理函數(shù)如下:private void button1_Click(object sender, EventArgs e) label1.Text = num.ToString(); (4)為Form1類構造函數(shù)增加語句如下: thread1= new Thread(new ThreadStart(Fun1);thread2= new Thread(new ThreadStart(Fun2);thread1

15、.Start();thread2.Start();(5)為Form1類中定義Fun1() 方法如下:public void Fun1() int k,n; for(k=0;k4;k+) n=num; /取出num,可以把把num想象為總人數(shù) n+;/加1 Thread.Sleep(20); /模擬復雜的費時運算 num=n;/存回num Thread.Sleep(50); /退出該方法,線程結束public void Fun2() int k,n; for(k=0;k4;k+) n=num; n+; Thread.Sleep(10); num=n; Thread.Sleep(100); (6)

16、編譯運行,單擊按鈕,標簽控件應顯示8,實際運行多次,顯示的數(shù)要小于8。10.2.2 用Lock語句實現(xiàn)互斥Lock語句的形式如下:lock(e)訪問共享資源的代碼。其中e指定要鎖定的對象,鎖定該對象內所有臨界區(qū),必須是引用類型,一般為this。Lock語句將訪問共享資源的代碼標記為臨界區(qū)。臨界區(qū)的意義是:假設線程1正在執(zhí)行e對象的臨界區(qū)中的代碼時,如其他線程也要求執(zhí)行這個e對象的任何臨界區(qū)中代碼,將被阻塞,一直到線程1退出臨界區(qū)。 【例10.4】用C#語句Lock實現(xiàn)互斥。修改例10.2中的Fun1()和Fun2()方法如下: public void Fun1() int k,n; for(k

17、=0;k4;k+) lock(this)/這里的this是Form1類的對象 n=num; /這對大括號中代碼為this的臨界區(qū) /this的臨界區(qū)包含兩部分, n+; /函數(shù)Fun1和Fun2中的臨界區(qū) Thread.Sleep(10); num=n; Thread.Sleep(50); /退出該方法,線程結束public void Fun2() int k,n; for(k=0;k4;k+) lock(this)/如有線程進入此臨界區(qū), n=num; /其他線程就不能進入這個臨界區(qū) /this的臨界區(qū)包含兩部分, n+; /函數(shù)Fun1和Fun2中的臨界區(qū) Thread.Sleep(10)

18、; num=n; Thread.Sleep(100); /退出該方法,線程結束編譯運行,單擊按鈕標簽控件應顯示8。 10.3 TCP/IP協(xié)議和Socket本節(jié)首先介紹TCP/IP協(xié)議的基礎知識,然后介紹Socket類的基本概念。 10.3.1 TCP/IP協(xié)議協(xié)議 把分布在不同地理區(qū)域的計算機和網絡設備利用通信設備互連,使各個計算機之間能夠相互通信,實現(xiàn)信息和資源共享,就組成了計算機網絡。網絡的目的是為了通信,共享資源。通信即傳輸數(shù)據(jù),為了傳輸數(shù)據(jù)各個網絡系統(tǒng)應遵守一定規(guī)則,這個規(guī)則叫網絡傳輸協(xié)議。當前廣泛采用的網絡協(xié)議是TCP/IP協(xié)議。 網絡中有成千上萬臺計算機,應允許任何兩臺計算機之間

19、進行通信,為了區(qū)分不同的計算機,必須給每一臺連網計算機一個唯一的編號,這個編號在TCP/IP協(xié)議中叫計算機的IP地址,它是一個32位二進制數(shù),用四個十進制數(shù)表示,中間用點隔開,每個十進制數(shù)允許值為0-255(一個字節(jié)),例如,202.112.10.105,這種記錄方法叫點數(shù)記法。一個計算機要和網絡中其他計算機連接,必須有自己的IP地址。C#語言使用IPAddress類表示IP地址,用靜態(tài)方法Parse可將IP地址字符串轉換為IPAddress實例。例如:/127.0.0.1表示本機IP地址IPAddress ip = IPAddress.Parse(“127.0.0.1”); IPAddres

20、s類提供了幾個靜態(tài)只讀字段,其中字段Any表示本地系統(tǒng)所有可用的IP地址,字段Broadcast表示本地網絡廣播地址。 Dns類提供了一系列靜態(tài)的方法,其中GetHostAddresses方法獲取指定主機的IP地址,返回一個IPAddress類型的數(shù)組(一臺計算機可能有多個IP地址)。 例如獲得CCTV網站的所有IP地址:IPAddress ip=Dns.GetHostAddresses(); Dns類GetHostName方法,獲取本機主機名。 string hostname = Dns.GetHostName(); IPAddress ip=Dns.GetHostAddresses(hos

21、tname);一臺計算機上可能運行多個網絡通信軟件,它們的IP地址是相同的。為了訪問IP地址相同的不同網絡通信軟件,可為運行的每個網絡通信軟件編號,這個編號叫端口號。 IPEndPoint類包含了IP地址和端口信息,IPEndPoint類常用的構造函數(shù)如下,第一個參數(shù)指定IP地址,第二個參數(shù)指定端口號 public IPEndPoint(IPAddress, int); 10.3.2 套接字(Socket)套接字可以理解為編寫網絡通信軟件的函數(shù)庫,在套接字中封裝了為進行網絡通信而設計的一組公共函數(shù),網絡通信軟件通過調用這些公共函數(shù),完成和在網絡其他計算機中運行的指定網絡通信軟件間的雙向通信。在

22、.Net中,System.Net.Sockets 命名空間為開發(fā)人員提供了開發(fā)基于Socket套接字的網絡通信程序的一些類,包括Socket類、TcpClient類、TcpListener類和UdpClient類,如果開發(fā)基于TCP/IP網絡協(xié)議網絡通信程序,可以使用TcpClient類、TcpListener類和UdpClient類,使用上比較簡單,本書所有例子基本上都是使用這三個類。如果為了提高效率或者采用其他網絡通信協(xié)議,可采用Socket類。 套接字有兩種不同的類型:一種是流套接字,又稱面向連接的協(xié)議,如 TCP;另一種是數(shù)據(jù)報套接字,又稱無連接協(xié)議,例如 UDP。基于流套接字的網絡通

23、信采用連接方式,通信前要進行網絡連接,一旦建立了這種連接,就可以在設備之間可靠的傳輸數(shù)據(jù),建立連接后數(shù)據(jù)以流的形式在被連接的兩個計算機中運行程序間進行流動。這有些像打電話。基于流套接字的網絡通信一般采用客戶機/服務器模式。基于數(shù)據(jù)報套接字,采用不連接方式,兩個計算機中運行程序間使用單個信息包進行數(shù)據(jù)傳輸,這種方式類似郵局,不保證數(shù)據(jù)包按照發(fā)送順序傳送,也可能丟失。以下簡單介紹Socket類的用法,后續(xù)章節(jié)將詳細介紹TcpClient類、TcpListener類和UdpClient類的使用。Socket類的構造方法定義如下,其中,addressFamily 參數(shù)指定 Socket 使用的尋址方案

24、,socketType 參數(shù)指定 Socket 的類型,protocolType 參數(shù)指定 Socket 使用的協(xié)議。 public Socket(AddressFamily addressFamily,SocketType socketType, ProtocolType protocolType);生成基于 TCP協(xié)議的Socket類對象的例子如下: Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 一旦創(chuàng)建 基于 TCP協(xié)議連接的Socket類對象,在客戶端將通過

25、Connect方法連接到指定的服務器,通過Send/SendTo方法向遠程服務器發(fā)送數(shù)據(jù),通過Receive/ReceiveFrom從服務端接收數(shù)據(jù);而在服務器端,需要使用Bind方法將Socket對象綁定到本地指定的IP地址和端口號,并通過Listen方法偵聽該接口上的請求,當偵聽到用戶端的連接時,調用Accept完成連接的操作,創(chuàng)建新的Socket以處理傳入的連接請求。使用完 Socket 后,使用 Shutdown 方法禁用 Socket,并使用 Close 方法關閉 Socket。 生成基于 UDP協(xié)議的Socket類對象的例子如下: Socket s = new Socket(Add

26、ressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 由于UDP不存在固定連接,所以可直接使用SendTo方法發(fā)送數(shù)據(jù),用ReceiveFrom方法接收數(shù)據(jù),如不再使用 Socket對象,用 Shutdown 方法禁用Socket對象,用 Close 方法關閉 Socket對象。10.4 基于TCP協(xié)議的Socket編程本節(jié)詳細介紹編寫基于基于TCP協(xié)議的Socket程序方法和步驟。在System.Net.Sockets命名空間下,TcpClient類與TcpListener類是兩個專門用于TCP協(xié)議編程的類。這兩個類封裝了

27、底層的套接字,并分別提供了對Socket進行封裝后的同步和異步操作的方法,降低了TCP應用編程的難度。TcpClient類用于連接、發(fā)送和接收數(shù)據(jù)。TcpListener類則用于監(jiān)聽是否有傳入的連接請求。基于TCP協(xié)議的網絡通信一般采用客戶機/服務器模式,因此必須分別建立客戶機和服務器程序。10.4.1 TcpClient類 利用TcpClient類提供的方法,可以通過網絡進行連接、發(fā)送和接收網絡數(shù)據(jù)流。該類的構造函數(shù)有四種重載形式,常用屬性方法如下: 無參數(shù)構造函數(shù):創(chuàng)建一個TcpClient類對象,該對象自動選擇客戶端IP地址和尚未使用的端口號。創(chuàng)建該對象后,即可用Connect方法與服務

28、器端進行連接。例如:TcpClient tcpClient=new TcpClient();tcpClient.Connect(, 51888); 構造函數(shù)New(AddressFamily family):創(chuàng)建的TcpClient類對象也能自動選擇客戶端IP地址和尚未使用的端口號,但是使用AddressFamily枚舉指定了使用哪種網絡協(xié)議。創(chuàng)建該對象后,即可用Connect方法與服務器端進行連接。例如:TcpClient tcpClient = new TcpClient(AddressFamily.InterNetwork);tcpClient.Connect(, 51888); 構造函

29、數(shù)New(IPEndPoint iep):iep是IPEndPoint類的對象,iep指定了客戶端的IP地址與端口號。當客戶端的主機有一個以上的IP地址時,可使用此構造函數(shù)選擇要使用的客戶端主機IP地址。例如:IPAddress address = Dns.GetHostAddresses(Dns.GetHostName();IPEndPoint iep = new IPEndPoint(address0, 51888);TcpClient tcpClient = new TcpClient(iep);tcpClient.Connect(, 51888); 構造函數(shù)New(string hos

30、tname,int port):這是使用最方便的一種構造函數(shù)。該構造函數(shù)可直接指定服務器端域名和端口號,而且不需使用connect方法??蛻舳酥鳈C的IP地址和端口號則自動選擇。例如:TcpClient tcpClient=new TcpClient(, 51888); 方法Connect():和服務器進行連接,參數(shù)分別是服務器IP地址和端口號。 方法Close():釋放此 TcpClient 實例,而不關閉基礎連接。 方法GetStream():返回用于發(fā)送和接收數(shù)據(jù)的 NetworkStream。見后邊例子。 屬性SendTimeout和ReceiveTimeout:等待發(fā)送和接收成功完成時

31、間,超過這個時間,將產生SocketException異常。 屬性SendBufferSize和ReceiveBufferSize:發(fā)送和接收緩沖區(qū)大小。 屬性Connected:是否已和服務器連接。 屬性Client:TcpClient類對象使用的Socket類對象。10.4.2 TcpListener類TcpListener類用于監(jiān)聽和接收傳入的連接請求。該類的構造函數(shù)及常用函數(shù)如下: TcpListener(IPEndPoint iep):該構造函數(shù)通過IPEndPoint類型的對象在指定的IP地址與端口監(jiān)聽客戶端連接請求。 TcpListener(IPAddress localAddr

32、, int port):建立TcpListener對象,在參數(shù)中直接指定本機IP地址和端口,并通過指定的本機IP地址和端口號監(jiān)聽傳入的連接請求。 AcceptTcpClient():等待連接,直到有新的連接,獲取并返回一個用來接收和發(fā)送數(shù)據(jù)的套接字對象后,才執(zhí)行后續(xù)語句。這種方式稱作同步阻塞方式。 AcceptSocket:在同步阻塞方式下獲取并返回一個可以用來接收和發(fā)送數(shù)據(jù)的封裝了Socket的TcpClient對象。 Start():啟動監(jiān)聽,其構造函數(shù)為: Start(int backlog):整型參數(shù)backlog為請求隊列的最大長度,即最多允許的客戶端連接個數(shù)。 Stop():停止監(jiān)

33、聽請求。10.4.3 服務器程序 使用TCP和流套接字建立服務器,服務器將等待來自客戶機的連接請求。在接到請求后,服務器建立和客戶機的連接,利用這個連接,服務器和客戶機實現(xiàn)通信。IE瀏覽器(客戶機)和Web服務器就是一個典型的客戶機/服務器模式,IE瀏覽器向Web服務器請求網頁,Web服務器接到請求,發(fā)送請求的網頁到IE瀏覽器。VB.Net語言使用TCP和流套接字建立服務器需要五步。具體步驟如下:(1)System.Net.Sockets命名空間的TcpListener類對象用來等待來自客戶機的連接請求,TcpListener類采用TCP協(xié)議。創(chuàng)建TcpListener類對象例子如下: /采用

34、本機IP地址,端口號為1300 TcpListener server = new TcpListener(1300); 客戶端程序必須知道服務器的IP地址和端口號,才能和服務器建立連接。使用如下方法獲得IP地址和端口號,IPEndPoint和IPAddress在System.Net命名空間。IPEndPoint iPEndPoint = server.LocalEndpoint;IPAddress iPAddress = iPEndPoint.Address;int port = iPEndPoint.Port;(2)使用TcpListener類方法Start()開始等待來自客戶機的連接請求,

35、代碼如下:Server.Start() 或者采用下條語句Server.Start(200)參數(shù)是允許的最大的連接客戶機數(shù)(3)使用TcpListener類方法AcceptSocket()等待來自客戶機的連接請求,如果沒有客戶機的連接請求,程序將被阻塞,既不能執(zhí)行這條語句的后續(xù)語句。如果有一個客戶機的連接請求,將返回一個Socket或TcpClient類對象,將繼續(xù)執(zhí)行后續(xù)語句。代碼如下: /返回Socket類對象,然后執(zhí)行后續(xù)語句 Socket socket = server.AcceptSocket(); /或采用本語句返回TcpClient類對象TcpClient tcpClient =

36、server.AcceptTcpClient(); 得到Socket或TcpClient類對象,已經和客戶機建立了連接,就可以和客戶機進行通信。在通信時,將不再偵聽其他客戶機的連接要求。很多服務器是不允許這種情況發(fā)生的,例如Web服務器必須隨時等待眾多的瀏覽器的訪問。解決的方法是建立一個線程用來和這個客戶機進行通信,而TcpListener類對象server將繼續(xù)偵聽其他客戶機的連接要求。(4)如果使用server.AcceptSocket方法建立連接,返回的Socket類對象,就可以使用Socket類的Send方法發(fā)送數(shù)據(jù)(返回TcpClient用法見下節(jié))。代碼如下:byte msg=En

37、coding.UTF8.GetBytes(This is a test);int i=socket.Send(msg); /i為發(fā)送數(shù)據(jù)的字節(jié)數(shù) 可以使用Socket類的方法Receive接收數(shù)據(jù)(TcpClient用法見下節(jié)),代碼如下:byte bytes = new byte256;i=socket.Receive(bytes);/i為發(fā)送數(shù)據(jù)的字節(jié)數(shù)(5)最后,如果不再通信,使用Socket類的Close方法終止連接,代碼如下:Socket.Close()10.4.4 客戶機程序 網絡中的計算機可以運行客戶機端網絡程序訪問服務器,例如,通過IE瀏覽器(客戶機)可以訪問Internet中

38、的Web服務器,瀏覽網頁。編寫運行于客戶機端的網絡程序程序需要四個步驟。具體步驟如下:(1)創(chuàng)建System.Net.Sockets命名空間的TcpClient類對象用來和服務器建立連接,代碼如下:/自動選擇最合適的本地 IP 地址和端口號 TcpClient tcpClient = new TcpClient (); /和本機的服務器連接tcpClient.Connect (localhost,1300); Connect方法的第一個參數(shù)也可以是遠程服務器的域名,例如,“”。如果知道遠程服務器的IP地址,可以采用如下代碼: TcpClient tcpClient = new TcpClien

39、t ();/參數(shù)為遠程服務器的IP地址IPAddress ServerIP=IPAddress.Parse(202.206.96.204); tcpClient.Connect (ServerIP,1300)(2)使用TcpClient類的GetStream方法得到一個NetworkStream類對象,用來對服務器進行讀寫。NetworkStream netStream = tcpClient.GetStream(); (3)使用NetworkStream類對象讀寫服務器數(shù)據(jù)代碼如下:if (netStream.CanWrite) Byte sendBytes = Encoding.UTF8.

40、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);(4)關閉NetworkStream類對象后,關閉和服務器的連接。netStream.Close()tcpClient.Close()10.4.5 TCP協(xié)議Socket實

41、例 本節(jié)首先實現(xiàn)一個時間服務器,客戶端訪問這個時間服務器系統(tǒng),可以得到時間服務器系統(tǒng)所在地點的時間,在例子中時間服務器直接使用偵聽線程和客戶機通信,因此本例僅支持客戶機順序訪問和多次訪問,但由于服務器發(fā)送時間的代碼很少,很快能夠完成,所以客戶機程序感覺沒有延遲很快就能得到時間。這是一個最簡單的基于TCP協(xié)議的Socket程序實例,通過這個例子讀者可以清楚地理解Socket編程的基本步驟。實際服務器要比這個時間服務器復雜的多,一般情況下,服務器和客戶機通信也許需要較多的時間,例如客戶機訪問文件下載服務器下載文件,服務器直接使用偵聽線程和客戶機通信顯然不能實現(xiàn)多客戶機同時訪問服務器功能。例10.7

42、和例10.8實現(xiàn)了一個文件下載系統(tǒng),該系統(tǒng)實現(xiàn)了多客戶機同時訪問服務器功能?!纠?0.5】本例實現(xiàn)一個時間服務器,客戶端訪問這個時間服務器系統(tǒng),可以得到時間服務器系統(tǒng)所在地點的時間。這是一個最簡單的Scoket編程實例。具體實現(xiàn)步驟如下: (1)建立一個新的Windows應用項目 。在Form1.cs頭部增加命名空間引用: using System.Net; using System.Net.Sockets; using System.Threading;(2)為Form1類增加變量: Thread thread; /線程類變量 bool ifStop = true; /是否停止時間服務器 /

43、負責偵聽是否有客戶機訪問服務器 TcpListener server;/服務器端和客戶機連接的Socket類對象Socket socket; (3)修改構造函數(shù)如下:public Form1() InitializeComponent();/建立偵聽線程,TimeThread是線程執(zhí)行的方 /法名稱,退出該方法,線程結束 thread = new Thread (new ThreadStart(TimeThread); thread.Start();/線程啟動 ifStop=false;/變量表示是否退出線程,false不退出Text= 時間服務器; /Form1窗體的標題欄內容(4)偵聽工作

44、不能在主線程中進行,否則當偵聽工作被阻塞后,將不能執(zhí)行其他任何語句,程序看起來就像死了一樣,不能執(zhí)行任何動作。因此偵聽工作必須在另一線程中進行。在線程為Form1類定義一個偵聽線程方法如下,采用本機IP地址,端口號為1300。public void TimeThread() try server = new TcpListener(1300); server.Start();/開始偵聽是否有客戶機連接服務器 catch MessageBox.Show(不能建立服務器, 提示, MessageBoxButtons.OK); Return; /原因可能是端口號1300被占用 /或網絡不可用,退出線

45、程 while (!ifStop) /如退出while語句,線程結束 Try /下句等待客戶端的連接 aSocket = server.AcceptSocket() /阻塞 /得到用字符串表示的時間 string s = DateTime.Now.ToString(); /將時間字符串轉換為字節(jié)數(shù)組 byte msg = Encoding.UTF8.GetBytes(s); /本例發(fā)送時間方法Send和偵聽方法/AcceptSocket()在同一線程。在發(fā)送時間時不能/繼續(xù)偵聽是否有客戶機連接服務器。本例發(fā)送/數(shù)據(jù)較少,發(fā)送后很快開始偵聽,基本不影響 /其他客戶機的連接。本方法支持客戶機順序訪

46、/問和多次訪問。如果發(fā)送數(shù)據(jù)較多占用較多時/間或者客戶機要長時間和服務器連接,必須建/立新線程用來發(fā)送數(shù)據(jù)使偵聽可以繼續(xù),見后/續(xù)例子。發(fā)送時間到客戶機,完成之前被阻塞/完成后執(zhí)行后續(xù)語句。 aSocket.Send(msg) aSocket.Close() 送出時間后關閉和客戶機的連接/退出前,要使ifStop=true,關閉socket和server,/如果這兩個對象正在使用必定產生異常,執(zhí)行catch中/語句,繼續(xù)while循環(huán),由于ifStop=true,將退出while/循環(huán)語句,即退出TimeThread方法結束線程。/如果僅僅是在程序運行時,socket = /server.Ac

47、ceptSocket()或socket.Send(msg)語句發(fā)生/異常,由于ifStop=false,僅僅重新開始偵聽。 catch if (socket != null) socket.Close();/關閉關閉和客戶機的連接 if (socket != null)/運行到此,線程將結束 socket.Close();/關閉關閉和客戶機的連接 server.Stop(); /關閉TcpListener類對象取消偵聽/運行到此,線程將結束,要關閉所有建立的對象 (5)在關閉程序之前,必須撤銷線程對象。為主窗體的Closing事件增加事件處理函數(shù)如下:private void Form1_Fo

48、rmClosing(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í)行文件。請注意,所建立的時間服務器必須在另一個線程中運行,而不能在主線程中,否則主線程將不會響應用戶的任何動作,包括關閉程序。這是由于函數(shù)TimeThread()中包括一個死循環(huán),如在主線程中運行,將

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

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

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

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

53、時間服務器程序,再一次單擊客戶機程序的標題為“得到時間”按鈕,顯示連接超時,連接不成功。 在網絡應用程序中,經常傳送文件。從上邊的例子可以看到,在網路中使用字節(jié)數(shù)組進行傳送,因此傳送文件,首先要把文件變?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

54、.Close();字節(jié)數(shù)組變?yōu)槲募唧w步驟如下:FileStream fs= /參數(shù)1是保存文件全路徑 new FileStream(d:/g1.bin,FileMode.Create)/寫data字節(jié)數(shù)組中的所有數(shù)據(jù)到文件fs.Write(data,0,data.Length)fs.Close() 如果創(chuàng)建一個文件下載服務器,客戶機就可以訪問文件下載服務器下載文件。下載文件的時間一般比較長,因此當客戶機和文件下載服務器建立連接后,下載文件的工作必須在另一個線程中進行,以便文件下載服務器可以繼續(xù)偵聽工作,等待其他客戶機的連接,使文件下載服務器允許多個客戶機同時下載文件。 在客戶機中,接收文件下

55、載服務器傳輸?shù)奈募脖仨氃诹硪痪€程中,否則,接收過程將占用主線程的所有時間,主線程不能響應其他任何事件,包括關閉窗體。Socket類讀寫緩沖區(qū)的大小是一定的,而文件大小可能超過讀寫緩沖區(qū)的大小,如使用Socket類Send方法發(fā)送文件,可能要寫多次才能完成,很不方便??梢允褂肗etworkStream的Write方法寫文件,調用一次Write方法就完成文件文件傳送?!纠?0.7】本例實現(xiàn)文件下載服務器,客戶機可以訪問文件下載服務器下載文件。為了簡單,客戶機和下載文件服務器建立連接后,立刻傳遞一個指定文件到客戶端。步驟如下:(1)建立一個新的Windows應用項目 。在Form1.vb頭部增加命

56、名空間引用:using System.Net; using System.Net.Sockets; using System.Threading;using System.IO; /讀寫文件必須引用的命名空間(2)為Form1類增加變量:/線程類變量,分別引用偵聽線程和下載線程private Thread Listenerthread,DownLoadthread; bool ifStop = true; /是否停止下載服務器/負責偵聽是否有客戶機訪問服務器TcpListener server; /服務器端和客戶機連接的TcpClient類對象TcpClient socket; /服務器端和客

57、戶機連接后,得到TcpClient類對/象,將創(chuàng)建下載線程和流對象程序關閉前,必/須關閉TcpClient類對象、下載線程和流對象,/本結構用來記錄這些信息public struct DownLoadthreadObject public Thread thread;/下載線程public TcpClient tcpClient;/TcpClient類對象public NetworkStream networkStream; /流對象 /記錄所有服務器端和客戶機連接信息List downLoadthreadObjectS; (3)修改構造函數(shù)如下:public Form1() /建立偵聽線程,

58、ListenerthreadMethod是線程執(zhí) /行的方法名稱,退出該方法,線程結束 Listenerthread = new Thread(new ThreadStart(ListenerthreadMethod); Listenerthread.Start(); /偵聽線程啟動 ifStop=false; /為false表示不退出偵聽線程 Text= 文件下載服務器“; /窗體的標題欄內容 downLoadthreadObjectS=new List();(4)為Form1類定義一個線程方法如下:public void ListenerthreadMethod() /線程執(zhí)行的方法 Tr

59、y /下句采用本機IP地址,端口號為1300 server = New TcpListener(1300) server.Start()/開始偵聽是否有客戶機連接服務器 catch MessageBox.Show(不能建立服務器, 提示, MessageBoxButtons.OK); Return; while (!ifStop) try /未偵聽到客戶機前被阻塞,成功后執(zhí)行后續(xù)語句 aSocket=server.AcceptTcpClient(); DownLoadthread = New Thread( 下載線程 New ThreadStart(AddressOf ClientThread

60、F); DownLoadthread.Start(); Thread.Sleep(100)/等待ClientThreadF正常工作 catch /不處理異常,退出線程方法 if (socket != null) socket.Close(); server.Stop();/關閉TcpListener類對象取消偵聽 (5)為Form1類定義ClientThreadF如下,每連接一個客戶,建立一個下載線程。下載線程負責發(fā)送文件。public void ClientThreadF() FileStream fs = null;DownLoadthreadObject downLoadthreadOb

61、ject=new DownLoadthreadObject();downLoadthreadObject.thread = DownLoadthread;downLoadthreadObject.tcpClient = socket;try/下條語句得到網路流對象 NetworkStream netStream = downLoadthreadObject.tcpClient.GetStream();downLoadthreadOworkStream = netStream; lock(this) downLoadthreadObjectS.Add(downLoadthreadObject);

62、 /下句參數(shù)1是要傳輸?shù)奈募?fs=new FileStream(d:/g1.txt,FileMode.Open); byte data=new bytefs.Length; 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) downLoad

63、threadOworkStream.Close(); if (downLoadthreadObject.tcpClient!= null) downLoadthreadObject.tcpClient.Close();lock(this) downLoadthreadObjectS.Remove( downLoadthreadObject); (6)在關閉程序之前,必須撤銷線程對象。為主窗體的Closing事件增加事件處理函數(shù)如下: private void Form1_FormClosing(object sender, FormClosingEventArgs e) ifStop = tr

64、ue; foreach(DownLoadthreadObject DLO in downLoadthreadObjectS) if(DLO.networkStream != null)DLO.networkStream.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&Lis

65、tenerthread.IsAlive) Listenerthread.Abort();(7)編譯得到可執(zhí)行文件?!纠?0.8】本例實現(xiàn)客戶機,從例10.7的文件下載服務器下載文件。具體步驟如下:(1)建立一個新的Windows應用項目 。在Form1.cs頭部增加命名空間引用:using System.Net; using System.Net.Sockets; using System.Threading;using System.IO;(2)為Form1類增加變量:TcpClient tcpClient;/客戶機類對象NetworkStream netStream;/網絡流對象Threa

66、d DownLoadthread;/下載線程類變量(3)在窗體Form1中放置1個Button控件,標題為“下載文件”,事件函數(shù)如下:private void button1_Click(object sender, EventArgs e) tcpClient = new TcpClient(); DownLoadthread = new Thread(new ThreadStart(ClientThreadF); /下載線程DownLoadthread.Start();(4)為Form1類定義ClientThreadF如下,負責接收文件。public void ClientThreadF()/字節(jié)數(shù)組保存接收的數(shù)據(jù) byte bytes=new ytetcpClient.ReceiveBufferSize; FileStream fs = null; List data = new List(); int n = 0; try /localhost表示程序所在計算機的服務器,這樣設 /置服務器和客戶機在同一臺計算機,連接成功之前 /被阻塞,成功后執(zhí)行后續(xù)語句。 /1300為時間服務器端口號 aTcpCli

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

相關資源

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

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

備案號:ICP2024067431-1 川公網安備51140202000466號


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