1. 程式人生 > >C# Socket程式設計(5)使用TCP Socket

C# Socket程式設計(5)使用TCP Socket

TCP 協議(Transmission Control Protocol,傳輸控制協議)是TCP/IP體系中面向連線(connection oriented)的傳輸層(transport layer)TCP協議能夠檢測和恢復IP層提供的主機到主機的通道中可能發生的報文丟失、重複以及其他錯誤。由於TCP協議是一種面向連線協議:在使用它進行通訊之前,兩個應用程式之間首先要建立一個TCP連線。TCP能夠在網路中提供雙工和可靠的的服務。

 閱讀目錄:

1.TCP概述

  通訊雙方建立了TCP連線後,雙方就可以相互發送資料了。TCP負責把使用者資料(位元組流)按照一定格式和長度組成多個數據報進行傳送,然後在接到資料報之後分解按順序重新組裝和恢復使用者資料。

利用TCP傳輸資料時,資料時以位元組的形式進行傳輸的。客戶端和服務端建立連線後,傳送資料方需要先將資料轉換為位元組流,然後將位元組流傳送到對方。TCP協議主要有以下特點:

  1. 是面向連線的傳輸層協議
  2. 每個TCP連線只能有兩個端點,且只能一對一通訊
  3. 通過TCP連線傳送資料能夠保證報文的完整和準確性
  4. 資料只能夠以位元組流的形式傳輸
  5. 傳輸的資料無訊息邊界

2.在.NET平臺TCP應用的工作模式 

   在.NET平臺下開發TCP應用程式,框架提供兩種工作方式,①同步工作方式 ②非同步工作方式。這裡所說的同步工作方式和非同步工作方式和執行緒間的同步並不是一個概念。執行緒間的同步指的是不同執行緒或其他共享資源具有先後關聯的關係;而

同步TCP和非同步TCP指的是TCP程式設計中採用的兩種不同的工作方式,即:從執行到方式、接收或監聽語句時,程式是否繼續往下執行,繼續執行的就是非同步TCP,如果程式阻塞那就是同步TCP

  與同步工作方式和非同步工作方式相對應,利用Socket類開發應用.NET框架也提供了相應的程式設計方式:分別是同步Socket程式設計非同步Socket程式設計。為了簡化程式設計的複雜度,.NET將Socket類進行進一步的封裝,提供了兩個類:TcpClient類和TcpListener類,這兩個類也分別提供同步和非同步工作方式的API。

2.1 瞭解TcpListener和TcpClient

  通過前面我們知道:

類和類簡化了Socket程式設計的複雜度,但是要注意:TcpClientTcpListener這兩個類只支援標準協議程式設計,如果需要編寫非標準協議的應用程式,只能使用Socket來實現。

類用於提供本機主機和遠端主機的連線資訊,而類則用於監聽客戶端的請求(這兩個類的更多資訊可以參考MSDN類庫,文中已經給出連線,這裡就不在贅述了)。當Socket通訊雙發建立了連線後,建立了TcpClient物件,就可以使用該物件的GetStream()方法得到NetworkStream物件,然後再利用網路流物件向遠端主機發送或接收流資料。

3.解決TCP的無訊息邊界問題

   我們知道網路資料傳輸是基於流的,在採用TCP通訊保證了我們接收和傳送資料順序和完整性,但是在實際的網路傳輸過程可能會出現傳送方和接收方訊息不一致的情況。例如:第一次傳送的資料為“123456”,第二次傳送的資料為“ABCDEF”,有時候可能會出現這種情況:“123456ABCDEF”同時接收了;或者是先接收“123456ABC”,然後在接收“DEF”等情況。之所以出現這種情況是因為:TCP是一種以位元組流形式傳輸的、無訊息邊界的協議,由於網路中不確定因數的影響,因此不能夠保證每個Send方法傳送的資料被對應的Recive讀取。所以在實際的Socket應用程式開發是必須要考慮訊息邊界的問題否則就有可能出現數據錯誤等問題。解決TCP訊息邊界一般使用下面的三種方式,我們可以根據場景的不同選用不同的方式。

3.1 傳送固定長度的訊息

  這種方式適用於訊息長度固定的場景。具體實現時可以使用BinaryReader/BinaryWriter物件每次向網路流,傳送/讀取一個固定長度的資料即可。例如每次傳送一個int型別的32位整數。

1             TcpClient client = new TcpClient("www.baidu.com", 5968);
2             NetworkStream m_NetStream = client.GetStream();
3             BinaryWriter bw = new BinaryWriter(m_NetStream, Encoding.UTF8);
4             bw.Write(99);

3.2 將訊息長度與訊息一起傳送

  這種方式一般在每次傳送訊息的前面用4個位元組表面本次訊息的長度,然後將包含訊息長度的訊息傳送給對方;對方收到訊息後,首先從訊息的前四個位元組讀取訊息長度,然後根據訊息長度值接收發送方傳送的資料。這種方法適用於任何場合,在這裡我們可以利用BinaryReaderBinaryWriter物件來對NetworkStream進行進一步的封裝,當我們使用BinaryWriter物件呼叫Write(+18過載)方法向網路流寫入資料時,該方法會自動計算髮出送資料佔用的位元組數,並使用4(根據傳送資料型別)個位元組附加到字串前面;然後另一方使用BinaryReader物件的對應於BinaryWriter物件的Write()方法讀取資料時,它會首先讀取資料的長度,並自動根據資料字首讀取指定長度的資料。

3.3 使用特殊標記分隔訊息

  這種方式適用於訊息中不包含特殊標記的場合。例如:在每個命令後面添加回車換行(\r\n)符號作為分隔符的場合。如果對於字串處理,實現這種方法最簡便的途徑是使用StreamWriter物件和StreamReader物件。傳送時使用StreamWriter物件的WriteLine()方法將傳送的字串寫入網路流,接收方只需需要呼叫StreamReader物件的ReadLine()方法將以回車換行符作為分隔符的字串從網路流中讀取即可。

4.同步TCP Socket 示例程式

   經過前面知識的積澱,現在我們直接通過建立一個簡單的聊天程式來基於同步TCP Socket網路聊天程式,程式的實現比較簡單,伺服器接收多個客戶端連線,客戶端和客戶端直接通過伺服器中轉訊息達到相互通訊的目的。客戶端和伺服器直接的訊息互動使用JSON來進行傳遞,在這裡使用了第三方的JSON庫JSON.NET(關於JSON.NET的使用細節參考:)。下面是程式執行的效果:

執行服務端:

  

開啟多個客戶端,在線上列表中選擇聊天物件,傳送聊天資訊:

  

示例原始碼:猛擊下載

參考資料&進一步閱讀