NetworkStreamhtml
目錄:程序員
1.NetworkStream的做用編程
和先前的流有所不一樣,NetworkStream 的特殊性能夠在它的命名空間中得以瞭解(System.Net.Sockets),聰明的你立刻會反應過來:數組
既然是在網絡中傳輸的流,那必然有某種協議或者規則約束它,不錯,這種協議即是Tcp/IP協議,這個是什麼東東?別急,我先讓你們了瀏覽器
解下NetworkStream的做用:若是服務器和客戶端之間基於TCP鏈接的,他們之間可以依靠一個穩定的字節流進行相互傳輸信息,這也是安全
NetworkStream的最關鍵的做用,有了這個神奇的協議,NetWorkStream便能向其餘流同樣在網絡中(進行點對點的傳輸),這種傳輸的服務器
效率和速度是很是高的(UDP也很快,稍後再介紹)網絡
若是你們對這個概念還不是很清晰的話,別怕,後文中我會更詳細的說明異步
這裏有5點你們先理解就行socket
|
2.簡單介紹下TCP/IP 協議和相關層次
提到協議相信許多初學者或者沒搞過這塊的朋友會一頭霧水,
不過別怕,協議也是人定的,確定能搞懂:
其實協議能夠這麼理解,是人爲定製的爲某個活動定義的一些列規則和約束, 就比如足球賽上的紅黃牌,這是由世界足聯定製的協議或者規範,一旦不按照這個協議 足球賽確定會一片混亂 |
進入正題:
TCP/IP
全稱:Transmission Control Protocol/Internet Protocol (傳輸控制協議/因特網互聯協議,又名網絡通信協議)
這個即是互聯網中的最基本的協議,Tcp/IP 定義了電子設備如何進入到互聯網,以及數據如何在網絡中傳遞。既然有了協議可是空頭支票
仍是不行地,就比如足聯定製了這些規則,可是沒有裁判在球場上來實施這些規則同樣,Tcp/IP協議也有它本身的層次結構,關於它的層次
結構,你們看圖就能明白
發送數據:
你們不用刻板的去理解這個協議,我仍是用咱們最普通的瀏覽網頁來讓你們理解下,首先打開瀏覽器輸入一個url,這時候應用層會判斷這個要求是不是http的
,而後http會將請求信息交給傳輸層來執行,傳輸層主要負責信息流的格式化而且提供一個可靠地傳輸,這時候,TCP和UDP這兩個協議在這裏起做用了,
TCP協議規定:接收端必須發回確認,而且假如分組丟失,必須從新發送,接着網絡層獲得了這些須要發送的數據,(網絡中的IP協議很是重要,不只是IP協議,
還有ARP協議(查找遠程主機MAC地址)),這時候網絡層會命令網絡接口層去發送這些信息(IP層主要負責的是在節點之間(End to End)的數據包傳送,
這裏的節點是一臺網絡設備,好比計算機,你們即可理解爲網絡接口層的設備),最終將請求數據發送至遠程網站主機後等待遠程主機發送來信息
接收數據:
好了,遠程網站主機會根據請求信息(Ip,數據報等等)發送一些列的網頁數據經過網線或者無線路由,回到網絡接口層,而後逐級上報,經過網絡層的ip而後經過
傳輸層的一些列格式化,最終經過http返回至瀏覽器顯示網頁了
基於篇幅的關係,還有其餘的協議你們能夠自行去學習瞭解學習
喜歡足球的朋友的朋友也許會反應過來:這不是2-4-5陣型麼?其實否則,不少協議我還沒畫上去,其實大體含義就是每一個層次上的協議(足球隊員有他各自的職責),
這些才能構成計算機與計算機之間的傳輸信息的橋樑。相信園子裏不少大牛都寫過http 協議,你們也能夠去學習下
3.簡單說明下 TCP和UDP的區別
TCP:
1 TCP是面向鏈接的通訊協議,經過三次握手創建鏈接
2 TCP提供的是一種可靠的數據流服務,採用「帶重傳的確定確認」技術來實現傳輸的可靠性
UDP:
1 UDP是面向無鏈接的通信協議,UDP數據包括目的端口號和源端口號信息,因爲通信不須要鏈接,因此能夠實現廣播發送
2 UDP通信時不須要接收方確認,屬於不可靠的傳輸,可能會出丟包現象,實際應用中要求在程序員編程驗證
3 因爲上述2點的關係,UDP傳輸速度更快,可是安全性比較差,很容易發生未知的錯誤,因此本章的NetworkStream沒法使用在UDP的功能上
4.簡單介紹下套接字(Socket)的概念
關於Socket的概念和功能可能能夠寫很長一篇博文來介紹,這裏你們把Socket理解Tcp/IP協議的抽象,而且可以實現Tcp/IP協議棧的工具就行,換句話說,咱們能夠
利用Socket實現客戶端和服務端雙向通訊,一樣,對於Socket最關鍵的理解還沒到位,不少新人或者不經常使用的朋友會問:Socket到底功能是什麼?怎麼工做的?
再次舉個例子,女朋友打電話給我,我能夠選擇鏈接,或者拒絕,若是我接了她的電話,也就是說,我和她經過電話鏈接(Connect),那電話就是「Socket」,女朋友和我
均可以是客戶端或服務端,只要點對點就行,咱們的聲音經過電話傳遞,可是具體傳輸內容不歸Socket管轄範圍,Socket的直接任務能夠概括爲如下幾點:
|
簡單的Socket示例代碼:
5.簡單介紹下TcpClient,TcpListener,IPEndPoint類的做用
1: TcpClient
此類是微軟基於Tcp封裝類,用於簡化Tcp客戶端的開發,主要經過構造帶入主機地址或者IPEndPonint對象,而後調用Connect進行和服務器點對點的鏈接,鏈接成功後通
過GetStream方法返回NetworkStream對象
2: TcpListener
此類也是微軟基於Tcp封裝類,用於監聽服務端或客戶端的鏈接請求,一旦有鏈接請求信息,馬上交給TcpClient的AcceptTcpClient方法捕獲,Start方法用於開始監聽
3: IPEndPonint
處理IP地址和端口的封裝類
4:IPAddress
提供包含計算機在 IP 網絡上的地址的工具類
6.使用NetworkStream的注意事項和侷限性
抱歉到目前爲止纔開始介紹NetworkStream,我相信你們到這裏在回過頭去看第一節的做用時可以更多的領悟。前五節意在說明下NetworkStream背後那個必須掌握的知識點,
這樣才能在實際編程過程當中很快上手,畢竟NetworkStream的工做環境和其餘流有着很大的差異,
再回到第一節關於NetworkStream的知識點,在使用時有幾點必須注意
首先
1 再次強調NetworkStream是穩定的,面向鏈接的,因此它只適合TCP協議的環境下工做
因此一旦在UDP環境中,雖然編譯不會報錯,可是會跳出異常
2 咱們能夠經過NetworkStream簡化Socket開發
3 若是要創建NetworkStream一個新的實例,則必須使用已經鏈接的Socket
4 NetworkStream 使用後不會自動關閉提供的socket,必須使用NetworkStream構造函數時指定Socket全部權(NetworkStream 的構造函數中設置)。
6 NetworkStream支持異步讀寫操做
NetworkStream的侷限性
7.NetworkStream的構造
1.NetworkStream (Socket) 爲指定的 Socket 建立 NetworkStream 類的新實例
2.NetworkStream (Socket, Boolean ownsSocket) 用指定的 Socket 所屬權爲指定的 Socket
ownsSocket表示指示NetworkStream是否擁有該Socket
3.NetworkStream (Socket, FileAccess) 用指定的訪問權限爲指定的 Socket 建立
FileAccess 值的按位組合,這些值指定授予所提供的 Socket 上的 NetworkStream 的訪問類型
4.NetworkStream (Socket, FileAccess, Boolean ownsSocket) 。
對於NetworkStream構造函數的理解相信你們通過前文的解釋也可以掌握了,可是有幾點 必須強調下 1若是用構造產生NetworkStream的實例,則必須使用鏈接的Socket 2 若是該NetworkStream擁有對Socket的全部權,則在使用NetworkStream的Close方法時會同時關閉Socket, 不然關閉NetworkStream時不會關閉Socket 3, 可以建立對指定Socket帶有讀寫權限的NetworkStream |
8.NetworkStream的屬性
1. CanSeek :用於指示流是否支持查找,它的值始終爲 false
2. DataAvailable 指示在要讀取的 NetworkStream 上是否有可用的數據。通常來講經過判斷這個屬性來判斷NetworkStream中是否有數據
3. Length:NetworkStream不支持使用Length屬性,強行使用會發生NotSupportedException異常
4.Position: NetworkStream不支持使用Position屬性,強行使用會發生NotSupportedException異常
9.NetworkStream的方法
一樣,NetworkStream的方法大體重寫或繼承了Stream的方法
可是如下方法必須注意:
1 int Read(byte[] buffer,int offset,int size)
該方法將數據讀入 buffer 參數並返回成功讀取的字節數。若是沒有能夠讀取的數據,則 Read 方法返回 0。Read 操做將讀取儘量多的可用數據,
直至達到由 size 參數指定的字節數爲止。若是遠程主機關閉了鏈接而且已接收到全部可用數據,Read 方法將當即完成並返回零字節。
2 long Seek(long offset, SeekOrigin origin)
將流的當前位置設置爲給定值。此方法當前不受支持,老是引起 NotSupportedException。
3 void Write(byte[] buffer, int offset,int size)
Write方法在指定的 offset 處啓動,並將 buffer 內容中的 size 字節發送到網絡。Write 方法將一直處於阻止狀態(能夠用異步解決),直到發送了請求
的字節數或引起 SocketException 爲止。若是收到 SocketException,可使用 SocketException.ErrorCode 屬性獲取特定的錯誤代碼。
10.NetworkStream的簡單示例
建立一個客戶端向服務端傳輸圖片的小示例
服務端一直監聽客戶端傳來的圖片信息
/// <summary> /// 服務端監聽客戶端信息,一旦有發送過來的信息,便當即處理 /// </summary> class Program { //全局TcpClient static TcpClient client; //文件流創建到磁盤上的讀寫流 static FileStream fs = new FileStream("E:\\abc.jpg", FileMode.Create); //buffer static int bufferlength = 200; static byte[] buffer = new byte[bufferlength]; //網絡流 static NetworkStream ns; static void Main(string[] args) { ConnectAndListen(); } static void ConnectAndListen() { //服務端監放任何IP 可是端口號是80的鏈接 TcpListener listener = new TcpListener(IPAddress.Any,80); //監聽對象開始監聽 listener.Start(); while(true) { Console.WriteLine("等待鏈接"); //線程會掛在這裏,直到客戶端發來鏈接請求 client = listener.AcceptTcpClient(); Console.WriteLine("已經鏈接"); //獲得從客戶端傳來的網絡流 ns = client.GetStream(); //若是網絡流中有數據 if (ns.DataAvailable) { //同步讀取網絡流中的byte信息 // do // { // ns.Read(buffer, 0, bufferlength); //} while (readLength > 0); //異步讀取網絡流中的byte信息 ns.BeginRead(buffer, 0, bufferlength, ReadAsyncCallBack, null); } } } /// <summary> /// 異步讀取 /// </summary> /// <param name="result"></param> static void ReadAsyncCallBack(IAsyncResult result) { int readCount; //得到每次異步讀取數量 readCount = client.GetStream().EndRead(result); //若是所有讀完退出,垃圾回收 if (readCount < 1) { client.Close(); ns.Dispose(); fs.Dispose(); return; } //將網絡流中的圖片數據片斷順序寫入本地 fs.Write(buffer, 0, 200); //再次異步讀取 ns.BeginRead(buffer, 0, 200, ReadAsyncCallBack, null); } }
客戶端先鏈接上服務端後在發送圖片,注意若是是雙向通訊的話最好將客戶端和服務端的項目設置爲多個啓動項便於調試
class Program { /// <summary> /// 客戶端 /// </summary> /// <param name="args"></param> static void Main(string[] args) { SendImageToServer("xxx.jpg"); } static void SendImageToServer(string imgURl) { if (!File.Exists(imgURl)) return; //建立一個文件流打開圖片 FileStream fs = File.Open(imgURl, FileMode.Open); //聲明一個byte數組接受圖片byte信息 byte[] fileBytes = new byte[fs.Length]; using (fs) { //將圖片byte信息讀入byte數組中 fs.Read(fileBytes, 0, fileBytes.Length); fs.Close(); } //找到服務器的IP地址 IPAddress address = IPAddress.Parse("127.0.0.1"); //建立TcpClient對象實現與服務器的鏈接 TcpClient client = new TcpClient(); //鏈接服務器 client.Connect(address, 80); using (client) { //鏈接完服務器後便在客戶端和服務端之間產生一個流的通道 NetworkStream ns = client.GetStream(); using (ns) { //經過此通道將圖片數據寫入網絡流,傳向服務器端接收 ns.Write(fileBytes, 0, fileBytes.Length); } } } }
附件: 關於Socket的一個簡單示例
服務器端創建服務而且循環監聽
const int PORT = 80; static Socket clientSocket; static Socket client; static void Main(string[] args) { Thread thread=new Thread(new ThreadStart(SetUpBlockServer)); thread.Start(); } /// <summary> /// 服務器端創建服務而且循環監聽 /// </summary> static void SetUpBlockServer() { //一樣創建TcpListener 監聽對象監聽客戶端傳來的信息 TcpListener lis = new TcpListener(IPAddress.Any, PORT); Console.WriteLine("正在監放任何端口號爲80的任意IP的鏈接"); //啓動監聽 lis.Start(); while (true) { //進程會掛起,知道客戶端的socket發送鏈接請求 clientSocket = lis.AcceptSocket(); Console.WriteLine("{0}時刻接收到客戶端的鏈接請求",DateTime.Now.ToString("G")); //鏈接成功後發送給客戶端信息 string testMessage = "鏈接成功"; clientSocket.Send(Encoding.Default.GetBytes(testMessage)); } }
客戶端鏈接服務端的請求,和循環監聽服務端傳來的信息
class Program { //端口 const int PORT = 80; static void Main(string[] args) { Connect("127.0.0.1"); } /// <summary> /// 創建與服務器端的異步鏈接 /// </summary> /// <param name="server"></param> static void Connect(string server) { //創建一個socket用來和服務的Socket進行通訊 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //得到服務器IP地址 IPAddress ipAddress = IPAddress.Parse(server); //得到服務器端口 IPEndPoint point = new IPEndPoint(ipAddress, PORT); //開始異步鏈接,注意將socket放入異步方法的參數中,提供給回調方法使用 socket.BeginConnect(point, new AsyncCallback(ConnectCallBack), socket); Thread.Sleep(10000000); } /// <summary> ///異步鏈接後的callback事件 /// </summary> /// <param name="result"></param> static void ConnectCallBack(IAsyncResult result) { try { //創建一個接受信息的byte數組 byte[] receiveBuffer = new byte[4098]; //從回調參數中獲取上面Conntect方法中的socket對象 Socket socket = result.AsyncState as Socket; //判斷是否和服務端的socket創建鏈接 if (socket.Connected) { //開始 異步接收服務端傳來的信息,一樣將socket傳入回調方法的參數中 socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), socket); } else { ConnectCallBack(result); } } catch { Console.WriteLine("鏈接出錯"); } } /// <summary> /// 一旦服務器發送信息,則會觸發回調方法 /// </summary> /// <param name="result"></param> static void ReceiveCallBack(IAsyncResult result) { byte[] receiveBuffer = new byte[4098]; Socket socket = result.AsyncState as Socket; //讀取從服務器端傳來的數據,EndReceive是關閉異步接收方法,同時讀取數據 int count = socket.EndReceive(result); if (count > 0) { try { //接受完服務端的數據後的邏輯 } catch { } } // 遞歸監聽服務器端是否發來信息,一旦服務器再次發送信息,客戶端仍然能夠接收到 socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ReceiveCallBack,socket); } }
本章總結
本章簡單介紹了關於NetworkStream以及其周邊的一些衍生知識,這些知識的重要性不言而喻,從Tcp/IP協議到期分層結構,
Socket和NetworkStream 的關係和注意事項,以及Socket在Tcp/IP協議中的角色等等,不知不覺Stream篇快接近於尾聲了
期間感謝你們的關注,從此我會寫新的系列,請你們多多鼓勵