C#.Net網(wǎng)絡(luò)程序開發(fā)基礎(chǔ)之TCP篇

字號(hào):

《Visual C#.Net網(wǎng)絡(luò)程序開發(fā)-Socket篇》中說到:支持Http、Tcp和Udp的類組成了TCP/IP三層模型(請(qǐng)求響應(yīng)層、應(yīng)用協(xié)議層、傳輸層)的中間層-應(yīng)用協(xié)議層,該層的類比位于最底層的Socket類提供了更高層次的抽象,它們封裝 TCP 和 UDP 套接字的創(chuàng)建,不需要處理連接的細(xì)節(jié),這使得我們?cè)诰帉懱捉幼旨?jí)別的協(xié)議時(shí),可以更多地嘗試使用 TCPClient 、 UDPClient和TcpListener,而不是直接向 Socket 中寫。它們之間的這種層次關(guān)系示意如下:
    ???
    ??可見, TcpClient 類基于 Socket 類構(gòu)建,這是它能夠以更高的抽象程度提供 TCP 服務(wù)的基礎(chǔ)。正因?yàn)檫@樣,許多應(yīng)用層上的通訊協(xié)議,比如FTP(File Transfers Protocol)文件傳輸協(xié)議、HTTP(Hypertext Transfers Protocol)超文本傳輸協(xié)議等都直接創(chuàng)建在TcpClient等類之上。
    ???
    ??TCPClient 類使用 TCP 從 Internet 資源請(qǐng)求數(shù)據(jù)。TCP 協(xié)議建立與遠(yuǎn)程終結(jié)點(diǎn)的連接,然后使用此連接發(fā)送和接收數(shù)據(jù)包。TCP 負(fù)責(zé)確保將數(shù)據(jù)包發(fā)送到終結(jié)點(diǎn)并在數(shù)據(jù)包到達(dá)時(shí)以正確的順序?qū)ζ溥M(jìn)行組合。
    ???
    ??從名字上就可以看出,TcpClient類專為客戶端設(shè)計(jì),它為 TCP 網(wǎng)絡(luò)服務(wù)提供客戶端連接。TcpClient 提供了通過網(wǎng)絡(luò)連接、發(fā)送和接收數(shù)據(jù)的簡(jiǎn)單方法。
    ???
    ??若要建立 TCP 連接,必須知道承載所需服務(wù)的網(wǎng)絡(luò)設(shè)備的地址(IPAddress)以及該服務(wù)用于通訊的 TCP 端口 (Port)。Internet 分配號(hào)碼機(jī)構(gòu) (Internet Assigned Numbers Authority, IANA) 定義公共服務(wù)的端口號(hào)(你可以訪問 http://www.iana.org/assignments/port-numbers獲得這方面更詳細(xì)的資料)。IANA 列表中所沒有的服務(wù)可使用 1,024 到 65,535 這一范圍中的端口號(hào)。要?jiǎng)?chuàng)建這種連接,你可以選用TcpClient類的三種構(gòu)造函數(shù)之一:
    ???
    ??1、public TcpClient()當(dāng)使用這種不帶任何參數(shù)的構(gòu)造函數(shù)時(shí),將使用本機(jī)默認(rèn)的ip地址并將使用默認(rèn)的通信端口號(hào)0。這樣情況下,如果本機(jī)不止一個(gè)ip地址,將無法選擇使用。以下語句示例了如何使用默認(rèn)構(gòu)造函數(shù)來創(chuàng)建新的 TcpClient:
    ???
    ??TcpClient tcpClientC = new TcpClient();
    ???
    ???
    ??2、public TcpClient(IPEndPoint)使用本機(jī)IPEndPoint創(chuàng)建TcpClient的實(shí)例對(duì)象。上一篇介紹過了,IPEndPoint將網(wǎng)絡(luò)端點(diǎn)表示為IP地址和端口號(hào),在這里它用于指定在建立遠(yuǎn)程主機(jī)連接時(shí)所使用的本地網(wǎng)絡(luò)接口(IP 地址)和端口號(hào),這個(gè)構(gòu)造方法為使用本機(jī)IPAddress和Port提供了選擇余地。下面的語句示例了如何使用本地終結(jié)點(diǎn)創(chuàng)建 TcpClient 類的實(shí)例:
    ???
    ??IPHostEntry ipInfo=Dns.GetHostByName("www.tuha.net");//主機(jī)信息
    ???IPAddressList[] ipList=ipInfo.AddressList;//IP地址數(shù)組
    ???IPAddress ip=ipList[0];//多IP地址時(shí)一般用第一個(gè)
    ???IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到網(wǎng)絡(luò)終結(jié)點(diǎn)
    ???try{
    ???TcpClient tcpClientA = new TcpClient(ipLocalEndPoint);
    ???}
    ??catch (Exception e ) {
    ???Console.WriteLine(e.ToString());
    ???}
    ???
    ???
    ??到這里,你可能會(huì)感到困惑,客戶端要和服務(wù)端創(chuàng)建連接,所指定的IP地址及通信端口號(hào)應(yīng)該是遠(yuǎn)程服務(wù)器的呀!事實(shí)上的確如此,使用以上兩種構(gòu)造函數(shù),你所實(shí)現(xiàn)的只是TcpClient實(shí)例對(duì)象與IP地址和Port端口的綁定,要完成連接,你還需要顯式指定與遠(yuǎn)程主機(jī)的連接,這可以通過TcpClient類的Connect方法來實(shí)現(xiàn), Connet方法使用指定的主機(jī)名和端口號(hào)將客戶端連接到 遠(yuǎn)程主機(jī):
    ???
    ??1)、public void Connect(IPEndPoint); 使用指定的遠(yuǎn)程網(wǎng)絡(luò)終結(jié)點(diǎn)將客戶端連接到遠(yuǎn)程 TCP 主機(jī)。
    ???
    ??public void Connect(IPAddress, int); 使用指定的 IP 地址和端口號(hào)將客戶端連接到 TCP 主機(jī)。
    ???
    ??public void Connect(string, int); 將客戶端連接到指定主機(jī)上的指定端口。
    ???
    ??需要指出的是,Connect方法的所有重載形式中的參數(shù)IPEndPoint網(wǎng)絡(luò)終
    ???
    ??結(jié)點(diǎn)、IPAddress以及表現(xiàn)為string的Dns主機(jī)名和int指出的Port端口均指的是遠(yuǎn)程服務(wù)器。
    ???
    ??以下示例語句使用主機(jī)默認(rèn)IP和Port端口號(hào)0與遠(yuǎn)程主機(jī)建立連接:
    ???
    ??TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對(duì)象實(shí)例
    ???try{
    ???tcpClient.Connect("www.contoso.com",11002);//建立連接
    ???}
    ???catch (Exception e ){
    ???Console.WriteLine(e.ToString());
    ???}
    ???
    ???
    ??3、public TcpClient(string, int);初始化 TcpClient 類的新實(shí)例并連接到指定主機(jī)上的指定端口。與前兩個(gè)構(gòu)造函數(shù)不一樣,這個(gè)構(gòu)造函數(shù)將自動(dòng)建立連接,你不再需要額外調(diào)用Connect方法,其中string類型的參數(shù)表示遠(yuǎn)程主機(jī)的Dns名,如:www.tuha.net。
    ???
    ??以下示例語句調(diào)用這一方法實(shí)現(xiàn)與指定主機(jī)名和端口號(hào)的主機(jī)相連:
    ???
    ??try{
    ???TcpClient tcpClientB = new TcpClient("www.tuha.net", 4088);
    ???}
    ???catch (Exception e ) {
    ???Console.WriteLine(e.ToString());
    ???}
    ??前面我們說,TcpClient類創(chuàng)建在Socket之上,在Tcp服務(wù)方面提供了更高層次的抽象,體現(xiàn)在網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接受方面,是TcpClient使用標(biāo)準(zhǔn)的Stream流處理技術(shù),使得它讀寫數(shù)據(jù)更加方便直觀,同時(shí),.Net框架負(fù)責(zé)提供更豐富的結(jié)構(gòu)來處理流,貫穿于整個(gè).Net框架中的流具有更廣泛的兼容性,構(gòu)建在更一般化的流操作上的通用方法使我們不再需要困惑于文件的實(shí)際內(nèi)容(HTML、XML 或其他任何內(nèi)容),應(yīng)用程序都將使用一致的方法(Stream.Write、Stream.Read) 發(fā)送和接收數(shù)據(jù)。另外,流在數(shù)據(jù)從 Internet 下載的過程中提供對(duì)數(shù)據(jù)的即時(shí)訪問,可以在部分?jǐn)?shù)據(jù)到達(dá)時(shí)立即開始處理,而不需要等待應(yīng)用程序下載完整個(gè)數(shù)據(jù)集。.Net中通過NetworkStream類實(shí)現(xiàn)了這些處理技術(shù)。
    ???
    ??NetworkStream 類包含在.Net框架的System.Net.Sockets 命名空間里,該類專門提供用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流。NetworkStream 實(shí)現(xiàn)通過網(wǎng)絡(luò)套接字發(fā)送和接收數(shù)據(jù)的標(biāo)準(zhǔn).Net 框架流機(jī)制。NetworkStream 支持對(duì)網(wǎng)絡(luò)數(shù)據(jù)流的同步和異步訪問。NetworkStream 從 Stream 繼承,后者提供了一組豐富的用于方便網(wǎng)絡(luò)通訊的方法和屬性。
    ???
    ??同其它繼承自抽象基類Stream的所有流一樣,NetworkStream網(wǎng)絡(luò)流也可以被視為一個(gè)數(shù)據(jù)通道,架設(shè)在數(shù)據(jù)來源端(客戶Client)和接收端(服務(wù)Server)之間,而后的數(shù)據(jù)讀取及寫入均針對(duì)這個(gè)通道來進(jìn)行。
    ???
    ??.Net框架中,NetworkStream流支持兩方面的操作:
    ???
    ??1、 寫入流。寫入是從數(shù)據(jù)結(jié)構(gòu)到流的數(shù)據(jù)傳輸。
    ???
    ??2、讀取流。讀取是從流到數(shù)據(jù)結(jié)構(gòu)(如字節(jié)數(shù)組)的數(shù)據(jù)傳輸。
    ???
    ??與普通流Stream不同的是,網(wǎng)絡(luò)流沒有當(dāng)前位置的統(tǒng)一概念,因此不支持查找和對(duì)數(shù)據(jù)流的隨機(jī)訪問。相應(yīng)屬性CanSeek 始終返回 false,而 Seek 和 Position 方法也將引發(fā) NotSupportedException。
    ???
    ??基于Socket上的應(yīng)用協(xié)議方面,你可以通過以下兩種方式獲取NetworkStream網(wǎng)絡(luò)數(shù)據(jù)流:
    ???
    ??1、使用NetworkStream構(gòu)造函數(shù):public NetworkStream(Socket, FileAccess, bool);(有重載方法),它用指定的訪問權(quán)限和指定的 Socket 所屬權(quán)為指定的 Socket 創(chuàng)建 NetworkStream 類的新實(shí)例,使用前你需要?jiǎng)?chuàng)建Socket對(duì)象實(shí)例,并通過Socket.Connect方法建立與遠(yuǎn)程服務(wù)端的連接,而后才可以使用該方法得到網(wǎng)絡(luò)傳輸流。示例如下:
    ???
    ??Socket s=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//創(chuàng)建客戶端Socket對(duì)象實(shí)例
    ???try{
    ???s.Connect("www.tuha.net",4088);//建立與遠(yuǎn)程主機(jī)的連接
    ???}
    ???catch(Exception e){
    ???MessageBox.show("連接錯(cuò)誤:" +e.Message);
    ???}
    ???try{
    ???NetworkStream stream=new NetworkStream(s,FileAccess.ReadWrite,false);//取得網(wǎng)絡(luò)傳輸流
    ???}
    ???
    ???
    ??2、通過TcpClient.GetStream方法:public NetworkStream etStream();它返回用于發(fā)送和接收數(shù)據(jù)的基礎(chǔ)網(wǎng)絡(luò)流NetworkStream。GetStream 通過將基礎(chǔ) Socket 用作它的構(gòu)造函數(shù)參數(shù)來創(chuàng)建 NetworkStream 類的實(shí)例。使用前你需要先創(chuàng)TcpClient對(duì)象實(shí)例并建立與遠(yuǎn)程主機(jī)的連接,示例如下:
    ???
    ??TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對(duì)象實(shí)例
    ???Try{
    ???tcpClient.Connect("www.tuha.net",4088);//嘗試與遠(yuǎn)程主機(jī)相連
    ???}
    ???catch(Exception e){
    ???MessageBox.Show("連接錯(cuò)誤:"+e.Message);
    ???}
    ???try{
    ???NetworkStream stream=tcpClient.GetStream();//獲取網(wǎng)絡(luò)傳輸流
    ???}
    ???catch(Exception e)
    ???{
    ???MessageBox.Show("TcpClient錯(cuò)誤:"+e.Message);
    ???}
    ???
    ???
    ??通過以上方法得到NetworkStream網(wǎng)絡(luò)流之后,你就可以使用標(biāo)準(zhǔn)流讀寫方法Write和Read來發(fā)送和接受數(shù)據(jù)了。