![C#網絡編程(基本概念和操作)_第1頁](http://file4.renrendoc.com/view/6e806b3c81fa5a365d1e975037220c74/6e806b3c81fa5a365d1e975037220c741.gif)
![C#網絡編程(基本概念和操作)_第2頁](http://file4.renrendoc.com/view/6e806b3c81fa5a365d1e975037220c74/6e806b3c81fa5a365d1e975037220c742.gif)
![C#網絡編程(基本概念和操作)_第3頁](http://file4.renrendoc.com/view/6e806b3c81fa5a365d1e975037220c74/6e806b3c81fa5a365d1e975037220c743.gif)
![C#網絡編程(基本概念和操作)_第4頁](http://file4.renrendoc.com/view/6e806b3c81fa5a365d1e975037220c74/6e806b3c81fa5a365d1e975037220c744.gif)
下載本文檔
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
C#網絡編程(基本概念和操作)?Part1引言C#網絡編程系列文章計劃簡單地講述網絡編程方面的基礎知識,由于本人在這方面功カ有限,所以只能提供ー些初步的入門知識,希望能対剛開始學習的朋友提供?些幫助。如果想要更加深入的內容,可以參考相關書籍。本文是該系列第?篇,主要講述了基于套接字(Socket)進行網絡編程的基本概念,其中包括TCP協(xié)議、套接字、聊天程序的三種開發(fā)模式,以及兩個基本操作:偵聽端口、連接遠程服務端:第二篇講述了一個簡單的范例:從客戶端傳輸字符串到服務端,服務端接收并打印字符串,將字符串改為大寫,然后再將字符串回發(fā)到客戶端,客戶端最后打印傳回的字符串:第三篇是第二篇的一個強化,講述了第二篇中沒有解決的ー個問題,并使用了異步傳輸的方式來完成和第二篇同樣的功能:第四篇則演示了如何在客戶端與服務端之間收發(fā)文件;第五篇實現了一個能夠在線聊天并進行文件傳輸的聊天程序,實際上是對前面知識的ー個綜合應用。與本文相關的還有一篇文章是:C# ,但這個聊天程序不及本系列中的聊天程序功能強大,實現方式也不相同。網絡編程基本概念.面向連接的傳輸協(xié)議:TCP對于TCP協(xié)議我不想說太多東西,這屬于大學課程,又涉及計算機科學,而我不是“學院派”,對于這部分內容,我覺得作為開發(fā)人員,只需要掌握與程序相關的概念就可以了,不需要做太艱深的研究。我們首先知道TCP是面向連接的,它的意思是說兩個遠程主機(或者叫進程,因為實際上遠程通信是進程之間的通信,而進程則是運行中的程序),必須首先進行一個握手過程,確認連接成功,之后才能傳輸實際的數據。比如說進程A想將字符串“It'safinedaytoday”發(fā)給進程B,它首先要建立連接。在這ー過程中,它首先需要知道進程B的位置(主機地址和端口號)?隨后發(fā)送一個不包含實際數據的請求報文,我們可以將這個報文稱之為"hello"。如果進程B接收到了這個“hello”,就向進程A回復ー個“hello”,進程A隨后オ發(fā)送實際的數據"It'safinedaytoday”。關于TCP第二個需要了解的,就是它是全雙工的。意思是說如果兩個主機上的進程(比如進程A、進程B),一旦建立好連接,那么數據就既可以由A流向B,也可以由B流向A。除此以外,它還是點對點的,意思是說ー個TCP連接總是兩者之間的,在發(fā)送中,通過ー個連接將數據發(fā)給多個接收方是不可能的。TCP還有一個特性,就是稱為可靠的數據傳輸,意思是連接建立后,數據的發(fā)送一定能夠到達,并且是有序的,就是說發(fā)的時候你發(fā)了ABC,那么收的一方收到的也一定是ABC,而不會是BCA或者別的什么。編程中與TCP相關的最重要的?個概念就是套接字。我們應該知道網絡七層協(xié)議,如果我們將上面的應用程、表示層、會話層籠統(tǒng)地算作一層(有的教材便是如此劃分的),那么我們編寫的網絡應用程序就位于應用層,而大家知道TCP是屬于傳輸層的協(xié)議,那么我們在應用層如何使用傳輸層的服務呢(消息發(fā)送或者文件上傳下載)?大家知道在應用程序中我們用接口來分離實現,在應用層和傳輸層之間,則是使用套接字來進行分離。它就像是傳輸層為應用層開的ー個小口,應用程序通過這個小口向遠程發(fā)送數據,或者接收遠程發(fā)來的數據:而這個小口以內,也就是數據進入這個口之后,或者數據從這個口出來之前,我們是不知道也不需要知道的,我們也不會關心它如何傳輸,這屬于網絡其它層次的工作。舉個例子,如果你想寫封郵件發(fā)給遠方的朋友,那么你如何寫信、將信打包,屬于應用層,信怎么寫,怎么打包完全由我們做主;而當我們將信投入郵筒時,郵筒的那個口就是套接字,在進入套接字之后,就是傳輸層、網絡層等(郵局、公路交管或者航線等)其它層次的工作了。我們從來不會去關心信是如何從西安發(fā)往北京的,我們只知道寫好了投入郵筒就0K了。可以用下面這兩幅圖來表示它;注意在上面圖中,兩個主機是対等的,但是按照約定,我們將發(fā)起請求的一方稱為客戶端,將另一端稱為服務端。可以看岀兩個程序之間的對話是通過套接字這個出入口來完成的,實際上套接字包含的最重要的也就是兩個信息:連接至遠程的本地的端口信息(本機地址和端口號),連接到的遠程的端口信息(遠程地址和端口號)。注意上面詞語的微妙變化,ー個是本地地址,ー個是遠程地址。這里又出現了了一個名詞端口。一般來說我們的計算機上運行著非常多的應用程序,它們可能都需要同遠程主機打交道,所以遠程主機就需要有一個ID來標識它想與本地機器上的哪個應用程序打交道,這里的ID就是端口。將端口分配給ー個應用程序,那么來自這個端口的數據則總是針對這個應用程序的。有這樣ー個很好的例子:可以將主機地址想象為電話號碼,而將端口號想象為分機號。在,NET中,盡管我們可以直接對套接字編程,但是.NET提供了兩個類將対套接字的編程進行了一個封裝,使我們的使用能夠更加方便,這兩個類是TcpClient和TcpListener,它與套接字的關系如下:去往傳輸乂來白傳輸乂從上面圖中可以看出TcpClient和TcpListener對套接字進行了封裝。從中也可以看出,TcpListener用于接受連接請求,而TcpClient則用于接收和發(fā)送流數據這幅圖的意思是TcpListener持續(xù)地保持對端口的偵聽,一旦收到ー個連接請求后,就可以獲得一
個TcpClient對象,而對于數據的發(fā)送和接收都有TcpClient去完成。此時,TcpListener并沒有停止工作,它始終持續(xù)地保持對端口的偵聽狀態(tài)。我們考慮這樣?種情況:兩臺主機,主機A和主機B,起初它們誰也不知道誰在哪兒,當它們想要進行對話時,總是需要有一方發(fā)起連接,而另一方則需要對本機的某一端口進行偵聽。而在偵聽方收到連接請求、并建立起連接以后,它們之間進行收發(fā)數據時,發(fā)起連接的一方并不需要再進行偵聽。因為連接是全雙工的,它可以使用現有的連接進行收發(fā)數據。而我們前面已經做了定義:將發(fā)起連接的一方稱為客戶端,另一段稱為服務端,則現在可以得出:總是服務端在使用TcpListener類,因為它需要建立起一個初始的連接..網絡聊天程序的三種模式實現ー個網絡聊天程序本應是最后一篇文章的內容,也是本系列最后的ー個程序,來作為ー個終結。但是我想后面更多的是編碼,講述的內容應該不會太多,所以還是把講述的東西都放到這里吧。當采用這種模式時,即是所謂的完全點對點模式,此時每臺計算機本身也是服務器,因為它需要進行端口的偵聽。實現這個模式的難點是:各個主機(或終端)之間如何知道其它主機的存在?此時通常的做法是當某一主機上線時,使用UDP協(xié)議進行ー個廣播(Broadcast),通過這種方式來“告知”其它主機自己已經在線并說明位置,收到廣播的主機發(fā)回ー個應答,此時主機便知道其他主機的存在。這種方式我個人并不喜歡,但在G" 這篇文章中,我使用了這種模式,可惜的是我沒有實現廣播,所以還很不完善。
第二種方式較好的解決了上面的問題,它引入了服務器,由這個服務器來專門進行廣播。服務器持續(xù)保持對端U的偵聽狀態(tài),每當有主機上線時,首先連接至服務器,服務器收到連接后,將該主機的位置(地址和端口號)發(fā)往其他在線主機(綠色箭頭標識)。這樣其他主機便知道該主機已上線,并知道其所在位置,從而可以進行連接和對話。在服務器進行了廣播之后,因為各個主機已經知道了其他主機的位置,因此主機之間的對話就不再通過服務器(黑色箭頭表示),而是直接進行連接。因此,使用這種模式時,各個主機依然需要保持對端口的偵聽。在某臺主機離線時,與登錄時的模式類似,服務器會收到通知,然后轉告給其他的主機。
第三種模式是我覺得最簡單也最實用的?種,主機的登錄與離線與第二種模式相同。注意到每臺主機在上線時首先就與服務器建立了連接,那么從主機A發(fā)往主機B發(fā)送消息,就可以通過這樣一條路徑,主機A->服務器一>主機B,通過這種方式,各個主機不需要在對端口進行偵聽,而只需要服務器進行偵聽就可以了,大大地簡化了開發(fā)。而對于ー些較大的文件,比如說圖片或者文件,如果想由主機A發(fā)往主機B,如果通過服務器進行傳輸效率會比較低,此時可以臨時搭建一個主機A至主機B之間的連接,用于傳輸大文件。當文件傳輸結束之后再關閉連接(桔紅色箭頭標識)。除此以外,由于消息都經過服務器,所以服務器還可以緩存主機間的對話,即是說當主機A發(fā)往主機B時,如果主機B已經離線,則服務器可以對消息進行緩存,當主機B下次連接到服務器時,服務器自動將緩存的消息發(fā)給主機Bo本系列文章最后采用的即是此種模式,不過沒有實現過多復雜的功能。接下來我們的理論知識告一段落,開始下ー階段ーー漫長的編碼?;静僮?服務端對端口進行偵聽接下來我們開始編寫ー些實際的代碼,第一步就是開啟對本地機器上某一端口的偵聽。首先創(chuàng)建一個控制臺應用程序,將項目名稱命名為ServerConsole,它代表我們的服務端。如果想要與外界進行通信,第一件要做的事情就是開啟對端口的偵聽,這就像為計算機打開了一個“門”,所有向這個“門”發(fā)送的請求(“敲門”)都會被系統(tǒng)接收到。在C#中可以通過下面幾個步驟完成,首先使用本機Ip地址和端口號創(chuàng)建一個System.Net.Sockets.TcpListener類型的實例,然后在該實例上調用Start。方法,從而開啟對指定端口的偵聽。usingSystem.Net; /ZりI入這兩個命名空間,以卜同usingSystem.Net.Sockets;using...//略classServer{staticvoidMain(string[]args){Console.WriteLine(MServerisrunning...n);IPAddressip=newIPAddress(newbyte[]{12フ,0f0,1!);TcpListenerlistener=newTcpListener(ip,8500);
listener.Start(); /Z開始偵聽Console.WriteLine(MStartListeningConsole.WriteLine(H\n\n輸入、"Q\"鍵退出。");ConsoleKeykey;do{key=Console.ReadKey(true).Key;}while(key!=ConsoleKey.Q);/Z獲得IPAddress對象的另外幾種常用方法:IPAddressip=IPAddress.Parse("");IPAddressip=Dns.GetHostEntry("localhost").AddressList[0];上面的代碼中,我們開啟了對8500端口的偵聽。在運行了上面的程序之后,然后打開“命令提示符”,輸入“netstat-a”,可以看到計算機器中所有打開的端口的狀態(tài)。可以從中找到8500端口,看到它的狀態(tài)是LISTENING,這說明它已經開始了偵聽:TCPjimmy:1030:0LISTENINGTCPjimmy:360:0LISTENINGTCPjimmy:8500:0LISTENINGTCPjimmy:netbios-ssn:0LISTENING在打開了對端口的偵聽以后,服務端必須通過某種方式進行阻塞(比如Console.ReadKey()),使得程序不能夠因為運行結束而退出。否則就無法使用“netstat-a”看到端口的連接狀態(tài),因為程序已經退出,連接會自然中斷,再運行“netstat-a”當然就不會顯示端口了。所以程序最后按“Q”退出那段代碼是必要的,下面的每段程序都會含有這個代碼段,但為了節(jié)省空間,我都省略掉了。.客戶端與服務端連接單ー客戶端與服務端連接當服務器開始對端口偵聽之后,便可以創(chuàng)建客戶端與它建立連接。這ー步是通過在客戶端創(chuàng)建一個TcpClient的類型實例完成。每創(chuàng)建一個新的TcpClient便相當于創(chuàng)建了一個新的套接字Socket去與服務端通信,.Net會自動為這個套接字分配ー個端口號,上面說過,TcpClient類不過是對Socket進行了一個包裝。創(chuàng)建TcpClient類型實例時,可以在構造函數中指定遠程服務器的地址和端口號。這樣在創(chuàng)建的同時,就會向遠程服務端發(fā)
送ー個連接請求(“握手”),一旦成功,則兩者間的連接就建立起來了。也可以使用重載的無參數構造函數創(chuàng)建對象,然后再調用Connect。方法,在Connect()方法中傳入遠程服務器地址和端口號,來與服務器建立連接。這里需要注意的是,不管是使用有參數的構造函數與服務器連接,或者是通過Connect。方法與服務器建立連接,都是同步方法(或者說是阻塞的,英文叫block)。它的意思是說,客戶端在與服務端連接成功、從而方法返回,或者是服務端不存、從而拋出異常之前,是無法繼續(xù)進行后繼操作的。這里還有一個名為BeginConnect。的方法,用于實施異步的連接,這樣程序不會被阻塞,可以立即執(zhí)行后面的操作,這是因為可能由于網絡擁塞等問題,連接需要較長時間才能完成。網絡編程中有非常多的異步操作,凡事都是由簡入難,關于異步操作,我們后面再討論,現在只看同步操作。創(chuàng)建?個新的控制臺應用程序項目,命名為Clientconsole,它是我們的客戶端,然后添加下面的代碼,創(chuàng)建與服務器的連接:classClient{staticvoidMain(string[]args){Console.WriteLine(MClientRunningTcpClientclient=newTcpClient();try{client.Connect(Mlocalhostnz8500);/Z與服務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;)/Z打印連接到的服務端信息Console.WriteLine(MServerConnected!{0}——>{1}*',client.Client.LocalEndPointfclient.Client.RemoteEndPoint);//按Q退出))上面帶代碼中,我們通過調用Connect。方法來與服務端連接。隨后,我們打印了這個連接消息:本機的Ip地址和端口號,以及連接到的遠程Ip地址和端口號。TcpClient的Client屬性返回了一個Socket對象,它的LocalEndPoint和RemoteEndPoint屬性分別包含了本地和遠程的地址信息。先運行服務端,再運行這段代碼。可以看到兩邊的輸出情況如下:
//服務端:Serverisrunning...StartListening.../Z客戶端:ClientRunning...ServerConnected!:4761ーー>:8500我們看到客戶端使用的端口號為4761,上面已經說過,這個端口號是山.NET隨機選取的,并不需要我們來設置,并且每次運行時,這個端口號都不同。再次打開“命令提示符”,輸入“netstat-a”,可以看到下面的輸出:TCPjimmy:8500:0LISTENINGTCPjimmy:8500localhost:4761ESTABLISHETCPjimmy:4761localhost:8500ESTABLISHED從這里我們可以得出幾個重要信息:1、端口8500和端口4761建立了連接,這個4761端口便是客戶端用來與服務端進行通信的端口;2、8500端口在與客戶端建立起一個連接后,仍然繼續(xù)保持在監(jiān)聽狀態(tài)。這也就是說?個端口可以與多個遠程端口建立通信,這是顯然的,大家眾所周之的HTTP使用的默認端口為80,但是ー個Web服務器要通過這個端口與多少個瀏覽器通信啊?.2多個客戶端與服務端連接那么既然ー個服務器端口可以應對多個客戶端連接,那么接下來我們就看一下,如何讓多個客戶端與服務端連接。如同我們上面所說的,ー個TcpClient就是ー個Socket,所以我們只要創(chuàng)建多個TcpClient,然后再調用Connect。方法就可以了:classClient{staticvoidMain(string[]args){Console.WriteLine(HClientRunning...");TcpClientclient;for(inti=0;i<=2;i++){try{client=newTcpClient();//與服務client.Connect("localhost”,8500);//與服務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;)/Z打印連接到的服務端信息Console.WriteLine(HServerConnected!{0}一一>{1}**rclient.Client.LocalEndPoint,client.Client.RemoteEndPoint);//按Q退出上面代碼最市:要的就是client=newTcpClient()這句,如果你將這個聲明放到循環(huán)外面,再循環(huán)的第二趟就會發(fā)生異常,原因很顯然:一個TcpClient對象對應ー個Socket,ー個Socket對應著ー個端口,如果不使用new操作符重新創(chuàng)建對象,那么就相當于使用ー個已經與服務端建立了連接的端口再次與遠程建立連接,此時,如果在“命令提示符"運行“netstat-a”,則會看到類似下面的的輸出:TCPjinuny:8500:0LISTENINGTCPjimmy:8500localhost:10282ESTABLISHEDTCPjimmy:8500localhost:10283ESTABLISHEDTCPjimmy:8500localhost:10284ESTABLISHEDTCPjimmy:10282localhost:8500ESTABLISHEDTCPjimmy:10283localhost:8500ESTABLISHEDTCPjimmy:10284localhost:8500ESTABLISHED可以看到創(chuàng)建了三個連接對,并且8500端口持續(xù)保持偵聽狀態(tài),從這里以及上面我們可以推斷出TcpListener的Start()方法是ー個異步方法。
.服務端獲取客戶端連接獲取單ー客戶端連接上面服務端、客戶端的代碼已經建立起了連接,這通過使用“netstat-a”命令,從端U的狀態(tài)可以看出來,但這是操作系統(tǒng)告訴我們的。那么我們現在需要知道的就是:服務端的程序如何知道已經與一個客戶端建立起了連接?服務器端開始偵聽以后,可以在TcpListener實例上調用AcceptTcpClient()來獲取與一一個客戶端的連接,它返回ー個TcpClient類型實例。此時它所包裝的是由服務端去往客戶端的Socket,而我們在客戶端創(chuàng)建的TcpClient則是由客戶端去往服務端的。這個方法是ー個同步方法(或者叫阻斷方法,blockmethod).意思就是說,當程序調用它以后,它會一直等待某個客戶端連接,然后オ會返回,否則就會一直等下去。這樣的話,在調用它以后,除非得到ー個客戶端連接,不然不會執(zhí)行接下來的代碼。ー個很好的類比就是Console.ReadLineO方法,它讀取輸入在控制臺中的一行字符串,如果有輸入,就繼續(xù)執(zhí)行下面代碼;如果沒有輸入,就會一直等待下去。classServer{staticvoidMain(string[]args){Console.WriteLine(wServerisrunning...n);IPAddressip=newIPAddress(newbyte[]{12"7,0f〇,1});TcpListenerlistener=newTcpListener(ip,8500);listener.Start(); /Z開始イ貞聽Console.WriteLine(MStartListeningI!獲取ー個連接,中斷方法TcpClientremoteClient=listener.AcceptTcpClient();/Z打印連接到的客戶端信息Console.WriteLine(MClientConnected!{0}<--{1}”,remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);//按Q退出))運行這段代碼,會發(fā)現服務端運行到listener.AcceptTcpClient。時便停止了,并不會執(zhí)行下面的Console.WriteLine。方法。為了讓它繼續(xù)執(zhí)行ド去,必須有一個客戶端連
接到它,所以我們現在運行客戶端,與它進行連接。簡單起見,我們只在客戶端開啟ー個端ロ與之連接:classClient{staticvoidMain(string[]args){Console.WriteLine("ClientRunningTcpClientclient=newTcpClient();try(client.Connect(Mlocalhost**z8500);/Z與服務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;)/Z打印連接到的服務端信息Console.WriteLine("ServerConnected!{0(——>{1}"zclient.Client.LocalEndPointzclient.Client.RemoteEndPoint);〃按Q退出}}此時,服務端、客戶端的輸出分別為:/Z服務端Serverisrunning...StartListening...ClientConnected!:8500<--:5188/Z客戶端ClientRunning...ServerConnected!:5188-->:85003.2獲取多個客戶端連接現在我們再接著考慮,如果有多個客戶端發(fā)動對服務器端的連接會怎么樣,為了避免你將瀏覽器向上滾動,來查看上面的代碼,我將它拷貝了下來,我們先看下客戶端的關鍵代碼:
TcpClientclient;for(inti=0;i<=2;i++){try(client=newTcpClient();client.Connect(Mlocalhostn,8500); /Z與ル艮務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;}/Z打印連接到的服務端信息Console.WriteLine("ServerConnected!{0}-->{1}”,client.Client.LocalEndPointtclient.Client.RemoteEndPoint);}如果服務端代碼不變,我們先運行服務端,再運行客戶端,那么接下來會看到這樣的輸出:/Z服務端Serverisrunning...StartListening...ClientConnected!:8500<--:5226/Z客戶端ClientRunning...ServerConnected!:5226-->:8500ServerConnected!:5227-->:8500ServerConnected!:5228-->:8500就又回到了本章第2.2小節(jié)“多個客戶端與服務端連接”中的處境:盡管有三個客戶端連接到了服務端,但是服務端程序只接收到了一個。這是因為服務端只調用了一次listener.AcceptTcpClient()>而它只對應ー個連往客戶端的Socket。但是操作系統(tǒng)是知道連接已經建立了的,只是我們程序中沒有處理到,所以我們當我們輸入“netstat-a”時,仍然會看到3對連接都已經建立成功。為了能夠接收到三個客戶端的連接,我們只要對服務端稍稍進行ー下修改,將AcceptTcpClient方法放入ー個do/while循環(huán)中就可以了:
Console.WriteLine(**StartListeningwhile(true){/Z獲取ー個連接,同步方法TcpClientremoteClient=listener.AcceptTcpClient〇;/Z打印連接到的客戶端信息Console.WriteLine("ClientConnected!{0}<--{1}”,remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);)這樣看上去是ー個死循環(huán),但是并不會讓你的機器系統(tǒng)資源迅速耗盡。因為前面已經說過了,AcceptTcpClient。再沒有收到客戶端的連接之前,是不會繼續(xù)執(zhí)行的,它的大部分時間都在等待。另外,服務端幾乎總是要保持在運行狀態(tài),所以這樣做并無不可,還可以省去“按Q退出”那段代碼。此時再運行代碼,會看到服務端可以收到3個客戶端的連接了。Serverisrunning...StartListening...ClientConnected!:8500<--:5305ClientConnected!:8500<ーー:5306ClientConnected!:8500<--:5307本篇文章到此就結束了,接ド來ー篇我們來看看如何在服務端與客戶端之間收發(fā)數據。C#網絡編程(同步傳輸字符串)-Part.2服務端客戶端通信在與服務端的連接建立以后,我們就可以通過此連接來發(fā)送和接收數據。端口與端口之間以流(Stream)的形式傳輸數據,因為幾乎任何対象都可以保存到流中,所以實際上可以在客戶端與服務端之間傳輸任何類型的數據。對客戶端來說,往流中寫入數據,即為向服務器傳送數據;從流中讀取數據,即為從服務端接收數據。対服務端來說,往流中寫入數據,即為向客戶端發(fā)送數據;從流中讀取數據,即為從客戶端接收數據。同步傳輸字符串我們現在考慮這樣ー個任務;客戶端打印ー串字符串,然后發(fā)往服務端,服務端先輸出它,然后將它改為大寫,再回發(fā)到客戶端,客戶端接收到以后,最后再次打印一遍它。我們將它分為兩部分:1、客戶端發(fā)送,服務端接收并輸出;2、服務端回發(fā),客戶端接收并輸出。.客戶端發(fā)送,服務端接收并輸出服務端程序我們可以在TcpClient上調用GetStreamO方法來獲得連接到遠程計算機的流。注意這里我用了遠程這個詞,當在客戶端調用時,它得到連接服務端的流;當在服務端調用時,它獲得連接客戶端的流。接ド來我們來看一下代碼,我們先看服務端(注意這里沒有使用do/while循環(huán)):classServer{staticvoidMain(string[]args){constintBufferSize=8192; //緩存大小,8192字節(jié)Console.WriteLine(HServerisrunning...n);IPAddressip=newIPAddress(newbyte[]{127,0,0,1});TcpListenerlistener=newTcpListener(ipr8500);listener.Start();/Zlistener.Start();/Z開始偵聽Console.WriteLine(MStartListening//獲取ー個連接,中斷方法TcpCl:entremoteClient=listener.AcceptTcpClient〇;/Z打印連接到的客戶端信息Console.WriteLine("ClientConnected!{0}<--{1}",remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);/Z獲得流,并寫入buffer中NetworkstreamstreamToClient=remoteClient.GetStream();byte[]buffer=newbyte[BufferSize];intbytesRead=streamToClient.Read(buffer,0,BufferSize);Console.WriteLine("Readingdata,{0}bytes...",bytesRead);/Z獲得請求的字符串stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);Console.WriteLine("Received:{0}",msg);//按Q退出}}這段程序的上半部分已經很熟悉了,我就不再解釋。remoteClient.GetStreamO方法獲取到了連接至客戶端的流,然后從流中讀出數據并保存在了buffer緩存中,隨后使用Encoding.Unicode.GetStringO方法,從緩存中獲取到了實際的字符串。最后將字符串打印在了控制臺上。這段代碼有個地方需要注意:在能夠讀取的字符串的總字節(jié)數大于BufferSize的時候會出現字符串截斷現象,因為緩存中的數目總是有限的,而對于大對象,比如說圖片或者其它文件來說,則必須采用“分次讀取然后轉存”這種方式,比如這樣:/Z獲取字符串byte[]buffer=newbyte[BufferSize];intbytesRead; /Z讀取的字節(jié)數MemoryStreammsStream=newMemorystream();dobytesRead=streamToClient.Read(buffer,0,BufferSize);
msStream.Write(buffer,0,bytesRead);}while(bytesRead>0);buffer=msStream.GetBuffer();stringmsg=Encoding.Unicode.GetString(buffer);這里我沒有使用這種方法,ー個是因為不想關注在太多的細節(jié)上面,ー個是因為對于字符串來說,8192字節(jié)已經很多了,我們通常不會傳遞這么多的文本。當使用Unicode編碼時,8192字節(jié)可以保存4096個漢字和英文字符。使用不同的編碼方式,占用的字節(jié)數有很大的差異,在本文最后面,有一段小程序,可以用來測試Unicode、UTF8、ASCII三種常用編碼方式對字符串編碼時,占用的字節(jié)數大小。現在對客戶端不做任何修改,然后運行先運行服務端,再運行客戶端。結果我們會發(fā)現這樣ー件事:服務端再打印完"ClientConnected!:8500<—:xxxxx”之后,再次被阻塞了,而沒有輸出"Readingdata,{0}bytes…”??梢?與AcceptTcpClientO方法類似,這個Read。方法也是同步的,只有當客戶端發(fā)送數據的時候,服務端オ會讀取數據、運行此方法,否則它便會一直等待。1.2客戶端程序接下來我們編寫客戶端向服務器發(fā)送字符串的代碼,與服務端類似,它先獲取連接服務器端的流,將字符串保存到buffer緩存中,再將緩存寫入流,寫入流這一過程,相當于將消息發(fā)往服務端。classClient{staticvoidMain(string[]args){Console.WriteLine(HClientRunningTcpClientclient;try{client=newTcpClient();client.Connect(Mlocalhostn,8500); /Z與服務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;)/Z打印連接到的服務端信息Console.WriteLine(HServerConnected!{0}ーー>{1}nzclient.Client.LocalEndPoint,client.Client.RemoteEndPoint);
stringmsg=n\"WelcomeToTraceFact.Net\"**;NetworkstreamstreamToServer=client.GetStream();byte[]buffer=Encoding.Unicode.GetBytes(msg);/Z獲得緩存streamToServer.Write(buffer,〇,buffer.Length);/Z發(fā)往服務器Console.WriteLine("Sent:{0}",msg);//按Q退出現在再次運行程序,得到的輸出為:/Z服務端Serverisrunning...StartListening...ClientConnected!:8500<--:7847Readingdata,52bytes...Received:"WelcomeToTraceFact.Net"輸入"Q"鍵退出。/Z客戶端ClientRunning...ServerConnected!:7847-->:8500Sent:"WelcomeToTraceFact.Net"輸入"Q"鍵退出。再繼續(xù)進行之前,我們假設客戶端可以發(fā)送多條消息,而服務端要不斷的接收來自客戶端發(fā)送的消息,但是上面的代碼只能接收客戶端發(fā)來的一條消息,因為它已經輸出了“輸入Q鍵退出",說明程序已經執(zhí)行完畢,無法再進行任何動作。此時如果我們再開啟ー個客戶端,那么出現的情況是:客戶端可以與服務器建立連接,也就是netstat-a顯示為ESTABLISHED,這是操作系統(tǒng)所知道的;但是由于服務端的程序已經執(zhí)行到了最后ー步,只能輸入Q鍵退出,無法再采取任何的動作?;叵氅`個上面我們需要一個服務器對應多個客戶端時,對AcceptTcpClient()方法的處理辦法,將它放在了do/while循環(huán)中;類似地,當我們需要一個服務端對同一個客戶端的多次請求服務時,可以將Read。方法放入到do/while循環(huán)中。
現在,我們大致可以得出這樣幾個結論:?如果不使用do/while循環(huán),服務端只有一個listener.AcceptTcpClient()方法和一?個TcpClient.GetStreamO.Read。方法,則服務端只能處理到同一客戶端的一條請求。?如果使用ー個do/while循環(huán),并將listener.AcceptTcpClient()方法和TcpClient.GetStreamO.Read。方法都放在這個循環(huán)以內,那么服務端將可以處理多個客戶端的一條請求。?如果使用ー個do/while循環(huán),并將listener.AcceptTcpClient()方法放在循環(huán)之外,將TcpClient.GetStreamO.Read。方法放在循環(huán)以內,那么服務端可以處理一個客戶端的多條請求。?如果使用兩個do/while循環(huán),對它們進行分別嵌套,那么結果是什么呢?結果并不是可以處理多個客戶端的多條請求。因為里層的do/while循環(huán)總是在為ー個客戶端服務,因為它會中斷在TcpClient.GetStreamO.Read。方法的位置,而無法執(zhí)行完畢。即使可以通過某種方式讓里層循環(huán)退出,比如客戶端往服務端發(fā)去“exit”字符串時,服務端也只能挨個對客戶端提供服務。如果服務端想執(zhí)行多個客戶端的多個請求,那么服務端就需要采用多線程。主線程,也就是執(zhí)行外層do/while循環(huán)的線程,在收到ー個TcpClient之后,必須將里層的do/while循環(huán)交給新線程去執(zhí)行,然后主線程快速地重新回到listener.AcceptTcpClient()的位置,以響應其它的客戶端。對于第四種情況,實際上是構建一個服務端更為通常的情況,所以需要專門開辟ー個章節(jié)討論,這里暫且放過。而我們上面所做的,即是列出的第一種情況,接下來我們再分別看一下第二種和第三種情況。對于第二種情況,我們按照上面的敘述先對服務端進行ー下改動:do{/Z獲取ー個連接,中斷方法TcpClientremoteClient=listener.AcceptTcpClient();/Z打印連接到的客戶端信息Console.WriteLine(MClientConnected!{0}<--{1}”,remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);/Z獲得流,并寫入buffer中NetworkstreamstreamToClient=remoteClient.GetStream();byte[]buffer=newbyte[BufferSize];intbytesRead=streamToClient.Read(buffer,〇,BufferSize);
Console.WriteLine(MReadingdata,{0}bytes bytesRead);/Z獲得請求的字符串stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);Console.WriteLine(MReceived:{0}",msg);}while(true);然后啟動多個客戶端,在服務端應該可以看到下面的輸出(客戶端沒有變化):Serverisrunning...StartListening...ClientConnected!:8500<--:8196Readingdata,52bytes...Received:"WelcomeToTraceFact.Net"ClientConnected!:8500<-:8199Readingdata,52bytes...Received:"WelcomeToTraceFact.Net"山第2種情況改為第3種情況,只需要將do向下挪動幾行就可以了:/Z獲取ー個連接,中斷方法TcpClientremoteClient=listener.AcceptTcpClient〇;/Z打印連接到的客戶端信息Console.WriteLine("ClientConnected!{0}<ーー{1}",remoteClient.Client.LocalEndPoint,remoteClient.Client.RemoteEndPoint);/Z獲得流,并寫入buffeエ中NetworkstreamstreamToClient=remoteClient.GetStream();do{byte[]buffer=newbyte[BufferSize];intbytesRead=streamToClient.Read(buffer,0,BufferSize);Console.WriteLine("Readingdata,{0}bytes...",bytesRead);/Z獲得請求的字符串stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);Console.WriteLine("Received:{0}",msg);}while(true);然后我們再改動一下客戶端,讓它發(fā)送多個請求。當我們按下s的時候,可以輸入ー行字符串,然后將這行字符串發(fā)送到服務端;當我們輸入X的時候則退出循環(huán):
NetworkstreamstreamToServer=client.GetStream();ConsoleKeykey;Console.WriteLine("Menu:S-Send,X-ExitM);do{key=Console.ReadKey(true).Key;if(key==ConsoleKey.S){/Z獲取輸入的字符串Console.Write("Inputthemessage:");stringmsg=Console.ReadLine();byte[]buffer=Encoding.Unicode.GetBytes(msg);/Z獲得緩存streamToServer.Write(buffer,0,buffer.Length);/Z發(fā)往服務器Console.WriteLine("Sent:{0}",msg);)}while(key!=ConsoleKey.X);接下來我們先運行服務端,然后再運行客戶端,輸入一些字符串,來進行測試,應該能夠看到下面的輸出結果:/Z服務端Serverisrunning...StartListening...ClientConnected!:8500<--:11004Readingdata,44bytes...Received:歡迎訪問我的博客:TraceFact.NetReadingdata,14bytes...Received:我們ー起進步!//客戶端ClientRunning...ServerConnected!:11004-一>:8500Menu:S一Send,X一ExitInputthemessage:歡迎訪問我的博客:TraceFact.NetSent:歡迎訪問我的博客:TraceFact.NetInputthemessage:我們"起進步!Sent:我們ー起進步!
這里還需要注意一點,當客戶端在TcpClient實例上調用Close。方法,或者在流上調用Dispose。方法,服務端的streamToClient.Read。方法會持續(xù)地返回。,但是不拋出異常,所以會產生?個無限循環(huán);而如果直接關閉掉客戶端,或者客戶端執(zhí)行完畢但沒有調用stream.Dispose。或者TcpClient.Close。,如果服務器端此時仍阻塞在Read()方法處,則會在服務器端拋出異常:“遠程主機強制關閉了?個現有連接”。因此,我們將服務端的streamToClient.Read。方法需要寫在ー個try/catch中。同理,如果在服務端己經連接到客戶端之后,服務端調用remoteClient.Close。,則客戶端會得到異?!盁o法將數據寫入傳輸連接:您的主機中的軟件放棄了一個ー建立的連接?!?而如果服務端直接關閉程序的話,則客戶端會得到異?!盁o法將數據寫入傳輸連接:遠程主機強迫關閉了一個現有的連接?!薄R虼?它們的讀寫操作必須都放入到try/catch塊中。2.服務端回發(fā),客戶端接收并輸出2.2服務端程序我們接著再進行進ー步處理,服務端將收到的字符串改為大寫,然后回發(fā),客戶端接收后打印。此時它們的角色和上面完全進行了一下對調:對于服務端來說,就好像剛オ的客戶端ー樣,將字符串寫入到流中:而客戶端則同服務端ー樣,接收并打印。除此以外,我們最好對流的讀寫操作加上lock,現在我們直接看代碼,首先看服務端:classServer{staticvoidMain(string[]args){constintBufferSize=8192; //緩存大小,8192BytesConsoleKeykey;Console.WriteLine(MServerisrunning...M);IPAddressip=newIPAddress(newbyte[]{127r0,0,1!);TcpListenerlistener=newTcpListener(ip,8500);listener.Start(); /Z開始偵聽Console.WriteLine(nStartListening...**);/Z獲取ー個連接,同步方法,在此處中斷TcpClientremoteClient=listener.AcceptTcpClient〇;/Z打印連接到的客戶端信息Console.WriteLine(MClientConnected!{0}<--{1}M,remoteClient.Client.LocalEndPoint,
remoteClient.Client.RemoteEndPoint);/Z獲得流NetworkstreamstreamToClient=remoteClient.GetStream();do{//寫入buffer中byte[]buffer=newbyte[BufferSize];intbytesRead;try{lock(streamToClient){bytesRead=streamToClient.Read(buffer,〇,BufferSize);)if(bytesRead==0)thrownewException("讀取至リ〇字節(jié)リ;Console.WriteLine(MReadingdata,{0}bytesbytesRead);/Z獲得請求的字符串stringmsg=Encoding.Unicode.GetString(buffer,0,bytesRead);Console.WriteLine("Received:{0}",msg);/Z轉換成大寫并發(fā)送msg=msg.ToUpper();buffer=Encoding.Unicode.GetBytes(msg);lock(streamToClient){streamToClient.Write(buffer,0,buffer.Length);)Console.WriteLine("Sent:{0}",msg);}catch(Exceptionex){Console.WriteLine(ex.Message);break;)}while(true);
streamToClient.Dispose();remoteClient.Close();Console.WriteLine("\n'n輸入、"Q\"鍵退出。");do{key=Console.ReadKey(true).Key;}while(key!=ConsoleKey.Q);}}接下來是客戶端:classClient{staticvoidMain(string[]args){Console.WriteLine("ClientRunning...");TcpClientclient;ConsoleKeykey;constintBufferSize=8192;try(client=newTcpClient();client.Connect("localhost"z8500);/Z與服務器連接}catch(Exceptionex){Console.WriteLine(ex.Message);return;}/Z打印連接到的服務端信息Console.WriteLine("ServerConnected!{0}——>{1}"zclient.Client.LocalEndPointzclient.Client.RemoteEndPoint);NetworkStreamstreamToServer=client.GetStream();Console.WriteLine("Menu:S-SendzX-Exit");do{key=Cons(:>Le.ReadKey(true).Key;
if(key==ConsoleKey.S){//獲取輸入的字符串Console.Write(MInputthemessage:**);stringmsg=Console.ReadLine();byte[]buffer=Encoding.Unicode.GetBytes(msg);/Z獲得緩存try(lock(streamToServer){streamToServer.Write(buffer,〇,buffer.Length);/Z發(fā)往服務器}Console.WriteLine(MSent:{0}",msg);intbytesRead;buffer=newbyte[BufferSize];lock(streamToServer){bytesRead=streamToServer.Read(buffer,0,BufferSize);}msg=Encoding.Unicode.GetString(buffer,0,bytesRead);Console.WriteLine("Received:{0}",msg);}catch(Exceptionex){Console.WriteLine(ex.Message);break;)}}while(key!=ConsoleKey.X);streamToServer.Dispose();client.Close();Console.WriteLine("\n\n輸入、"Q\"鍵退出。");do{key=Console.ReadKey(true).Key;
}while(key!=ConsoleKey.Q);最后我們運行程序,然后輸入ー串英文字符串,然后看一下輸出:/Z客戶端Clientisrunning...ServerConnected!:12662—>:8500Menu:S-Send,X-ExitInputthemessage:Hello,11mjimmyzhang.Sent:Hello,I*mjimmyzhang.Received:HELLO,I?MJIMMYZHANG./Z服務端Serverisrunning...StartListening...ClientConnected!:8500<--:12662Readingdata,46bytes...Received:Hello,I*mjimmyzhang.Sent:HELLO,I?MJIMMYZHANG.看到這里,我想你應該對使用TcpClient和TcpListener進行C#網絡編程有了一個初步的認識,可以說是剛剛入門了,后面的路還很長。本章的所有操作都是同步操作,像上面的代碼也只是作為ー個入門的范例,實際當中,ー個服務端只能為一個客戶端提供服務的情況是不存在的,下面就讓我們來看看上面所說的第四種情況,如何進行異步的服務端編程。附錄:ASCH、UTF8>Uncicode編碼下的中英文字符大小privatestaticvoidShowCode(){string[]strArray={MbM,nabcd","乙","甲乙丙丁”);byte[]buffer;stringmode,back;foreach(stringstrinstrArray){for(inti=0;i<=2;i++){if(i==0){
buffer=Encoding.ASCII.GetBytes(str);back=Encoding.ASCII.GetString(buffer,〇,buffer.Length);mode="ASCII”;}elseif(i==1){buffer=Encoding.UTF8.GetBytes(str);back=Encoding.UTF8.GetString(buffer,0,buffer.Length);mode=MUTF8M;}else{buffer=Encoding.Unicode.GetBytes(str);back=Encoding.Unicode.GetString(buffer,0,buffer.Length);mode="Unicode";)Console.WriteLine("Mode:{0},String:{1},Buffer.Length:{2}",mode,str,buffer.Length);Console.WriteLine("Buffer:");for(intj=0;j<=buffer.Length-1;j++){Console.Write(buffer[j]+"");)Console.WriteLine("\nRetrived:{0}\n",back);)輸出為:Mode:ASCII,String:b,Buffer.Length:1Buffer:98Retrived:bMode:UTF8,String:b,Buffer.Length:1Buffer:98Retrived:b
Mode:Unicode,String:b,Buffer.Length:2Buffer:980Retrived:bMode:ASCII,String:abed.Buffer.Length:4Buffer:979899100Retrived:abedMode:UTF8,String:abed,Buffer.Length:4Buffer:979899100Retrived:abedMode:Unicode,String:abed,Buffer.Length:8Buffer:9709809901000Retrived:abedMode:ASCII,String:乙,Buffer.Length:1Buffer:63Retrived:?Mode:UTF8,String:乙,Buffer.Length:3Buffer:228185153Retrived:乙Mode:Unicode,String:乙,Buffer.Length:2Buffer:8978Retrived:乙Mode:ASCII,String:甲乙丙丁,Buffer.Length:4Buffer:63636363Retrived:????Mode:UTF8,String:甲乙丙J,Buffer.Length:12Buffer:231148178228185153228184153228184129Retrived:甲乙丙「Mode:Unicode,String:甲乙丙」,Buffer.Length:8
Buffer:5011789782578178Retrived:甲乙丙丁大體ヒ可以得出這么幾個結論:ASCII不能保存中文(貌似誰都知道--')。UTF8是變長編碼。在對ASCII字符編碼時,UTF更省空間,只占1個字節(jié),與ASCII編碼方式和長度相同:Unicode在對ASCII字符編碼時,占用2個字節(jié),且第2個字節(jié)補零。UTF8在對中文編碼時需要占用3個字節(jié);Unicode對中文編碼則只需要2個字C#網絡編程(異步傳輸字符串)-Part.3這篇文章我們將前進一大步,使用異步的方式來對服務端編程,以使它成為ー個真正意義上的服務器:可以為多個客戶端的多次請求服務。但是開始之前,我們需要解決ヒー節(jié)中遺留的一個問題。消息發(fā)送時的問題這個問題就是:客戶端分兩次向流中寫入數據(比如字符串)時,我們主觀上將這兩次寫入視為兩次請求;然而服務端有可能將這兩次合起來視為一條請求,這在兩個請求間隔時間比較短的情況下尤其如此。同樣,也有可能客戶端發(fā)出一條請求,但是服務端將其視為兩條請求處理。下面列出了可能的情況,假設我們在客戶端連續(xù)發(fā)送兩條“WelcometoTracefact,net!",則數據到達服務端時可能有這樣三種情況:NOTE:在這里我們假設采用ASCH編碼方式,因為此時上面的ー個方框正好代表ー個字節(jié),而字符串到達末尾后為持續(xù)的O(因為byte是值類型,且最小為0)。上面的第一種情況是最理想的情況,此時兩條消息被視為兩個獨立請求由服務端完整地接收。第二種情況的示意圖如下,此時一條消息被當作兩條消息接收了:而對于第三種情況,則是兩條消息被合并成了一條接收:Welc〇me---WeIcometo如果你下載了上一篇文章所附帶的源碼,那么將Client2.cs進行ー下修改,不通過用戶輸入,而是使用ー個for循環(huán)連續(xù)的發(fā)送三個請求過去,這樣會使請求的間隔時間更短,下面是關鍵代碼:
stringmsg="WelcometoTraceFact.Net!";for(inti=0;i<=2;i++){byte[]buffer=Encoding.Unicode.GetBytes(msg);/Z獲得緩存try{streamToServer.Write(buffer,〇,buffer.Length);/Z發(fā)往月艮務器Console.WriteLine("Sent:{0}",msg);}catch(Exceptionex){Console.WriteLine(ex.Message);break;}運行服務端,然后再運行這個客戶端,你可能會看到這樣的結果:"C:\TinOYS\systea32\cBd.exeSeruei*isrunning...StartListening...ClientConnectedI:8500<—:7763Beadingdata.50bytes...Beceiued:WelcometoTraceFact.Net!Beadingdata.100bytes...Beceiued:WeleonetoTraceFact.Net?WeleonetoTraceFact.Net?XX可以看到,盡管上面將消息分成了三條單獨發(fā)送,但是服務端卻將后兩條合并成了一條。對于這些情況,我們可以這樣處理:就好像HTTP協(xié)議ー樣,在實際的請求和應答內容之前包含了HTTP頭,其中是一些與請求相關的信息。我們也可以訂立自己的協(xié)議,來解決這個問題,比如說,對于上面的情況,我們就可以定義這樣ー個協(xié)議:
[length-XXX]!其中xxx是實際發(fā)送的字符串長度(注意不是字節(jié)數組buffer的長度),那么對于上面的請求,則我們發(fā)送的數據為:“[length=25]WelcometoTraceFact.Net!”。而服務端接收字符串之后,首先讀取這個‘'元數據”的內容,然后再根據“元數據”內容來讀取實際的數據,它可能有下面這樣兩種情況:NOTE:我覺得這里借用“元數據”這個術語還算比較恰當,因為“元數據”就是用來描述數據的數據。?“[“”ド中括號是完整的,可以讀取到length的字節(jié)數。然后根據這個數值與后面的字符串長度相比,如果相等,則說明發(fā)來了一一條完整信息;如果多了,那么說明接收的字節(jié)數多了,取出合適的長度,并將剩余的進行緩存;如果少了,說明接收的不夠,那么將收到的進行ー個緩存,等待下次請求,然后將兩條合并。?"ド“]”中括號本身就不完整,此時讀不到length的值,因為中括號里的內容被截斷了,那么將讀到的數據進行緩存,等待讀取ド次發(fā)送來的數據,然后將兩次合并之后再按上面的方式進行處理。接ド來我們來看下如何來進行實際的操作,實際上,這個問題已經不屬于C#網絡編程的內容了,而完全是對字符串的處理。所以我們不再編寫服務端/客戶端代碼,直接編寫處理這幾種情況的方法:publicclassRequestHandler{privatestringtemp=string.Empty;publicstring[]GetActualString(stringinput){returnGetActualString(input,null);)privatestring[]GetActualString(stringinputtList<string>outputList){if(outputList==null)outputList=newList<string>();if(!String.IsNullOrEmpty(temp))input=temp+input;stringoutput=stringpattern=@H(?<=A\[length=)(\d+)(?=\])M;intlength;
if(Regex.IsMatch(input,pattern)){Matchm=Regex.Match(input,pattern);/Z獲取消息字符串實際應有的長度length=Convert.ToInt32(m.Groups[0].Value);/Z獲取需要進行截取的位置intstartIndex=input.IndexOf(1]1)+1;/Z獲取從此位置開始后所有字符的長度output=input.Substring(startIndex);if(output.Length==length){//如果。utput的長度與消息字符串的應有長度相等/Z說明剛好是完整的一條信息outputList.Add(output);temp=nM;}elseif(output.Length<length){//如果之后的長度小于應有的長度,/Z說明沒有發(fā)完整,則應將整條信息,包括元數據,全部緩存/Z與下一條數據合并起來再進行處理temp=input;/Z此時程序應該退出,因為需要等待下一條數據到來才能繼續(xù)處理}elseif(output.Length>length){//如果之后的長度大于應有的長度,/Z說明消息發(fā)完整了,但是有多余的數據//多余的數據可能是截斷消息,也可能是多條完整消息//截取字符串output=output.Substring(0,length);outputList.Add(output);temp=H;/Z縮短input的長度input=input.Substring(startIndex+length);
/Z遞歸調用GetActualString(input,outputList);)}else{/Z說明、'[",'']"就不完整temp=input;}returnoutputList.ToArray();這個方法接收ー個滿足協(xié)議格式要求的輸入字符串,然后返回一個數組,這是因為如果出現多次請求合并成一個發(fā)送過來的情況,那么就將它們全部返回。隨后簡單起見,我在這個類中添加了一個靜態(tài)的Test()方法和PrintOutput()幫助方法,進行了一個簡單的測試,注意我直接輸入了!ength-13,這個是我提前計算好的。publicstaticvoidTest(){RequestHandlerhandler=newRequestHandl
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- CH-5兒童各年齡期保健課件
- 2025年全球及中國纜索式起重機行業(yè)頭部企業(yè)市場占有率及排名調研報告
- 2025年全球及中國高壓有載分接開關行業(yè)頭部企業(yè)市場占有率及排名調研報告
- 2025年全球及中國可見光波段高光譜成像(HSI)設備行業(yè)頭部企業(yè)市場占有率及排名調研報告
- 2025-2030全球墻磨機開關行業(yè)調研及趨勢分析報告
- 2025年全球及中國打印貼標機和耗材行業(yè)頭部企業(yè)市場占有率及排名調研報告
- 2025-2030全球工業(yè)PTFE密封件行業(yè)調研及趨勢分析報告
- 2025-2030全球超高頻RFID一次性腕帶行業(yè)調研及趨勢分析報告
- 2025-2030全球便攜手持式光譜儀行業(yè)調研及趨勢分析報告
- 2025-2030全球除濕白帶丸行業(yè)調研及趨勢分析報告
- 建設銀行對賬單英文翻譯模板【英國簽證】
- 法醫(yī)解剖室管理制度
- 九年級下冊滬教版上?;瘜W5.2酸和堿的性質研究 課件
- 益生芽孢桿菌體外抑菌活性及耐藥性研究
- 2023數聯網(DSSN)白皮書
- ISO17025經典培訓教材
- 餐飲行業(yè)品牌介紹商務宣傳PPT模板
- 東南大學宣講介紹
- 2023年菏澤醫(yī)學??茖W校單招綜合素質題庫及答案解析
- 九年級下冊-2023年中考歷史總復習知識點速查速記(部編版)
- GB/T 18103-2022實木復合地板
評論
0/150
提交評論