c#網(wǎng)絡(luò)編程參考模板_第1頁
c#網(wǎng)絡(luò)編程參考模板_第2頁
c#網(wǎng)絡(luò)編程參考模板_第3頁
c#網(wǎng)絡(luò)編程參考模板_第4頁
c#網(wǎng)絡(luò)編程參考模板_第5頁
已閱讀5頁,還剩55頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、網(wǎng)絡(luò)編程基本概念1.面向連接的傳輸協(xié)議:TCP對于TCP協(xié)議我不想說太多東西,這屬于大學(xué)課程,又涉及計算機(jī)科學(xué),而我不是“學(xué)院派”,對于這部分內(nèi)容,我覺得作為開發(fā)人員,只需要掌握與程序相關(guān)的概念就可以了,不需要做太艱深的研究。我們首先知道TCP是面向連接的,它的意思是說兩個遠(yuǎn)程主機(jī)(或者叫進(jìn)程,因為實際上遠(yuǎn)程通信是進(jìn)程之間的通信,而進(jìn)程則是運(yùn)行中的程序),必須首先進(jìn)行一個握手過程,確認(rèn)連接成功,之后才能傳輸實際的數(shù)據(jù)。比如說進(jìn)程A想將字符串“Its a fine day today”發(fā)給進(jìn)程B,它首先要建立連接。在這一過程中,它首先需要知道進(jìn)程B的位置(主機(jī)地址和端口號)。隨后發(fā)送一個不包含實

2、際數(shù)據(jù)的請求報文,我們可以將這個報文稱之為“hello”。如果進(jìn)程B接收到了這個“hello”,就向進(jìn)程A回復(fù)一個“hello”,進(jìn)程A隨后才發(fā)送實際的數(shù)據(jù)“Its a fine day today”。關(guān)于TCP第二個需要了解的,就是它是全雙工的。意思是說如果兩個主機(jī)上的進(jìn)程(比如進(jìn)程A、進(jìn)程B),一旦建立好連接,那么數(shù)據(jù)就既可以由A流向B,也可以由B流向A。除此以外,它還是點對點的,意思是說一個TCP連接總是兩者之間的,在發(fā)送中,通過一個連接將數(shù)據(jù)發(fā)給多個接收方是不可能的。TCP還有一個特性,就是稱為可靠的數(shù)據(jù)傳輸,意思是連接建立后,數(shù)據(jù)的發(fā)送一定能夠到達(dá),并且是有序的,就是說發(fā)的時候你發(fā)了

3、ABC,那么收的一方收到的也一定是ABC,而不會是BCA或者別的什么。編程中與TCP相關(guān)的最重要的一個概念就是套接字。我們應(yīng)該知道網(wǎng)絡(luò)七層協(xié)議,如果我們將上面的應(yīng)用程、表示層、會話層籠統(tǒng)地算作一層(有的教材便是如此劃分的),那么我們編寫的網(wǎng)絡(luò)應(yīng)用程序就位于應(yīng)用層,而大家知道TCP是屬于傳輸層的協(xié)議,那么我們在應(yīng)用層如何使用傳輸層的服務(wù)呢(消息發(fā)送或者文件上傳下載)?大家知道在應(yīng)用程序中我們用接口來分離實現(xiàn),在應(yīng)用層和傳輸層之間,則是使用套接字來進(jìn)行分離。它就像是傳輸層為應(yīng)用層開的一個小口,應(yīng)用程序通過這個小口向遠(yuǎn)程發(fā)送數(shù)據(jù),或者接收遠(yuǎn)程發(fā)來的數(shù)據(jù);而這個小口以內(nèi),也就是數(shù)據(jù)進(jìn)入這個口之后,或者

4、數(shù)據(jù)從這個口出來之前,我們是不知道也不需要知道的,我們也不會關(guān)心它如何傳輸,這屬于網(wǎng)絡(luò)其它層次的工作。2 / 60舉個例子,如果你想寫封郵件發(fā)給遠(yuǎn)方的朋友,那么你如何寫信、將信打包,屬于應(yīng)用層,信怎么寫,怎么打包完全由我們做主;而當(dāng)我們將信投入郵筒時,郵筒的那個口就是套接字,在進(jìn)入套接字之后,就是傳輸層、網(wǎng)絡(luò)層等(郵局、公路交管或者航線等)其它層次的工作了。我們從來不會去關(guān)心信是如何從西安發(fā)往北京的,我們只知道寫好了投入郵筒就OK了??梢杂孟旅孢@兩幅圖來表示它:注意在上面圖中,兩個主機(jī)是對等的,但是按照約定,我們將發(fā)起請求的一方稱為客戶端,將另一端稱為服務(wù)端??梢钥闯鰞蓚€程序之間的對話是通過套

5、接字這個出入口來完成的,實際上套接字包含的最重要的也就是兩個信息:連接至遠(yuǎn)程的本地的端口信息(本機(jī)地址和端口號),連接到的遠(yuǎn)程的端口信息(遠(yuǎn)程地址和端口號)。注意上面詞語的微妙變化,一個是本地地址,一個是遠(yuǎn)程地址。這里又出現(xiàn)了了一個名詞端口。一般來說我們的計算機(jī)上運(yùn)行著非常多的應(yīng)用程序,它們可能都需要同遠(yuǎn)程主機(jī)打交道,所以遠(yuǎn)程主機(jī)就需要有一個ID來標(biāo)識它想與本地機(jī)器上的哪個應(yīng)用程序打交道,這里的ID就是端口。將端口分配給一個應(yīng)用程序,那么來自這個端口的數(shù)據(jù)則總是針對這個應(yīng)用程序的。有這樣一個很好的例子:可以將主機(jī)地址想象為電話號碼,而將端口號想象為分機(jī)號。在.NET中,盡管我們可以直接對套接字

6、編程,但是.NET提供了兩個類將對套接字的編程進(jìn)行了一個封裝,使我們的使用能夠更加方便,這兩個類是TcpClient和TcpListener,它與套接字的關(guān)系如下:從上面圖中可以看出TcpClient和TcpListener對套接字進(jìn)行了封裝。從中也可以看出,TcpListener位于接收流的位置,TcpClient位于輸出流的位置(實際上TcpListener在收到一個請求后,就創(chuàng)建了TcpClient,而它本身則持續(xù)處于偵聽狀態(tài),收發(fā)數(shù)據(jù)都可以由TcpClient完成。這個圖有點不夠準(zhǔn)確,而我暫時沒有想到更好的畫法,后面看到代碼時會更加清楚一些)。我們考慮這樣一種情況:兩臺主機(jī),主機(jī)A和主

7、機(jī)B,起初它們誰也不知道誰在哪兒,當(dāng)它們想要進(jìn)行對話時,總是需要有一方發(fā)起連接,而另一方則需要對本機(jī)的某一端口進(jìn)行偵聽。而在偵聽方收到連接請求、并建立起連接以后,它們之間進(jìn)行收發(fā)數(shù)據(jù)時,發(fā)起連接的一方并不需要再進(jìn)行偵聽。因為連接是全雙工的,它可以使用現(xiàn)有的連接進(jìn)行收發(fā)數(shù)據(jù)。而我們前面已經(jīng)做了定義:將發(fā)起連接的一方稱為客戶端,另一段稱為服務(wù)端,則現(xiàn)在可以得出:總是服務(wù)端在使用TcpListener類,因為它需要建立起一個初始的連接。2.網(wǎng)絡(luò)聊天程序的三種模式實現(xiàn)一個網(wǎng)絡(luò)聊天程序本應(yīng)是最后一篇文章的內(nèi)容,也是本系列最后的一個程序,來作為一個終結(jié)。但是我想后面更多的是編碼,講述的內(nèi)容應(yīng)該不會太多,所

8、以還是把講述的東西都放到這里吧。當(dāng)采用這種模式時,即是所謂的完全點對點模式,此時每臺計算機(jī)本身也是服務(wù)器,因為它需要進(jìn)行端口的偵聽。實現(xiàn)這個模式的難點是:各個主機(jī)(或終端)之間如何知道其它主機(jī)的存在?此時通常的做法是當(dāng)某一主機(jī)上線時,使用UDP協(xié)議進(jìn)行一個廣播(Broadcast),通過這種方式來“告知”其它主機(jī)自己已經(jīng)在線并說明位置,收到廣播的主機(jī)發(fā)回一個應(yīng)答,此時主機(jī)便知道其他主機(jī)的存在。這種方式我個人并不喜歡,但在 C#編寫簡單的聊天程序 這篇文章中,我使用了這種模式,可惜的是我沒有實現(xiàn)廣播,所以還很不完善。第二種方式較好的解決了上面的問題,它引入了服務(wù)器,由這個服務(wù)器來專門進(jìn)行廣播。服

9、務(wù)器持續(xù)保持對端口的偵聽狀態(tài),每當(dāng)有主機(jī)上線時,首先連接至服務(wù)器,服務(wù)器收到連接后,將該主機(jī)的位置(地址和端口號)發(fā)往其他在線主機(jī)(綠色箭頭標(biāo)識)。這樣其他主機(jī)便知道該主機(jī)已上線,并知道其所在位置,從而可以進(jìn)行連接和對話。在服務(wù)器進(jìn)行了廣播之后,因為各個主機(jī)已經(jīng)知道了其他主機(jī)的位置,因此主機(jī)之間的對話就不再通過服務(wù)器(黑色箭頭表示),而是直接進(jìn)行連接。因此,使用這種模式時,各個主機(jī)依然需要保持對端口的偵聽。在某臺主機(jī)離線時,與登錄時的模式類似,服務(wù)器會收到通知,然后轉(zhuǎn)告給其他的主機(jī)。第三種模式是我覺得最簡單也最實用的一種,主機(jī)的登錄與離線與第二種模式相同。注意到每臺主機(jī)在上線時首先就與服務(wù)器建

10、立了連接,那么從主機(jī)A發(fā)往主機(jī)B發(fā)送消息,就可以通過這樣一條路徑,主機(jī)A - 服務(wù)器 - 主機(jī)B,通過這種方式,各個主機(jī)不需要在對端口進(jìn)行偵聽,而只需要服務(wù)器進(jìn)行偵聽就可以了,大大地簡化了開發(fā)。而對于一些較大的文件,比如說圖片或者文件,如果想由主機(jī)A發(fā)往主機(jī)B,如果通過服務(wù)器進(jìn)行傳輸效率會比較低,此時可以臨時搭建一個主機(jī)A至主機(jī)B之間的連接,用于傳輸大文件。當(dāng)文件傳輸結(jié)束之后再關(guān)閉連接(桔紅色箭頭標(biāo)識)。除此以外,由于消息都經(jīng)過服務(wù)器,所以服務(wù)器還可以緩存主機(jī)間的對話,即是說當(dāng)主機(jī)A發(fā)往主機(jī)B時,如果主機(jī)B已經(jīng)離線,則服務(wù)器可以對消息進(jìn)行緩存,當(dāng)主機(jī)B下次連接到服務(wù)器時,服務(wù)器自動將緩存的消息

11、發(fā)給主機(jī)B。本系列文章最后采用的即是此種模式,不過沒有實現(xiàn)過多復(fù)雜的功能。接下來我們的理論知識告一段落,開始下一階段漫長的編碼?;静僮?.服務(wù)端對端口進(jìn)行偵聽接下來我們開始編寫一些實際的代碼,第一步就是開啟對本地機(jī)器上某一端口的偵聽。首先創(chuàng)建一個控制臺應(yīng)用程序,將項目名稱命名為ServerConsole,它代表我們的服務(wù)端。如果想要與外界進(jìn)行通信,第一件要做的事情就是開啟對端口的偵聽,這就像為計算機(jī)打開了一個“門”,所有向這個“門”發(fā)送的請求(“敲門”)都會被系統(tǒng)接收到。在C#中可以通過下面幾個步驟完成,首先使用本機(jī)Ip地址和端口號創(chuàng)建一個System.Net.Sockets.TcpList

12、ener類型的實例,然后在該實例上調(diào)用Start()方法,從而開啟對指定端口的偵聽。using System.Net; / 引入這兩個命名空間,以下同using System.Net.Sockets;using . / 略class Server static void Main(string args) Console.WriteLine(Server is running . ); IPAddress ip = new IPAddress(new byte 127, 0, 0, 1 ); TcpListener listener = new TcpListener(ip, 8500); l

13、istener.Start(); / 開始偵聽 Console.WriteLine(Start Listening .); Console.WriteLine(nn輸入Q鍵退出。); ConsoleKey key; do key = Console.ReadKey(true).Key; while (key != ConsoleKey.Q); / 獲得IPAddress對象的另外幾種常用方法:IPAddress ip = IPAddress.Parse();IPAddress ip = Dns.GetHostEntry(localhost).AddressList0; 上面的

14、代碼中,我們開啟了對8500端口的偵聽。在運(yùn)行了上面的程序之后,然后打開“命令提示符”,輸入“netstat-a”,可以看到計算機(jī)器中所有打開的端口的狀態(tài)??梢詮闹姓业?500端口,看到它的狀態(tài)是LISTENING,這說明它已經(jīng)開始了偵聽: TCP jimmy:1030 :0 LISTENING TCP jimmy:3603 :0 LISTENING TCP jimmy:8500 :0 LISTENING TCP jimmy:netbios-ssn :0 LISTENING在打開了對端口的偵聽以后,服務(wù)端必須通過某種方式進(jìn)行阻塞(比如Co

15、nsole.ReadKey()),使得程序不能夠因為運(yùn)行結(jié)束而退出。否則就無法使用“netstat -a”看到端口的連接狀態(tài),因為程序已經(jīng)退出,連接會自然中斷,再運(yùn)行“netstat -a”當(dāng)然就不會顯示端口了。所以程序最后按“Q”退出那段代碼是必要的,下面的每段程序都會含有這個代碼段,但為了節(jié)省空間,我都省略掉了。2.客戶端與服務(wù)端連接2.1單一客戶端與服務(wù)端連接當(dāng)服務(wù)器開始對端口偵聽之后,便可以創(chuàng)建客戶端與它建立連接。這一步是通過在客戶端創(chuàng)建一個TcpClient的類型實例完成。每創(chuàng)建一個新的TcpClient便相當(dāng)于創(chuàng)建了一個新的套接字Socket去與服務(wù)端通信,.Net會自動為這個套接

16、字分配一個端口號,上面說過,TcpClient類不過是對Socket進(jìn)行了一個包裝。創(chuàng)建TcpClient類型實例時,可以在構(gòu)造函數(shù)中指定遠(yuǎn)程服務(wù)器的地址和端口號。這樣在創(chuàng)建的同時,就會向遠(yuǎn)程服務(wù)端發(fā)送一個連接請求(“握手”),一旦成功,則兩者間的連接就建立起來了。也可以使用重載的無參數(shù)構(gòu)造函數(shù)創(chuàng)建對象,然后再調(diào)用Connect()方法,在Connect()方法中傳入遠(yuǎn)程服務(wù)器地址和端口號,來與服務(wù)器建立連接。這里需要注意的是,不管是使用有參數(shù)的構(gòu)造函數(shù)與服務(wù)器連接,或者是通過Connect()方法與服務(wù)器建立連接,都是同步方法(或者說是阻塞的,英文叫block)。它的意思是說,客戶端在與服務(wù)

17、端連接成功、從而方法返回,或者是服務(wù)端不存、從而拋出異常之前,是無法繼續(xù)進(jìn)行后繼操作的。這里還有一個名為BeginConnect()的方法,用于實施異步的連接,這樣程序不會被阻塞,可以立即執(zhí)行后面的操作,這是因為可能由于網(wǎng)絡(luò)擁塞等問題,連接需要較長時間才能完成。網(wǎng)絡(luò)編程中有非常多的異步操作,凡事都是由簡入難,關(guān)于異步操作,我們后面再討論,現(xiàn)在只看同步操作。創(chuàng)建一個新的控制臺應(yīng)用程序項目,命名為ClientConsole,它是我們的客戶端,然后添加下面的代碼,創(chuàng)建與服務(wù)器的連接:class Client static void Main(string args) Console.WriteLin

18、e(Client Running .); TcpClient client = new TcpClient(); try client.Connect(localhost, 8500); / 與服務(wù)器連接 catch (Exception ex) Console.WriteLine(ex.Message); return; / 打印連接到的服務(wù)端信息 Console.WriteLine(Server Connected!0 - 1, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); / 按Q退出 上面帶代碼中,我們通過調(diào)用C

19、onnect()方法來與服務(wù)端連接。隨后,我們打印了這個連接消息:本機(jī)的Ip地址和端口號,以及連接到的遠(yuǎn)程Ip地址和端口號。TcpClient的Client屬性返回了一個Socket對象,它的LocalEndPoint和RemoteEndPoint屬性分別包含了本地和遠(yuǎn)程的地址信息。先運(yùn)行服務(wù)端,再運(yùn)行這段代碼??梢钥吹絻蛇叺妮敵銮闆r如下:/ 服務(wù)端:Server is running .Start Listening ./ 客戶端:Client Running .Server Connected!:4761 - :8500我們看到客戶端使用的端口號為476

20、1,上面已經(jīng)說過,這個端口號是由.NET隨機(jī)選取的,并不需要我們來設(shè)置,并且每次運(yùn)行時,這個端口號都不同。再次打開“命令提示符”,輸入“netstat -a”,可以看到下面的輸出: TCP jimmy:8500 :0 LISTENING TCP jimmy:8500 localhost:4761 ESTABLISHED TCP jimmy:4761 localhost:8500 ESTABLISHED從這里我們可以得出幾個重要信息:1、端口8500和端口4761建立了連接,這個4761端口便是客戶端用來與服務(wù)端進(jìn)行通信的端口;2、8500端口在與客戶端建立起一個連接后,仍然繼續(xù)保

21、持在監(jiān)聽狀態(tài)。這也就是說一個端口可以與多個遠(yuǎn)程端口建立通信,這是顯然的,大家眾所周之的HTTP使用的默認(rèn)端口為80,但是一個Web服務(wù)器要通過這個端口與多少個瀏覽器通信啊。2.2多個客戶端與服務(wù)端連接那么既然一個服務(wù)器端口可以應(yīng)對多個客戶端連接,那么接下來我們就看一下,如何讓多個客戶端與服務(wù)端連接。如同我們上面所說的,一個TcpClient就是一個Socket,所以我們只要創(chuàng)建多個TcpClient,然后再調(diào)用Connect()方法就可以了:class Client static void Main(string args) Console.WriteLine(Client Running .

22、); TcpClient client; for (int i = 0; i 1, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); / 按Q退出 上面代碼最重要的就是client = new TcpClient()這句,如果你將這個聲明放到循環(huán)外面,再循環(huán)的第二趟就會發(fā)生異常,原因很顯然:一個TcpClient對象對應(yīng)一個Socket,一個Socket對應(yīng)著一個端口,如果不使用new操作符重新創(chuàng)建對象,那么就相當(dāng)于使用一個已經(jīng)與服務(wù)端建立了連接的端口再次與遠(yuǎn)程建立連接。此時,如果在“命令提示符”運(yùn)行“netstat -a”

23、,則會看到類似下面的的輸出: TCP jimmy:8500 :0 LISTENING TCP jimmy:8500 localhost:10282 ESTABLISHED TCP jimmy:8500 localhost:10283 ESTABLISHED TCP jimmy:8500 localhost:10284 ESTABLISHED TCP jimmy:10282 localhost:8500 ESTABLISHED TCP jimmy:10283 localhost:8500 ESTABLISHED TCP jimmy:10284 localhost:8500 ESTA

24、BLISHED可以看到創(chuàng)建了三個連接對,并且8500端口持續(xù)保持偵聽狀態(tài),從這里以及上面我們可以推斷出TcpListener的Start()方法是一個異步方法。3.服務(wù)端獲取客戶端連接3.1獲取單一客戶端連接上面服務(wù)端、客戶端的代碼已經(jīng)建立起了連接,這通過使用“netstat -a”命令,從端口的狀態(tài)可以看出來,但這是操作系統(tǒng)告訴我們的。那么我們現(xiàn)在需要知道的就是:服務(wù)端的程序如何知道已經(jīng)與一個客戶端建立起了連接?服務(wù)器端開始偵聽以后,可以在TcpListener實例上調(diào)用AcceptTcpClient()來獲取與一個客戶端的連接,它返回一個TcpClient類型實例。此時它所包裝的是由服務(wù)端

25、去往客戶端的Socket,而我們在客戶端創(chuàng)建的TcpClient則是由客戶端去往服務(wù)端的。這個方法是一個同步方法(或者叫阻斷方法,block method),意思就是說,當(dāng)程序調(diào)用它以后,它會一直等待某個客戶端連接,然后才會返回,否則就會一直等下去。這樣的話,在調(diào)用它以后,除非得到一個客戶端連接,不然不會執(zhí)行接下來的代碼。一個很好的類比就是Console.ReadLine()方法,它讀取輸入在控制臺中的一行字符串,如果有輸入,就繼續(xù)執(zhí)行下面代碼;如果沒有輸入,就會一直等待下去。class Server static void Main(string args) Console.WriteLin

26、e(Server is running . ); IPAddress ip = new IPAddress(new byte 127, 0, 0, 1 ); TcpListener listener = new TcpListener(ip, 8500); listener.Start(); / 開始偵聽 Console.WriteLine(Start Listening .); / 獲取一個連接,中斷方法 TcpClient remoteClient = listener.AcceptTcpClient(); / 打印連接到的客戶端信息 Console.WriteLine(Client Co

27、nnected!0 1, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); / 按Q退出 此時,服務(wù)端、客戶端的輸出分別為:/ 服務(wù)端Server is running .Start Listening .Client Connected!:8500 :85003.2獲取多個客戶端連接現(xiàn)在我們再接著考慮,如果有多個客戶端發(fā)動對服務(wù)器端的連接會怎么樣,為了避免你將瀏覽器向上滾動,來查看上面的代碼,我將它拷貝了下來,我們先看下客戶端的關(guān)鍵代碼:TcpClient client;for (int

28、 i = 0; i 1, client.Client.LocalEndPoint, client.Client.RemoteEndPoint);如果服務(wù)端代碼不變,我們先運(yùn)行服務(wù)端,再運(yùn)行客戶端,那么接下來會看到這樣的輸出:/ 服務(wù)端Server is running .Start Listening .Client Connected!:8500 :8500Server Connected!:5227 - :8500Server Connected!:5228 - :8500就又回到了

29、本章第2.2小節(jié)“多個客戶端與服務(wù)端連接”中的處境:盡管有三個客戶端連接到了服務(wù)端,但是服務(wù)端程序只接收到了一個。這是因為服務(wù)端只調(diào)用了一次listener.AcceptTcpClient(),而它只對應(yīng)一個連往客戶端的Socket。但是操作系統(tǒng)是知道連接已經(jīng)建立了的,只是我們程序中沒有處理到,所以我們當(dāng)我們輸入“netstat -a”時,仍然會看到3對連接都已經(jīng)建立成功。為了能夠接收到三個客戶端的連接,我們只要對服務(wù)端稍稍進(jìn)行一下修改,將AcceptTcpClient方法放入一個do/while循環(huán)中就可以了:Console.WriteLine(Start Listening .);whil

30、e (true) / 獲取一個連接,同步方法 TcpClient remoteClient = listener.AcceptTcpClient(); / 打印連接到的客戶端信息 Console.WriteLine(Client Connected!0 - 1, remoteClient.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint);這樣看上去是一個死循環(huán),但是并不會讓你的機(jī)器系統(tǒng)資源迅速耗盡。因為前面已經(jīng)說過了,AcceptTcpClient()再沒有收到客戶端的連接之前,是不會繼續(xù)執(zhí)行的,它的大部分時間都在等待。另外,服

31、務(wù)端幾乎總是要保持在運(yùn)行狀態(tài),所以這樣做并無不可,還可以省去“按Q退出”那段代碼。此時再運(yùn)行代碼,會看到服務(wù)端可以收到3個客戶端的連接了。Server is running .Start Listening .Client Connected!:8500 - :5305Client Connected!:8500 - :5306Client Connected!:8500 - :5307本篇文章到此就結(jié)束了,接下來一篇我們來看看如何在服務(wù)端與客戶端之間收發(fā)數(shù)據(jù)。C#網(wǎng)絡(luò)編程(同步傳輸字符

32、串) - Part.2服務(wù)端客戶端通信在與服務(wù)端的連接建立以后,我們就可以通過此連接來發(fā)送和接收數(shù)據(jù)。端口與端口之間以流(Stream)的形式傳輸數(shù)據(jù),因為幾乎任何對象都可以保存到流中,所以實際上可以在客戶端與服務(wù)端之間傳輸任何類型的數(shù)據(jù)。對客戶端來說,往流中寫入數(shù)據(jù),即為向服務(wù)器傳送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從服務(wù)端接收數(shù)據(jù)。對服務(wù)端來說,往流中寫入數(shù)據(jù),即為向客戶端發(fā)送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從客戶端接收數(shù)據(jù)。同步傳輸字符串我們現(xiàn)在考慮這樣一個任務(wù):客戶端打印一串字符串,然后發(fā)往服務(wù)端,服務(wù)端先輸出它,然后將它改為大寫,再回發(fā)到客戶端,客戶端接收到以后,最后再次打印一遍它。我們將它分為兩

33、部分:1、客戶端發(fā)送,服務(wù)端接收并輸出;2、服務(wù)端回發(fā),客戶端接收并輸出。1.客戶端發(fā)送,服務(wù)端接收并輸出1.1服務(wù)端程序我們可以在TcpClient上調(diào)用GetStream()方法來獲得連接到遠(yuǎn)程計算機(jī)的流。注意這里我用了遠(yuǎn)程這個詞,當(dāng)在客戶端調(diào)用時,它得到連接服務(wù)端的流;當(dāng)在服務(wù)端調(diào)用時,它獲得連接客戶端的流。接下來我們來看一下代碼,我們先看服務(wù)端(注意這里沒有使用do/while循環(huán)):class Server static void Main(string args) const int BufferSize = 8192; / 緩存大小,8192字節(jié) Console.WriteLin

34、e(Server is running . ); IPAddress ip = new IPAddress(new byte 127, 0, 0, 1 ); TcpListener listener = new TcpListener(ip, 8500); listener.Start(); / 開始偵聽 Console.WriteLine(Start Listening .); / 獲取一個連接,中斷方法 TcpClient remoteClient = listener.AcceptTcpClient(); / 打印連接到的客戶端信息 Console.WriteLine(Client Co

35、nnected!0 0);buffer = msStream.GetBuffer();string msg = Encoding.Unicode.GetString(buffer);這里我沒有使用這種方法,一個是因為不想關(guān)注在太多的細(xì)節(jié)上面,一個是因為對于字符串來說,8192字節(jié)已經(jīng)很多了,我們通常不會傳遞這么多的文本。當(dāng)使用Unicode編碼時,8192字節(jié)可以保存4096個漢字和英文字符。使用不同的編碼方式,占用的字節(jié)數(shù)有很大的差異,在本文最后面,有一段小程序,可以用來測試Unicode、UTF8、ASCII三種常用編碼方式對字符串編碼時,占用的字節(jié)數(shù)大小?,F(xiàn)在對客戶端不做任何修改,然后運(yùn)

36、行先運(yùn)行服務(wù)端,再運(yùn)行客戶端。結(jié)果我們會發(fā)現(xiàn)這樣一件事:服務(wù)端再打印完“Client Connected!:8500 1, client.Client.LocalEndPoint, client.Client.RemoteEndPoint); string msg = Welcome To TraceFact.Net; NetworkStream streamToServer = client.GetStream(); byte buffer = Encoding.Unicode.GetBytes(msg); / 獲得緩存 streamToServer.Write(buffe

37、r, 0, buffer.Length); / 發(fā)往服務(wù)器 Console.WriteLine(Sent: 0, msg); / 按Q退出 現(xiàn)在再次運(yùn)行程序,得到的輸出為:/ 服務(wù)端Server is running .Start Listening .Client Connected!:8500 :8500Sent: Welcome To TraceFact.Net輸入Q鍵退出。再繼續(xù)進(jìn)行之前,我們假設(shè)客戶端可以發(fā)送多條消息,而服務(wù)端要不斷的接收來自客戶端發(fā)送的消息,但是上面的代碼只能接收客戶端發(fā)來的一條消息,因為它已經(jīng)輸出了“輸入Q鍵退出”,說明程序已經(jīng)

38、執(zhí)行完畢,無法再進(jìn)行任何動作。此時如果我們再開啟一個客戶端,那么出現(xiàn)的情況是:客戶端可以與服務(wù)器建立連接,也就是netstat-a顯示為ESTABLISHED,這是操作系統(tǒng)所知道的;但是由于服務(wù)端的程序已經(jīng)執(zhí)行到了最后一步,只能輸入Q鍵退出,無法再采取任何的動作?;叵胍粋€上面我們需要一個服務(wù)器對應(yīng)多個客戶端時,對AcceptTcpClient()方法的處理辦法,將它放在了do/while循環(huán)中;類似地,當(dāng)我們需要一個服務(wù)端對同一個客戶端的多次請求服務(wù)時,可以將Read()方法放入到do/while循環(huán)中。現(xiàn)在,我們大致可以得出這樣幾個結(jié)論: 如果不使用do/while循環(huán),服務(wù)端只有一個lis

39、tener.AcceptTcpClient()方法和一個TcpClient.GetStream().Read()方法,則服務(wù)端只能處理到同一客戶端的一條請求。 如果使用一個do/while循環(huán),并將listener.AcceptTcpClient()方法和TcpClient.GetStream().Read()方法都放在這個循環(huán)以內(nèi),那么服務(wù)端將可以處理多個客戶端的一條請求。 如果使用一個do/while循環(huán),并將listener.AcceptTcpClient()方法放在循環(huán)之外,將TcpClient.GetStream().Read()方法放在循環(huán)以內(nèi),那么服務(wù)端可以處理一個客戶端的多條請

40、求。 如果使用兩個do/while循環(huán),對它們進(jìn)行分別嵌套,那么結(jié)果是什么呢?結(jié)果并不是可以處理多個客戶端的多條請求。因為里層的do/while循環(huán)總是在為一個客戶端服務(wù),因為它會中斷在TcpClient.GetStream().Read()方法的位置,而無法執(zhí)行完畢。即使可以通過某種方式讓里層循環(huán)退出,比如客戶端往服務(wù)端發(fā)去“exit”字符串時,服務(wù)端也只能挨個對客戶端提供服務(wù)。如果服務(wù)端想執(zhí)行多個客戶端的多個請求,那么服務(wù)端就需要采用多線程。主線程,也就是執(zhí)行外層do/while循環(huán)的線程,在收到一個TcpClient之后,必須將里層的do/while循環(huán)交給新線程去執(zhí)行,然后主線程快速地

41、重新回到listener.AcceptTcpClient()的位置,以響應(yīng)其它的客戶端。 對于第四種情況,實際上是構(gòu)建一個服務(wù)端更為通常的情況,所以需要專門開辟一個章節(jié)討論,這里暫且放過。而我們上面所做的,即是列出的第一種情況,接下來我們再分別看一下第二種和第三種情況。對于第二種情況,我們按照上面的敘述先對服務(wù)端進(jìn)行一下改動:do / 獲取一個連接,中斷方法 TcpClient remoteClient = listener.AcceptTcpClient(); / 打印連接到的客戶端信息 Console.WriteLine(Client Connected!0 - 1, remoteClie

42、nt.Client.LocalEndPoint, remoteClient.Client.RemoteEndPoint); / 獲得流,并寫入buffer中 NetworkStream streamToClient = remoteClient.GetStream(); byte buffer = new byteBufferSize; int bytesRead = streamToClient.Read(buffer, 0, BufferSize); Console.WriteLine(Reading data, 0 bytes ., bytesRead); / 獲得請求的字符串 string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); Console.WriteLine(Received: 0, msg); while (true);然后啟動多個客戶端,在服務(wù)端應(yīng)該可以看到下面的輸出(客戶端沒有變化):Server is running .Start Listening .Client Connected!:8500 - 12

溫馨提示

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

評論

0/150

提交評論