TCP協議是一個基本的網路通訊協定,基本上所有的網路服務都是基於TCP協議的,如HTTP,FTP等等,所以要瞭解網路編程就必須瞭解基於TCP協議的編程。然而TCP協議是一個龐雜的體系,要徹底的弄清楚它的實現不是一天兩天的功夫,所幸的是在.net framework環境下,我們不必要去追究TCP協議底層的實現,一樣可以很方便的編寫出基於TCP協議進行網路通訊的程式。
要進行基於TCP協議的網路通訊,首先必須建立同遠程主機的串連,串連地址通常包括兩部分——主機名稱和連接埠,如www.yesky.com:80中,www.yesky.com就是主機名稱,80指主機的80連接埠,當然,主機名稱也可以用IP地址代替。當串連建立之後,就可以使用這個串連去發送和接收資料包,TCP協議的作用就是保證這些資料包能到達終點並且能按照正確的順序組裝起來。
在.net framework的類庫(Class Library)中,提供了兩個用於TCP網路通訊的類,分別是TcpClient和TcpListener。由其英文意義顯而易見,TcpClient類是基於TCP協議的用戶端類,而TcpListener是伺服器端,監聽(Listen)用戶端傳來的串連請求。TcpClient類通過TCP協議與伺服器進行通訊並擷取資訊,它的內部封裝了一個Socket類的執行個體,這個Socket對象被用來使用TCP協議向伺服器請求和擷取資料。因為與遠程主機的互動是以資料流的形式出現的,所以傳輸的資料可以使用.net framework中流處理技術讀寫。在我們下邊的例子中,你可以看到使用NetworkStream類操作資料流的方法。
在下面的例子中,我們將建立一個時間伺服器,包括伺服器端程式和用戶端程式。伺服器端監聽用戶端的串連請求,建立串連以後向用戶端發送當前的系統時間。
先運行伺服器端程式,下面顯示了伺服器端程式啟動並執行狀況:
然後運行用戶端程式,用戶端首先發送串連請求到伺服器端,伺服器端回應後發送目前時間到用戶端,這是用戶端程式的:
發送完成後,伺服器端繼續等待下一次串連:
通過這個例子我們可以瞭解TcpClient類的基本用法,要使用這個類,必須使用System.Net.Socket命名空間,本例用到的三個命名空間如下:
using System;
using System.Net.Sockets;
using System.Text;//從位元組數組中擷取字串時使用該命名空間中的類
首先討論一下用戶端程式,開始我們必須初始化一個TcpClient類的執行個體:
TcpClient client = new TcpClient(hostName, portNum);
然後使用TcpClient類的GetStream()方法擷取資料流,並且用它初始化一個NetworkStream類的執行個體:
NetworkStream ns = client.GetStream();
注意,當使用主機名稱和連接埠號碼初始化TcpClient類的執行個體時,直到跟伺服器建立了串連,這個執行個體才算真正建立,程式才能往下執行。如果因為網路不通,伺服器不存在,伺服器連接埠未開放等等原因而不能串連,程式將拋出異常並且中斷執行。
建立資料流之後,我們可以使用NetworkStream類的Read()方法從流中讀取資料,使用Write()方法向流中寫入資料。讀取資料時,首先應該建立一個緩衝區,具體的說,就是建立一個byte型的數組用來存放從流中讀取的資料。Read()方法的原型描述如下:
public override int Read(in byte[] buffer,int offset,int size)
buffer是緩衝數組,offset是資料(位元組流)在緩衝數組中存放的開始位置,size是讀取的位元組數目,傳回值是讀取的位元組數。在本例中,簡單地使用該方法來讀取伺服器反饋的資訊:
byte[] bytes = new byte[1024];//建立緩衝區
int bytesRead = ns.Read(bytes, 0, bytes.Length);//讀取位元組流
然後顯示到螢幕上:
Console.WriteLine(Encoding.ASCII.GetString(bytes,0,bytesRead));
最後不要忘記關閉串連:
client.Close();
下面是本例完整的程式清單:
using System;
using System.Net.Sockets;
using System.Text;
namespace TcpClientExample
{
public class TcpTimeClient
{
private const int portNum = 13;//伺服器連接埠,可以隨意修改
private const string hostName = "127.0.0.1";//伺服器位址,127.0.0.1指本機
[STAThread]
static void Main(string[] args)
{
try
{
Console.Write("Try to connect to "+hostName+":"+portNum.ToString()+"\r\n");
TcpClient client = new TcpClient(hostName, portNum);
NetworkStream ns = client.GetStream();
byte[] bytes = new byte[1024];
int bytesRead = ns.Read(bytes, 0, bytes.Length);
Console.WriteLine(Encoding.ASCII.GetString(bytes,0,bytesRead));
client.Close();
Console.ReadLine();//由於是控制台程式,故為了清楚的看到結果,可以加上這句
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}
}
上面這個例子清晰地示範了用戶端程式的編寫要點,下面我們討論一下如何建立伺服器程式。這個例子將使用TcpListener類,在13號連接埠監聽,一旦有用戶端串連,將立即向用戶端發送當前伺服器的時間資訊。
TcpListener的關鍵在於AcceptTcpClient()方法,該方法將檢測連接埠是否有未處理的串連請求,如果有未處理的串連請求,該方法將使伺服器同用戶端建立串連,並且返回一個TcpClient對象,通過這個對象的GetStream方法建立同用戶端通訊的資料流。事實上,TcpListener類還提供一個更為靈活的方法AcceptSocket(),當然靈活的代價是複雜,對於比較簡單的程式,AcceptTcpClient()已經足夠用了。此外,TcpListener類提供Start()方法開始監聽,提供Stop()方法停止監聽。
首先我們使用連接埠初始化一個TcpListener執行個體,並且開始在13連接埠監聽:
private const int portNum = 13;
TcpListener listener = new TcpListener(portNum);
listener.Start();//開始監聽
如果有未處理的串連請求,使用AcceptTcpClient方法進行處理,並且擷取資料流:
TcpClient client = listener.AcceptTcpClient();
NetworkStream ns = client.GetStream();
然後,擷取本機時間,並儲存在位元組數組中,使用NetworkStream.Write()方法寫入資料流,然後用戶端就可以通過Read()方法從資料流中擷取這段資訊:
byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());
ns.Write(byteTime, 0, byteTime.Length);
ns.Close();//不要忘記關閉資料流和串連
client.Close();
伺服器端程式完整的程式清單如下:
using System;
using System.Net.Sockets;
using System.Text;
namespace TimeServer
{
class TimeServer
{
private const int portNum = 13;
[STAThread]
static void Main(string[] args)
{
bool done = false;
TcpListener listener = new TcpListener(portNum);
listener.Start();
while (!done)
{
Console.Write("Waiting for connection...");
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Connection accepted.");
NetworkStream ns = client.GetStream();
byte[] byteTime = Encoding.ASCII.GetBytes(DateTime.Now.ToString());
try
{
ns.Write(byteTime, 0, byteTime.Length);
ns.Close();
client.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
listener.Stop();
}
}
}