支持Http串远、Tcp和Udp的類組成了TCP/IP三層模型(請求響應層唾糯、應用協(xié)議層赦邻、傳輸層)的中間層-應用協(xié)議層,該層的類比位于最底層的Socket類提供了更高層次的抽象狠怨,它們封裝
TCP 和 UDP 套接字的創(chuàng)建,不需要處理連接的細節(jié)红氯,這使得我們在編寫套接字級別的協(xié)議時泣懊,可以更多地嘗試使用 TCPClient 、
UDPClient和TcpListener煤墙,而不是直接向 Socket 中寫梅惯。它們之間的這種層次關(guān)系示意如下:
可見,TcpClient 類基于 Socket 類構(gòu)建仿野,這是它能夠以更高的抽象程度提供 TCP
服務的基礎(chǔ)铣减。正因為這樣,許多應用層上的通訊協(xié)議设预,比如FTP(File Transfers Protocol)文件傳輸協(xié)議徙歼、HTTP(Hypertext
Transfers Protocol)超文本傳輸協(xié)議等都直接創(chuàng)建在TcpClient等類之上。
我們對C#語言基礎(chǔ)教程了解多少鳖枕,對Visual C#網(wǎng)絡(luò)編程的TCP又了解多少呢?
TCPClient 類使用 TCP 從 Internet 資源請求數(shù)據(jù)魄梯。TCP 協(xié)議建立與遠程終結(jié)點的連接,然后使用此連接發(fā)送和接收數(shù)據(jù)包宾符。TCP
負責確保將數(shù)據(jù)包發(fā)送到終結(jié)點并在數(shù)據(jù)包到達時以正確的順序?qū)ζ溥M行組合酿秸。
從名字上就可以看出,TcpClient類專為客戶端設(shè)計魏烫,它為 TCP 網(wǎng)絡(luò)服務提供客戶端連接辣苏。TcpClient
提供了通過網(wǎng)絡(luò)連接肝箱、發(fā)送和接收數(shù)據(jù)的簡單方法。
若要建立 TCP 連接稀蟋,必須知道承載所需服務的網(wǎng)絡(luò)設(shè)備的地址(IPAddress)以及該服務用于通訊的 TCP 端口 (Port)煌张。Internet
分配號碼機構(gòu) (Internet Assigned Numbers Authority, IANA) 定義公共服務的端口號(你可以訪問
http://www.iana.org/assignments/port-numbers獲得這方面更詳細的資料)。IANA 列表中所沒有的服務可使用 1,024
到 65,535 這一范圍中的端口號退客。要創(chuàng)建這種連接骏融,你可以選用TcpClient類的三種構(gòu)造函數(shù)之一:
1、public
TcpClient()當使用這種不帶任何參數(shù)的構(gòu)造函數(shù)時萌狂,將使用本機默認的ip地址并將使用默認的通信端口號0档玻。這樣情況下,如果本機不止一個ip地址茫藏,將無法選擇使用误趴。以下語句示例了如何使用默認構(gòu)造函數(shù)來創(chuàng)建新的
TcpClient:
TcpClient tcpClientC = new TcpClient();
2、public
TcpClient(IPEndPoint)使用本機IPEndPoint創(chuàng)建TcpClient的實例對象务傲。上一篇介紹過了凉当,IPEndPoint將網(wǎng)絡(luò)端點表示為IP地址和端口號,在這里它用于指定在建立遠程主機連接時所使用的本地網(wǎng)絡(luò)接口(IP
地址)和端口號树灶,這個構(gòu)造方法為使用本機IPAddress和Port提供了選擇余地纤怒。下面的語句示例了如何使用本地終結(jié)點創(chuàng)建 TcpClient 類的實例:
IPHostEntry ipInfo=Dns.GetHostByName("www.tuha.net");//主機信息
IPAddressList[] ipList=ipInfo.AddressList;//IP地址數(shù)組
IPAddress ip=ipList[0];//多IP地址時一般用第一個
IPEndPoint ipEP=new IPEndPoint(ip,4088);//得到網(wǎng)絡(luò)終結(jié)點
try{
TcpClient tcpClientA = new TcpClient(ipLocalEndPoint);
}
catch (Exception e ) {
Console.WriteLine(e.ToString());
}
到這里,你可能會感到困惑天通,客戶端要和服務端創(chuàng)建連接泊窘,所指定的IP地址及通信端口號應該是遠程服務器的呀!事實上的確如此,使用以上兩種構(gòu)造函數(shù)像寒,你所實現(xiàn)的只是TcpClient實例對象與IP地址和Port端口的綁定烘豹,要完成連接,你還需要顯式指定與遠程主機的連接诺祸,這可以通過TcpClient類的Connect方法來實現(xiàn)携悯,
Connet方法使用指定的主機名和端口號將客戶端連接到 遠程主機:
1)、public void Connect(IPEndPoint); 使用指定的遠程網(wǎng)絡(luò)終結(jié)點將客戶端連接到遠程 TCP 主機筷笨。
public void Connect(IPAddress, int); 使用指定的 IP 地址和端口號將客戶端連接到 TCP 主機憔鬼。
public void Connect(string, int); 將客戶端連接到指定主機上的指定端口。
需要指出的是胃夏,Connect方法的所有重載形式中的參數(shù)IPEndPoint網(wǎng)絡(luò)終結(jié)點轴或、IPAddress以及表現(xiàn)為string的Dns主機名和int指出的Port端口均指的是遠程服務器。
以下示例語句使用主機默認IP和Port端口號0與遠程主機建立連接:
TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對象實例
try{
tcpClient.Connect("www.contoso.com",11002);//建立連接
}
catch (Exception e ){
Console.WriteLine(e.ToString());
}
3仰禀、public TcpClient(string, int);初始化 TcpClient
類的新實例并連接到指定主機上的指定端口照雁。與前兩個構(gòu)造函數(shù)不一樣,這個構(gòu)造函數(shù)將自動建立連接答恶,你不再需要額外調(diào)用Connect方法饺蚊,其中string類型的參數(shù)表示遠程主機的Dns名萍诱,如:www.tuha.net。
以下示例語句調(diào)用這一方法實現(xiàn)與指定主機名和端口號的主機相連:
try{
TcpClient tcpClientB = new TcpClient("www.tuha.net", 4088);
}
catch (Exception e ) {
Console.WriteLine(e.ToString());
}
前面我們說,TcpClient類創(chuàng)建在Socket之上污呼,在Tcp服務方面提供了更高層次的抽象,體現(xiàn)在網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接受方面,是TcpClient使用標準的Stream流處理技術(shù)裕坊,使得它讀寫數(shù)據(jù)更加方便直觀,同時曙求,.Net框架負責提供更豐富的結(jié)構(gòu)來處理流碍庵,貫穿于整個.Net框架中的流具有更廣泛的兼容性,構(gòu)建在更一般化的流操作上的通用方法使我們不再需要困惑于文件的實際內(nèi)容(HTML悟狱、XML
或其他任何內(nèi)容),應用程序都將使用一致的方法(Stream.Write堰氓、Stream.Read) 發(fā)送和接收數(shù)據(jù)挤渐。另外,流在數(shù)據(jù)從 Internet
下載的過程中提供對數(shù)據(jù)的即時訪問双絮,可以在部分數(shù)據(jù)到達時立即開始處理浴麻,而不需要等待應用程序下載完整個數(shù)據(jù)集。.Net中通過NetworkStream類實現(xiàn)了這些處理技術(shù)囤攀。
NetworkStream 類包含在.Net框架的System.Net.Sockets
命名空間里软免,該類專門提供用于網(wǎng)絡(luò)訪問的基礎(chǔ)數(shù)據(jù)流。NetworkStream 實現(xiàn)通過網(wǎng)絡(luò)套接字發(fā)送和接收數(shù)據(jù)的標準.Net
框架流機制焚挠。NetworkStream 支持對網(wǎng)絡(luò)數(shù)據(jù)流的同步和異步訪問膏萧。NetworkStream 從 Stream
繼承,后者提供了一組豐富的用于方便網(wǎng)絡(luò)通訊的方法和屬性蝌衔。
同其它繼承自抽象基類Stream的所有流一樣榛泛,NetworkStream網(wǎng)絡(luò)流也可以被視為一個數(shù)據(jù)通道,架設(shè)在數(shù)據(jù)來源端(客戶Client)和接收端(服務Server)之間噩斟,而后的數(shù)據(jù)讀取及寫入均針對這個通道來進行曹锨。
.Net框架中,NetworkStream流支持兩方面的操作:
1剃允、寫入流沛简。寫入是從數(shù)據(jù)結(jié)構(gòu)到流的數(shù)據(jù)傳輸。
2斥废、讀取流椒楣。讀取是從流到數(shù)據(jù)結(jié)構(gòu)(如字節(jié)數(shù)組)的數(shù)據(jù)傳輸。
與普通流Stream不同的是营袜,網(wǎng)絡(luò)流沒有當前位置的統(tǒng)一概念撒顿,因此不支持查找和對數(shù)據(jù)流的隨機訪問。相應屬性CanSeek 始終返回 false荚板,而
Seek 和 Position 方法也將引發(fā) NotSupportedException凤壁。
基于Socket上的應用協(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
類的新實例拧抖,使用前你需要創(chuàng)建Socket對象實例煤搜,并通過Socket.Connect方法建立與遠程服務端的連接,而后才可以使用該方法得到網(wǎng)絡(luò)傳輸流唧席。示例如下:
Socket s=new
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);//創(chuàng)建客戶端Socket對象實例
try{
s.Connect("www.tuha.net",4088);//建立與遠程主機的連接
}
catch(Exception e){
MessageBox.show("連接錯誤:" +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 類的實例淌哟。使用前你需要先創(chuàng)TcpClient對象實例并建立與遠程主機的連接迹卢,示例如下:
TcpClient tcpClient = new TcpClient();//創(chuàng)建TcpClient對象實例
Try{
tcpClient.Connect("www.tuha.net",4088);//嘗試與遠程主機相連
}
catch(Exception e){
MessageBox.Show("連接錯誤:"+e.Message);
}
try{
NetworkStream stream=tcpClient.GetStream();//獲取網(wǎng)絡(luò)傳輸流
}
catch(Exception e)
{
MessageBox.Show("TcpClient錯誤:"+e.Message);
}
通過以上方法得到NetworkStream網(wǎng)絡(luò)流之后,你就可以使用標準流讀寫方法Write和Read來發(fā)送和接受數(shù)據(jù)了。
以上是.Net下使用TcpClient類實現(xiàn)客戶端編程的技術(shù)資料徒仓,為了向客戶端提供這些服務腐碱,我們還需要編制相應的服務端程序,前一篇《Visual
C#.Net網(wǎng)絡(luò)程序開發(fā)-Socket篇》上曾經(jīng)提到掉弛,
Socket作為其他網(wǎng)絡(luò)協(xié)議的基礎(chǔ)症见,既可以面向客戶端開發(fā),也可以面向服務端開發(fā)殃饿,在傳輸層面上使用較多谋作,而在應用協(xié)議層面上,客戶端我們采用構(gòu)建于Socket類之上的TcpClient取代Socket;相應地乎芳,構(gòu)建于Socket之上的TcpListener提供了更高理念級別的
TCP 服務遵蚜,使得我們能更方便地編寫服務端應用程序。正是因為這樣的原因秒咐,像FTP 和 HTTP 這樣的應用層協(xié)議都是在 TcpListener
類的基礎(chǔ)上建立的谬晕。
.Net中的TCPListener 用于監(jiān)視TCP
端口上的傳入請求,通過綁定本機IP地址和相應端口(這兩者應與客戶端的請求一致)創(chuàng)建TcpListener對象實例,并由Start方法啟動偵聽;當TcpListener偵聽到用戶端的連接后携取,視客戶端的不同請求方式攒钳,通過AcceptTcpClient
方法接受傳入的連接請求并創(chuàng)建 TcpClient 以處理請求,或者通過AcceptSocket 方法接受傳入的連接請求并創(chuàng)建 Socket
以處理請求雷滋。最后不撑,你需要使用 Stop 關(guān)閉用于偵聽傳入連接的 Socket,你必須也關(guān)閉從 AcceptSocket 或 AcceptTcpClient
返回的任何實例晤斩。這個過程詳細解說如下:
首先焕檬,創(chuàng)建TcpListener對象實例,這通過TcpListener類的構(gòu)造方法來實現(xiàn):
public TcpListener(port);//指定本機端口
public TcpListener(IPEndPoint)//指定本機終結(jié)點
public TcpListener(IPAddress,port)//指定本機IP地址及端口
以上方法中的參數(shù)在前面多次提到澳泵,這里不再細述实愚,唯一需要提醒的是,這些參數(shù)均針對服務端主機。下面的示例演示創(chuàng)建 TcpListener
類的實例:
IPHostEntry ipInfo=Dns.Resolve("127.0.0.1");//主機信息
IPAddressList[] ipList=ipInfo.IPAddressList;//IP數(shù)組
IPAddress ip=ipList[0];//IP
try{
TcpListener tcpListener = new TcpListener(ipAddress,
4088);//創(chuàng)建TcpListener對象實例以偵聽用戶端連接
}
catch ( Exception e){
MessageBox.Show("TcpListener錯誤:"+e.Message);
}
隨后腊敲,你需要調(diào)用Start方法啟動偵聽:
public void Start();
其次击喂,當偵聽到有用戶端連接時,需要接受掛起的連接請求碰辅,這通過調(diào)用以下兩方法之一來完成連接:
public Socket AcceptSocket();
public TcpClient AcceptTcpClient();
前一個方法返回代表客戶端的Socket對象懂昂,隨后可以通過Socket 類的 Send 和 Receive
方法與遠程計算機通訊;后一個方法返回代表客戶端的TcpClient對象,隨后使用上面介紹的 TcpClient.GetStream 方法獲取 TcpClient
的基礎(chǔ)網(wǎng)絡(luò)流 NetworkStream没宾,并使用流讀寫Read/Write方法與遠程計算機通訊凌彬。
最后,請記住關(guān)閉偵聽器:public void Stop();
同時關(guān)閉其他連接實例:public void Close();
下面的示例完整體現(xiàn)了上面的過程:
bool done = false;
TcpListener listener = new TcpListener(13);//
創(chuàng)建TcpListener對象實例(13號端口提供時間服務)
listener.Start();//啟動偵聽
while (!done) {//進入無限循環(huán)以偵聽用戶連接
TcpClient client = listener.AcceptTcpClient();//偵聽到連接后創(chuàng)建客戶端連接TcpClient
NetworkStream ns = client.GetStream();//得到網(wǎng)絡(luò)傳輸流
byte[] byteTime =
Encoding.ASCII.GetBytes(DateTime.Now.ToString());//預發(fā)送的內(nèi)容(此為服務端時間)轉(zhuǎn)換為字節(jié)數(shù)組以便寫入流
try {
ns.Write(byteTime, 0, byteTime.Length);//寫入流
ns.Close();//關(guān)閉流
client.Close();//關(guān)閉客戶端連接
}
catch (Exception e) {
MessageBox.Show("流錯誤:"+e.Message)
}
綜合運用上面的知識循衰,下面的實例實現(xiàn)了簡單的網(wǎng)絡(luò)通訊-雙機互連铲敛,針對客戶端和服務端分別編制了應用程序』岫郏客戶端創(chuàng)建到服務端的連接原探,向遠程主機發(fā)送連接請求連接信號,并發(fā)送交談內(nèi)容;遠程主機端接收來自客戶的連接顽素,向客戶端發(fā)回確認連接的信號,同時接收并顯示客戶端的交談內(nèi)容徒蟆。在這個基礎(chǔ)上胁出,發(fā)揮你的創(chuàng)造力,你完全可以開發(fā)出一個基于程序語言(C#)級的聊天室!
客戶端主要源代碼:
public void SendMeg()//發(fā)送信息
{
try
{
int port=Int32.Parse(textBox3.Text.ToString());//遠程主機端口
try
{
tcpClient=new TcpClient(textBox1.Text,port);//創(chuàng)建TcpClient對象實例 }
catch(Exception le)
{
MessageBox.Show("TcpClient Error:"+le.Message);
}
string strDateLine=DateTime.Now.ToShortDateString()+"
"+DateTime.Now.ToLongTimeString();//得到發(fā)送時客戶端時間
netStream=tcpClient.GetStream();//得到網(wǎng)絡(luò)流
sw=new StreamWriter(netStream);//創(chuàng)建TextWriter,向流中寫字符
string words=textBox4.Text;//待發(fā)送的話
string content=strDateLine+words;//待發(fā)送內(nèi)容
sw.Write(content);//寫入流
sw.Close();//關(guān)閉流寫入器
netStream.Close();//關(guān)閉網(wǎng)絡(luò)流
tcpClient.Close();//關(guān)閉客戶端連接
}
catch(Exception ex)
{
MessageBox.Show("Sending Message Failed!"+ex.Message);
}
textBox4.Text="";//清空
}
服務器端主要源代碼:
public void StartListen()//偵聽特定端口的用戶請求
{
//ReceiveMeg();
isLinked=false; //連接標志
try
{
int port=Int32.Parse(textBox1.Text.ToString());//本地待偵聽端口
serverListener=new TcpListener(port);//創(chuàng)建TcpListener對象實例
serverListener.Start(); //啟動偵聽
}
catch(Exception ex)
{
MessageBox.Show("Can‘t Start Server"+ex.Message);
return;
}
isLinked=true;
while(true)//進入無限循環(huán)等待用戶端連接
{
try
{
tcpClient=serverListener.AcceptTcpClient();//創(chuàng)建客戶端連接對象
netStream=tcpClient.GetStream();//得到網(wǎng)絡(luò)流
sr=new StreamReader(netStream);//流讀寫器
}
catch(Exception re)
{
MessageBox.Show(re.Message);
}
string buffer="";
string received="";
received+=sr.ReadLine();//讀流中一行
while(received.Length!=0)
{
buffer+=received;
buffer+="\r\n";
//received="";
received=sr.ReadLine();
}
listBox1.Items.Add(buffer);//顯示
//關(guān)閉
sr.Close();
netStream.Close();
tcpClient.Close();
}
}