C#網(wǎng)絡編程系列文章_第1頁
C#網(wǎng)絡編程系列文章_第2頁
C#網(wǎng)絡編程系列文章_第3頁
C#網(wǎng)絡編程系列文章_第4頁
C#網(wǎng)絡編程系列文章_第5頁
已閱讀5頁,還剩93頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

C#網(wǎng)絡編程(基本概念和操作)-Part.l

引言

C#網(wǎng)絡編程系列文章計劃簡單地講述網(wǎng)絡編程方面的基礎知識,由于本人在這方面功

力有限,所以只能提供一些初步的入門知識,希望能對剛開始學習的朋友提供一些幫助。如

果想要更加深入的內(nèi)容,可以參考相關(guān)書籍。

本文是該系列第一篇,主要講述了基于套接字(Socket)進行網(wǎng)絡編程的基本概念,

其中包括TCP協(xié)議、套接字、聊天程序的三種開發(fā)模式,以及兩個基本操作:偵聽端口、連

接遠程服務端;第二篇講述了一個簡單的范例:從客戶端傳輸字符串到服務端,服務端接收

并打印字符串,將字符串改為大寫,然后再將字符串回發(fā)到客戶端,客戶端最后打印傳回的

字符串;第三篇是第二篇的一個強化,講述了第二篇中沒有解決的一個問題,并使用了異步

傳輸?shù)姆绞絹硗瓿珊偷诙瑯拥墓δ?第四篇則演示了如何在客戶端與服務端之間收發(fā)文

件;第五篇實現(xiàn)了一個能夠在線聊天并進行文件傳輸?shù)牧奶斐绦?,實際上是對前面知識的一

個綜合應用。

與本文相關(guān)的還有一篇文章是:C#編寫簡單的聊天程序,但這個聊天程序不及本系列

中的聊天程序功能強大,實現(xiàn)方式也不相同。

網(wǎng)絡編程基本概念

1.面向連接的傳輸協(xié)議:TCP

對于TCP協(xié)議我不想說太多東西,這屬于大學課程,又涉及計算機科學,而我不是“學

院派”,對于這部分內(nèi)容,我覺得作為開發(fā)人員,只需要掌握與程序相關(guān)的概念就可以了,

不需要做太艱深的研究。

我們首先知道TCP是面向連接的,它的意思是說兩個遠程主機(或者叫進程,因為實

際上遠程通信是進程之間的通信,而進程則是運行中的程序),必須首先進行一個握手過程,

確認連接成功,之后才能傳輸實際的數(shù)據(jù)。比如說進程A想將字符串“It'safinedaytoday”

發(fā)給進程B,它首先要建立連接。在這一過程中,它首先需要知道進程B的位置(主機地址

和端口號)。隨后發(fā)送一個不包含實際數(shù)據(jù)的請求報文,我們可以將這個報文稱之為“hello”。

如果進程B接收到了這個“hello”,就向進程A回復一個“hello",進程A隨后才發(fā)送實

際的數(shù)據(jù)"It'safinedaytoday”。

關(guān)于TCP第二個需要了解的,就是它是全雙工的。意思是說如果兩個主機上的進程(比

如進程A、進程B),一旦建立好連接,那么數(shù)據(jù)就既可以由A流向B,也可以由B流向A。

除此以外,它還是點對點的,意思是說一個TCP連接總是兩者之間的,在發(fā)送中,通過一個

連接將數(shù)據(jù)發(fā)給多個接收方是不可能的。TCP還有一個特性,就是稱為可靠的數(shù)據(jù)傳輸,意

思是連接建立后,數(shù)據(jù)的發(fā)送?定能夠到達,并且是有序的,就是說發(fā)的時候你發(fā)了ABC,

那么收的一方收到的也?定是ABC,而不會是BCA或者別的什么。

編程中與TCP相關(guān)的最重要的一個概念就是套接字。我們應該知道網(wǎng)絡七層協(xié)議,如

果我們將上面的應用程、表示層、會話層籠統(tǒng)地算作一層(有的教材便是如此劃分的),那

么我們編寫的網(wǎng)絡應用程序就位于應用層,而大家知道TCP是屬于傳輸層的協(xié)議,那么我們

在應用層如何使用傳輸層的服務呢(消息發(fā)送或者文件上傳下載)?大家知道在應用程序中

我們用接口來分離實現(xiàn),在應用層和傳輸層之間,則是使用套接字來進行分離。它就像是傳

輸層為應用層開的一個小口,應用程序通過這個小口向遠程發(fā)送數(shù)據(jù),或者接收遠程發(fā)來的

數(shù)據(jù);而這個小口以內(nèi),也就是數(shù)據(jù)進入這個口之后,或者數(shù)據(jù)從這個口出來之前,我們是

不知道也不需要知道的,我們也不會關(guān)心它如何傳輸,這屬于網(wǎng)絡其它層次的工作。

舉個例子,如果你想寫封郵件發(fā)給遠方的朋友,那么你如何寫信、將信打包,屬于應

用層,信怎么寫,怎么打包完全由我們做主;而當我們將信投入郵筒時,郵筒的那個口就是

套接字,在進入套接字之后,就是傳輸層、網(wǎng)絡層等(郵局、公路交管或者航線等)其它層

次的工作了。我們從來不會去關(guān)心信是如何從西安發(fā)往北京的,我們只知道寫好了投入郵筒

就0K了??梢杂孟旅孢@兩幅圖來表示它:

注意在上面圖中,兩個主機是對等的,但是按照約定,我們將發(fā)起請求的一方稱為客

戶端,將另一端稱為服務端??梢钥闯鰞蓚€程序之間的時話是通過套接字這個出入口來完成

的,實際上套接字包含的最重要的也就是兩個信息:連接至遠程的本地的端口信息(本機地

址和端口號),連接到的遠程的端口信息(遠程地址和端口號)。注意上面詞語的微妙變化,

一個是本地地址,一個是遠程地址。

這里又出現(xiàn)了了個名詞端口。--般來說我們的計算機上運行著非常多的應用程序,

它們可能都需要同遠程主機打交道,所以遠程主機就需要有一個ID來標識它想與本地機器

上的哪個應用程序打交道,這里的ID就是端口。將端U分配給一個應用程序,那么來自這

個端口的數(shù)據(jù)則總是針對這個應用程序的。有這樣一個很好的例子:可以將主機地址想象為

電話號碼,而將端口號想象為分機號。

在.NET中,盡管我們可以直接對套接字編程,但是,NET提供了兩個類將對套接字的編

程進行了一個封裝,使我們的使用能夠更加方便,這兩個類是TcpClient和TcpListener,

它與套接字的關(guān)系如下:

從上面圖中可以看出TcpClient和TcpListener對套接字進行了封裝。從中也可以看

出,TcpListener用于接受連接請求,而TcpClient則用于接收和發(fā)送流數(shù)據(jù)。這幅圖的意

思是TcpListener持續(xù)地保持對端口的偵聽,?旦收到一個連接請求后,就可以獲得一個

TcpClient對象,而對于數(shù)據(jù)的發(fā)送和接收都有TcpClient去完成。此時,TcpListener并

沒有停止工作,它始終持續(xù)地保持對端口的偵聽狀態(tài)。

我們考慮這樣一種情況:兩臺主機,主機A和主機B,起初它們誰也不知道誰在哪兒,

當它們想要進行對話時,總是需要有一方發(fā)起連接,而另一方則需要對本機的某一端口進

行偵聽。而在偵聽方收到連接請求、并建立起連接以后,它們之間進行收發(fā)數(shù)據(jù)時,發(fā)起

連接的一方并不需要再進行偵聽。因為連接是全雙工的,它可以使用現(xiàn)有的連接進行收發(fā)

數(shù)據(jù)。而我們前面已經(jīng)做了定義:將發(fā)起連接的一方稱為客戶端,另一段稱為服務端,則現(xiàn)

在可以得出:總是服務端在使用TcpListener類,因為它需要建立起一個初始的連接。

2.網(wǎng)絡聊天程序的三種模式

實現(xiàn)一個網(wǎng)絡聊天程序本應是最后一篇文章的內(nèi)容,也是本系列最后的一個程序,來

作為一個終結(jié)。但是我想后面更多的是編碼,講述的內(nèi)容應該不會太多,所以還是把講述的

東西都放到這里吧。

當采用這種模式時,即是所謂的完全點對點模式,此時每臺計算機本身也是服務器,

因為它需要進行端口的偵聽。實現(xiàn)這個模式的難點是:各個主機(或終端)之間如何知道其

它主機的存在?此時通常的做法是當某一主機上線時,使用UDP協(xié)議進行一個廣播

(Broadcast),通過這種方式來“告知”其它主機自己已經(jīng)在線并說明位置,收到廣播的

主機發(fā)回一個應答,此時主機便知道其他主機的存在。這種方式我個人并不喜歡,但在C#

編寫簡單的聊天程序這篇文章中,我使用了這種模式,可惜的是我沒有實現(xiàn)廣播,所以還

很不完善。

網(wǎng)絡聊天實現(xiàn)模式2

第二種方式較好的解決了上面的問題,它引入了服務器,由這個服務器來專門進行廣

播。服務器持續(xù)保持對端口的偵聽狀態(tài),每當有主機上線時,首先連接至服務器,服務器收

到連接后,將該主機的位置(地址和端口號)發(fā)往其他在線主機(綠色箭頭標識)。這樣其

他主機便知道該主機已上線,并知道其所在位置,從而可以進行連接和對話。在服務器進行

了廣播之后,因為各個主機已經(jīng)知道了其他主機的位置,因此主機之間的對話就不再通過服

務器(黑色箭頭表示),而是直接進行連接。因此,使用這種模式時,各個主機依然需要保

持對端口的偵聽。在某臺主機離線時,與登錄時的模式類似,服務器會收到通知,然后轉(zhuǎn)告

給其他的主機。

第三種模式是我覺得最簡單也最實用的一種,主機的登錄與離線與第二種模式相同。

注意到每臺主機在上線時首先就與服務器建立了連接,那么從主機A發(fā)往主機B發(fā)送消息,

就可以通過這樣一條路徑,主機A->服務器一>主機B,通過這種方式,各個主機不需

要在對端口進行偵聽,而只需要服務器進行偵聽就可以了,大大地簡化了開發(fā)。

而對于一些較大的文件,比如說圖片或者文件,如果想由主機A發(fā)往主機B,如果通過

服務器進行傳輸效率會比較低,此時可以臨時搭建?個主機A至主機B之間的連接,用于傳

輸大文件。當文件傳輸結(jié)束之后再關(guān)閉連接(桔紅色箭頭標識)。

除此以外,由于消息都經(jīng)過服務器,所以服務器還可以緩存主機間的對話,即是說當

主機A發(fā)往主機B時,如果主機B已經(jīng)離線,則服務器可以對消息進行緩存,當主機B下次

連接到服務器時,服務器自動將緩存的消息發(fā)給主機Bo

本系列文章最后采用的即是此種模式,不過沒有實現(xiàn)過多復雜的功能。接下來我們的

理論知識告一段落,開始下一階段一一漫長的編碼。

基本操作

1.服務端對端口進行偵聽

接下來我們開始編寫一些實際的代碼,第一步就是開啟對本地機器上某一端口的偵聽。

首先創(chuàng)建一個控制臺應用程序,將項目名稱命名為ServerConsole,它代表我們的服務端。

如果想要與外界進行通信,第一件要做的事情就是開啟對端口的偵聽,這就像為計算機打開

了一個“門”,所有向這個“門”發(fā)送的請求(“敲門”)都會被系統(tǒng)接收到。在C#中可

以通過下面幾個步驟完成,首先使用本機Ip地址和端口號創(chuàng)建一個

System.Net.Sockets.TcpListener類型的實例,然后在該實例上調(diào)用Start。方法,從而開

啟對指定端口的偵聽。

usingSystem.Net;//引入這兩

個命名空間,以下同

usingSystem.Net.Sockets;

using...〃略

classServer{

staticvoidMain(string[]args){

Console.WriteLine(z,Serveris

running...〃);

TPAddressip=newIPAddress(newbyte[]

(127,0,0,1));

TcpListenerlistener=new

TcpListener(ip,8500);

listener.Start();//開始

偵聽

Console.WriteLine("Start

Listening…〃);

Console.WriteLine(,z\n\n輸入\〃Q\〃鍵

退出?!?;

ConsoleKeykey;

do{

key=Console.ReadKey(true).Key;

}while(key!=ConsoleKey.Q);

)

)

//獲得IPAddress對象的另外兒種常用方法:

IPAddressip=IPAddress.Parse("");

IPAddressip=

Dns.GetHostEntry("localhost").AddressList[0]

上面的代碼中,我們開啟了對8500端口的偵聽。在運行了上面的程序之后,然后打開

“命令提示符",輸入“netstat-a”,可以看到計算機器中所有打開的端口的狀態(tài)??梢?/p>

從中找到8500端口,看到它的狀態(tài)是LISTENING,這說明它已經(jīng)開始了偵聽:

TCPjimmy:1030:0

LISTENING

TCPjimmy:360:0

LISTENING

TCPjimmy:8500:0

LISTENING

TCPjimmy:netbios-ssn:0

LISTENING

在打開了對端口的偵聽以后,服務端必須通過某種方式進行阻塞(比如

Console.ReadKey()),使得程序不能夠因為運行結(jié)束而退出。否則就無法使用“netstat-a”

看到端口的連接狀態(tài),因為程序已經(jīng)退出,連接會自然中斷,再運行“netstat-a”當然就

不會顯示端口了。所以程序最后按“Q”退出那段代碼是必要的,下面的每段程序都會含有

這個代碼段,但為了節(jié)省空間,我都省略掉了。

2.客戶端與服務端連接

2.1單一客戶端與服務端連接

當服務器開始對端U偵聽之后,便可以創(chuàng)建客戶端與它建立連接。這一步是通過在客

戶端創(chuàng)建一個TcpClient的類型實例完成。每創(chuàng)建一個新的TcpClient便相當于創(chuàng)建了一個

新的套接字Socket去與服務端通信,.Net會自動為這個套接字分配一個端口號,上面說過,

TcpClient類不過是對Socket進行了--個包裝。創(chuàng)建TcpClient類型實例時,可以在構(gòu)造

函數(shù)中指定遠程服務器的地址和端U號。這樣在創(chuàng)建的同時,就會向遠程服務端發(fā)送一個連

接請求(“握手”),一旦成功,則兩者間的連接就建立起來了。也可以使用重載的無參數(shù)

構(gòu)造函數(shù)創(chuàng)建對象,然后再調(diào)用Connect。方法,在Connect。方法中傳入遠程服務器地址

和端口號,來與服務器建立連接。

這里需要注意的是,不管是使用有參數(shù)的構(gòu)造函數(shù)與服務器連接,或者是通過Connect。

方法與服務器建立連接,都是同步方法(或者說是阻塞的,英文叫block)。它的意思是說,

客戶端在與服務端連接成功、從而方法返回,或者是服務端不存、從而拋出異常之前,是無

法繼續(xù)進行后繼操作的。這里還有一個名為BeginConnectO的方法,用于實施異步的連接,

這樣程序不會被阻塞,可以立即執(zhí)行后面的操作,這是因為可能由于網(wǎng)絡擁塞等問題,連接

需要較長時間才能完成。網(wǎng)絡編程中有非常多的異步操作,凡事都是由簡入難,關(guān)于異步操

作,我們后面再討論,現(xiàn)在只看同步操作。

創(chuàng)建一個新的控制臺應用程序項目,命名為ClientConsole,它是我們的客戶端,然后

添加下面的代碼,創(chuàng)建與服務器的連接:

classClient{

staticvoidMain(string[]args){

Console.WriteLine("Client

Running…”);

TcpClientclient=newTcpClient();

try{

client.Connect("localhost”,

8500);//與服務器連接

}catch(Exceptionex){

Console.WriteLine(ex.Message);

return;

)

//打印連接到的服務端信息

Console.WriteLine("ServerConnected!

{0}->{1}〃,

client.Cllent.LocalEndPoint,

client.Client.RemoteEndPoint);

//按Q退出

}

}

上面帶代碼中,我們通過調(diào)用Connect。方法來與服務端連接。隨后,我們打印了這個

連接消息:本機的Ip地址和端口號,以及連接到的遠程Ip地址和端口號。TcpClient的

Client屬性返回了一個Socket對象,它的LocalEndPoint和RemoteEndPoint屬性分別包

含了本地和遠程的地址信息。先運行服務端,再運行這段代碼。可以看到兩邊的輸出情況如

下:

//服務端:

Serverisrunning

StartListening

//客戶端:

ClientRunning

ServerConnected!:4761一>

:8500

我們看到客戶端使用的端口號為4761,上面已經(jīng)說過,這個端口號是由.NET隨機選取

的,并不需要我們來設置,并且每次運行時:這個端口號都不同。再次打開“命令提示符”,

輸入“netstat-a”,可以看到下面的輸出:

TCPjimmy:8500:0

LISTENING

TCPjimmy:8500localhost:47

61ESTABLISHED

TCPjimmy:4761localhost:85

00ESTABLISHED

從這里我們可以得出幾個重要信息:1、端口8500和端口4761建立了連接,這個4761

端口便是客戶端用來與服務端進行通信的端口;2、8500端口在與客戶端建立起一個連接后,

仍然繼續(xù)保持在監(jiān)聽狀態(tài)。這也就是說一個端口可以與多個遠程端口建立通信,這是顯然的,

大家眾所周之的HTTP使用的默認端U為80,但是一個Web服務器要通過這個端口與多少個

瀏覽器通信啊。

2.2多個客戶端與服務端連接

那么既然一個服務器端口可以應對多個客戶端連接,那么接下來我們就看一下,如何

讓多個客戶端與服務端連接。如同我們上面所說的,一個TcpClient就是一個Socket,所

以我們只要創(chuàng)建多個TcpClient,然后再調(diào)用Connect()方法就可以了:

classClient{

staticvoidMain(string[]args){

Console.WriteLine("Client

Running…〃);

TcpClientclient;

for(inti=0;i<=2;i++){

try(

client=newTcpClient();

cllent.Connect("localhost”,

8500);//與服務器連接

}catch(Exceptionex){

Console.WriteLine(ex.Message

);

return;

)

//打印連接到的服務端信息

Console.WriteLine(''Server

Connected!{0}—>{1}”,

client.Client.LocalEndPoint,

client.Client.RemoteEndPoint);

)

//按Q退出

)

)

上面代碼最重要的就是client=newTcpClient()這句,如果你將這個聲明放到循環(huán)

外面,再循環(huán)的第二趟就會發(fā)生異常,原因很顯然:一個TcpClient對象對應一個Socket,

一個Socket對應著一個端口,如果不使用new操作符重新創(chuàng)建對象,那么就相當于使用一

個已經(jīng)與服務端建立了連接的端口再次與遠程建立連接。

此時,如果在“命令提示符"運行"netstat-a”,則會看到類似下面的的輸出:

TCPjimmy:8500:0

LISTENING

TCPjimmy:8500localhost:10

282ESTABLISHED

TCPjimmy:8500localhost:10

283ESTABLISHED

TCPjimmy:8500localhost:10

284ESTABLISHED

TCPjimmy:10282localhost:85

00ESTABLISHED

TCPjimmy:10283localhost:85

00ESTABLISHED

TCPjimmy:10284localhost:85

00ESTABLISHED

可以看到創(chuàng)建了三個連接對,并且8500端口持續(xù)保持偵聽狀態(tài),從這里以及上面我們

可以推斷出TcpListener的Start()方法是一個異步方法。

3.服務端獲取客戶端連接

3.1獲取單一客戶端連接

上面服務端、客戶端的代碼已經(jīng)建立起了連接,這通過使用"netstat-a”命令,從

端口的狀態(tài)可以看出來,但這是操作系統(tǒng)告訴我們的。那么我們現(xiàn)在需要知道的就是:服務

端的程序如何知道已經(jīng)與一個客戶端建立起了連接?

服務器端開始偵聽以后,可以在TcpListener實例上調(diào)用AcceptTcpClientO來獲取與

一個客戶端的連接,它返回一個TcpClient類型實例。此時它所包裝的是由服務端去往客戶

端的Socket,而我們在客戶端創(chuàng)建的TcpClient則是由客戶端去往服務端的。這個方法是

一個同步方法(或者叫阻斷方法,blockmethod),意思就是說,當程序調(diào)用它以后,它會

一直等待某個客戶端連接,然后才會返回,否則就會一直等下去。這樣的話,在調(diào)用它以后,

除非得到一個客戶端連接,不然不會執(zhí)行接下來的代碼。一個很好的類比就是

Console.ReadLineO方法,它讀取輸入在控制臺中的一行字符串,如果有輸入,就繼續(xù)執(zhí)行

F面代碼;如果沒有輸入,就會一直等待下去。

classServer{

staticvoidMain(string[]args){

Console.WriteLine(Z/Serveris

running...");

IPAddressip=newIPAddress(newbyte[]

(127,0,0,1));

TcpListenerlistener=new

TcpListener(ip,8500);

listener.Start();//開始

偵聽

Console.WriteLine("Start

Listening…〃);

//獲取一個連接,中斷方法

TcpClientremoteClient二

listener.AcceptTcpClient();

//打印連接到的客戶端信息

Console.WriteLine("ClientConnected!

{0}<一{1}〃,

remoteClient.Client.LocalEndPoint,

remoteClient.Client.RemoteEndPoint);

//按Q退出

)

)

運行這段代碼,會發(fā)現(xiàn)服務端運行到listener.AcceptTcpClient。時便停止了,并不

會執(zhí)行下面的Console.WriteLine。方法。為了讓它繼續(xù)執(zhí)行下去,必須有一個客戶端連接

到它,所以我們現(xiàn)在運行客戶端,與它進行連接。簡單起見,我們只在客戶端開啟一個端口

與之連接:

classClient{

staticvoidMain(string[]args){

Console.WriteLine("Client

Running…〃);

TcpClientclient=newTcpClient();

try(

client.Connect("localhost”,

8500);//與服務器連接

}catch(Exceptionex){

Console.WriteLine(ex.Message);

return;

)

//打印連接到的服務端信息

Console.WriteLine(,zServerConnected!

{0}一)⑴〃,

client.Client.LocalEndPoint,

client.Client.RemoteEndPoint);

〃按Q退出

}

)

此時,服務端、客戶端的輸出分別為:

//服務端

Serverisrunning

StartListening…

ClientConnected!:8500<一

:5188

//客戶端

ClientRunning

ServerConnected!:5188—>

:8500

3.2獲取多個客戶端連接

現(xiàn)在我們再接著考慮,如果有多個客戶端發(fā)動對服務器端的連接會怎么樣,為了避免

你將瀏覽器向上滾動,來查看上面的代碼,我將它拷貝了下來,我們先看下客戶端的關(guān)鍵代

碼:

TcpClientclient;

for(inti=0;i<=2;i++){

try(

client=newTcpClient();

client.Connect("localhost”,

8500);//與服務器連接

}catch(Exceptionex){

Console.WriteLine(ex.Message);

return;

}

//打印連接到的服務端信息

Console.WriteLine('"ServerConnected!{0}

一〉⑴〃,

client.Client.LocalEndPoint,

client.Client.RemoteEndPoint);

如果服務端代碼不變,我們先運行服務端,再運行客戶端,那么接下來會看到這樣的

輸出:

//服務端

Serverisrunning

StartListening

ClientConnected!:8500<—

:5226

//客戶端

ClientRunning

ServerConnected!:5226—>

:8500

ServerConnected!:5227一>

:8500

ServerConnected!:5228—>

:8500

就又回到了本章第2.2小節(jié)”多個客戶端與服務端連接”中的處境:盡管有三個客戶

端連接到了服務端,但是服務端程序只接收到了一個。這是因為服務端只調(diào)用了一次

listener.AcceptTcpClient(),而它只對應一個連往客戶端的Socket<,但是操作系統(tǒng)是知

道連接已經(jīng)建立了的,只是我們程序中沒有處理到,所以我們當我們輸入“netstat-a”時,

仍然會看到3對連接都已經(jīng)建立成功。

為了能夠接收到三個客戶端的連接,我們只要對服務端稍稍進行一下修改,將

AcceptTcpClient方法放入—do/while循環(huán)中就可以了:

Console.WriteLine(,zStartListening…”);

while(true){

//獲取一個連接,同步方法

TcpClientremoteClient=

listener.AcceptTcpClient();

//打印連接到的客戶端信息

Console.WriteLine(Z,C1ientConnected!{0}

<-⑴〃,

remoteClient.Client.LocalEndPoint,

remoteClient.Client.RemoteEndPoint);

}

這樣看上去是一個死循環(huán),但是并不會讓你的機器系統(tǒng)資源迅速耗盡。因為前面已經(jīng)

說過了,AcceptTcpClient()再沒有收到客戶端的連接之前,是不會繼續(xù)執(zhí)行的,它的大部

分時間都在等待。另外,服務端幾乎總是要保持在運行狀態(tài),所以這樣做并無不可,還可以

省去“按Q退出”那段代碼。此時再運行代碼,會看到服務端可以收到3個客戶端的連接了。

Serverisrunning

StartListening

ClientConnected!:8500<一

:5305

ClientConnected!:8500<—

:5306

ClientConnectedI:8500<一

:5307

本篇文章到此就結(jié)束了,接卜.來一篇我們來看看如何在服務端與客戶端之間收發(fā)數(shù)據(jù)。

C#網(wǎng)絡編程(同步傳輸字符串)-Part.2

服務端客戶端通信

在與服務端的連接建立以后,我們就可以通過此連接來發(fā)送和接收數(shù)據(jù)。端口與端口

之間以流(Stream)的形式傳輸數(shù)據(jù),因為幾乎任何對象都可以保存到流中,所以實際上可

以在客戶端與服務端之間傳輸任何類型的數(shù)據(jù)。對客戶端來說,往流中寫入數(shù)據(jù),即為向服

務器傳送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從服務端接收數(shù)據(jù)。對服務端來說,往流中寫入數(shù)據(jù),

即為向客戶端發(fā)送數(shù)據(jù);從流中讀取數(shù)據(jù),即為從客戶端接收數(shù)據(jù)。

同步傳輸字符串

我們現(xiàn)在考慮這樣一個任務:客戶端打印?串字符串,然后發(fā)往服務端,服務端先輸

出它,然后將它改為大寫,再回發(fā)到客戶端,客戶端接收到以后,最后再次打印一遍它。我

們將它分為兩部分:1、客戶端發(fā)送,服務端接收并輸出;2、服務端回發(fā),客戶端接收并輸

出。

1.客戶端發(fā)送,服務端接收并輸出

1.1服務端程序

我們可以在TcpClient上調(diào)用GetStreamO方法來獲得連接到遠程計算機的流。注意這

里我用了遠程這個詞,當在客戶端調(diào)用時,它得到連接服務端的流;當在服務端調(diào)用時;它

獲得連接客戶端的流。接下來我們來看嚇代碼,我們先看服務端(注意這里沒有使用

do/while循環(huán)):

classServer{

staticvoidMain(string[]args){

constintBufferSize=8192;//緩

存大小,8192字節(jié)

Console.WriteLine(^Serveris

running…〃);

IPAddressip=newIPAddress(newbyte[]

{127,0,0,1));

TcpListenerlistener=new

TcpListener(ip,8500);

listener.Start();//開始

偵聽

Console.WriteLine("Start

Listening…〃);

//獲取一個連接,中斷方法

TcpClientremoteClient=

listener.AcceptTcpClient();

//打印連接到的客戶端信息

Console.WriteLine("ClientConnected!

{0}<-{1}\

remoteClient.Client.LocalEndPoin

t,remoteClient.Client.RemoteEndPoint);

//獲得流,并寫入buffer中

\etworkStreamstreamToClient=

remoteClient.GetStream();

byte[]buffer=newbyte[BufferSize];

intbytesRead二

streamToClient.Read(buffer,0,BufferSize);

Console.WriteLine(z,Readingdata,{0}

bytes…“,bytesRead);

//獲得請求的字符串

stringmsg=

Encoding.Unicode.GetString(buffer,0,

bytesRead);

Console.WriteLine("Received:{0}”,

msg);

〃按Q退出

這段程序的上半部分已經(jīng)很熟悉了,我就不再解釋。remoteClient.GetStreamO方法

獲取到了連接至客戶端的流,然后從流中讀出數(shù)據(jù)并保存在了buffer緩存中,隨后使用

Encoding.Unicode.GetStringO方法,從緩存中獲取到了實際的字符串。最后將字符串打印

在了控制臺上。這段代碼有個地方需要注意:在能夠讀取的字符串的總字節(jié)數(shù)大于

BufferSize的時候會出現(xiàn)字符串截斷現(xiàn)象,因為緩存中的數(shù)目總是有限的,而對于大對象,

比如說圖片或者其它文件來說,則必須采用“分次讀取然后轉(zhuǎn)存”這種方式,比如這樣:

//獲取字符串

byte[]buffer=newbyte[BufferSize];

intbytesRead;//讀取的字節(jié)數(shù)

MemoryStreammsStream=newMemoryStream();

do{

bytesRead二streamToClient.Read(buffer,0,

BufferSize);

msStream.Write(buffer,0,bytesRead);

}while(bytesRead>0);

buffer=msStream.GetBuffer();

string

溫馨提示

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

最新文檔

評論

0/150

提交評論