使用SOCKET實現TCP/IP協議的通訊

一、原理: 

    首先要理解基本的原理,2臺電腦間實現TCP通訊,首先要建立起連接,在這裏要提到服務器端與客戶端,兩個的區別通俗講就是主動與被動的關係,兩個人對話,肯定是先有人先發起會話,要不然誰都不講,談什麼話題,呵呵!一樣,TCPIP下建立連接首先要有一個服務器,它是被動的,它只能等待別人跟它建立連接,自己不會去主動連接,那客戶端如何去連接它呢,這裏提到2個東西,IP地址和端口號,通俗來講就是你去拜訪某人,知道了他的地址是一號大街2號樓,這個是IP地址,那麼1號樓這麼多門牌號怎麼區分,嗯!門牌號就是端口(這裏提到一點,我們訪問網頁的時候也是IP地址和端口號,IE默認的端口號是80),一個服務器可以接受多個客戶端的連接,但是一個客戶端只能連接一臺服務器,在連接後,服務器自動劃分內存區域以分配各個客戶端的通訊,那麼,那麼多的客戶端服務器如何區分,你可能會說,根據IP麼,不是很完整,很簡單的例子,你一臺計算機開3個QQ,服務器怎麼區分?所以準確的說是IP和端口號,但是客戶端的端口號不是由你自己定的,是由計算機自動分配的,要不然就出現端口衝突了,說的這麼多,看下面的這張圖就簡單明瞭了。


    在上面這張圖中,你可以理解爲程序A和程序B是2個SOCKET程序,服務器端程序A設置端口爲81,已接受到3個客戶端的連接,計算機C開了2個程序,分別連接到E和D,而他的端口是計算機自動分配的,連接到E的端口爲789,連接到D的爲790。

    瞭解了TCPIP通訊的基本結構後,接下來講解建立的流程,首先聲明一下我用的開發環境是Visual Studio2008版的,語言C#,組件System.Net.Sockets,流程的建立包括服務器端的建立和客戶端的建立,如圖所示:




二、實現:


      1.客戶端:


      第一步,要創建一個客戶端對象TcpClient(命名空間在System.Net.Sockets),接着,調用對象下的方法BeginConnect進行嘗試連接,入口參數有4個,address(目標IP地址),port(目標端口號),requestCallback(連接成功後的返調函數),state(傳遞參數,是一個對象,隨便什麼都行,我建議是將TcpClient自己傳遞過去),調用完畢這個函數,系統將進行嘗試連接服務器。

      第二步,在第一步講過一個入口參數requestCallback(連接成功後的返調函數),比如我們定義一個函數void Connected(IAsyncResult result),在連接服務器成功後,系統會調用此函數,在函數裏,我們要獲取到系統分配的數據流傳輸對象(NetworkStream),這個對象是用來處理客戶端與服務器端數據傳輸的,此對象由TcpClient獲得,在第一步講過入口參數state,如果我們傳遞了TcpClient進去,那麼,在函數裏我們可以根據入口參數state獲得,將其進行強制轉換TcpClient tcpclt = (TcpClient)result.AsyncState,接着獲取數據流傳輸對象NetworkStream ns = tcpclt.GetStream(),此對象我建議弄成全局變量,以便於其他函數調用,接着我們將掛起數據接收等待,調用ns下的方法BeginRead,入口參數有5個,buff(數據緩衝),offset(緩衝起始序號),size(緩衝長度),callback(接收到數據後的返調函數),state(傳遞參數,一樣,隨便什麼都可以,建議將buff傳遞過去),調用完畢函數後,就可以進行數據接收等待了,在這裏因爲已經創建了NetworkStream對象,所以也可以進行向服務器發送數據的操作了,調用ns下的方法Write就可以向服務器發送數據了,入口參數3個,buff(數據緩衝),offset(緩衝起始序號),size(緩衝長度)。

      第三步,在第二步講過調用了BeginRead函數時的一個入口參數callback(接收到數據後的返調函數),比如我們定義了一個函數void DataRec(IAsyncResult result),在服務器向客戶端發送數據後,系統會調用此函數,在函數裏我們要獲得數據流(byte數組),在上一步講解BeginRead函數的時候還有一個入口參數state,如果我們傳遞了buff進去,那麼,在這裏我們要強制轉換成byte[]類型byte[] data= (byte[])result.AsyncState,轉換完畢後,我們還要獲取緩衝區的大小int length = ns.EndRead(result),ns爲上一步創建的NetworkStream全局對象,接着我們就可以對數據進行處理了,如果獲取的length爲0表示客戶端已經斷開連接。

    具體實現代碼,在這裏我建立了一個名稱爲Test的類:

  1. usingSystem;  
  2. using System.Collections.Generic;  
  3. using System.Net.Sockets;  
  4. namespace test  
  5. {  
  6.   public class Test  
  7.   {  
  8.         protected TcpClient tcpclient = null;  //全局客戶端對象  
  9.         protected NetworkStream networkstream = null;//全局數據流傳輸對象  
  10.         /// <summary>  
  11.         /// 進行遠程服務器的連接  
  12.         /// </summary>  
  13.         /// <param name="ip">ip地址</param>  
  14.         /// <param name="port">端口</param>  
  15.         public Test(string ip, int port)  
  16.         {  
  17.             networkstream = null;  
  18.             tcpclient = new TcpClient();  //對象轉換成實體  
  19.             tcpclient.BeginConnect(System.Net.IPAddress.Parse(ip), port, new AsyncCallback(Connected), tcpclient);  //開始進行嘗試連接  
  20.         }  
  21.          /// <summary>  
  22.         /// 發送數據  
  23.         /// </summary>  
  24.         /// <param name="data">數據</param>  
  25.         public void SendData(byte[] data)  
  26.         {  
  27.               if (networkstream != null)   
  28.                   networkstream.Write(data, 0, data.Length);  //向服務器發送數據  
  29.         }  
  30.         /// <summary>  
  31.         /// 關閉  
  32.         /// </summary>  
  33.         public void Close()  
  34.         {  
  35.             networkstream.Dispose(); //釋放數據流傳輸對象  
  36.             tcpclient.Close(); //關閉連接  
  37.         }  
  38.         /// <summary>  
  39.         /// 關閉  
  40.         /// </summary>  
  41.         /// <param name="result">傳入參數</param>  
  42.         protected void Connected(IAsyncResult result)  
  43.         {  
  44.                 TcpClient tcpclt = (TcpClient)result.AsyncState;  //將傳遞的參數強制轉換成TcpClient  
  45.                 networkstream = tcpclt.GetStream();  //獲取數據流傳輸對象  
  46.                 byte[] data = new byte[1000];  //新建傳輸的緩衝  
  47.                 networkstream.BeginRead(data, 0, 1000, new AsyncCallback(DataRec), data); //掛起數據的接收等待  
  48.         }  
  49.         /// <summary>  
  50.         /// 數據接收委託函數  
  51.         /// </summary>  
  52.         /// <param name="result">傳入參數</param>  
  53.         protected void DataRec(IAsyncResult result)  
  54.         {  
  55.                 int length = networkstream.EndRead(result);  //獲取接收數據的長度  
  56.                 List<byte> data = new List<byte>(); //新建byte數組  
  57.                 data.AddRange((byte[])result.AsyncState); //獲取數據  
  58.                 data.RemoveRange(length, data.Count - length); //根據長度移除無效的數據  
  59.                 byte[] data2 = new byte[1000]; //重新定義接收緩衝  
  60.                 networkstream.BeginRead(data2, 0, 1000, new AsyncCallback(DataRec), data2);  //重新掛起數據的接收等待  
  61.                 //自定義代碼區域,處理數據data  
  62.                 if (length == 0)  
  63.                 {  
  64.                     //連接已經關閉  
  65.                 }  
  66.         }  
  67.     }  
  68. }  

      2.服務器端:

    相對於客戶端的實現,服務器端的實現稍複雜一點,因爲前面講過,一個服務器端可以接受N個客戶端的連接,因此,在服務器端,有必要對每個連接上來的客戶端進行登記,因此服務器端的程序結構包括了2個程序結構,第一個程序結構主要負責啓動服務器、對來訪的客戶端進行登記和撤銷,因此我們需要建立2個類。

    第一個程序結構負責服務器的啓動與客戶端連接的登記,首先建立TcpListener網絡偵聽類,建立的時候構造函數分別包括localaddr和port2個參數,localaddr指的是本地地址,也就是服務器的IP地址,有人會問爲什麼它自己不去自動獲得本機的地址?關於這個舉個很簡單的例子,服務器安裝了2個網卡,也就有了2個IP地址,那建立服務器的時候就可以選擇偵聽的使用的是哪個網絡端口了,不過一般的電腦只有一個網絡端口,你可以懶點直接寫個固定的函數直接獲取IP地址System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0],GetHostAddresses函數就是獲取本機的IP地址,默認選擇第一個端口於是後面加個[0],第2個參數port是真偵聽的端口,這個簡單,自己決定,如果出現端口衝突,函數自己會提醒錯誤的。第二步,啓動服務器,TcpListener.Start()。第三步,啓動客戶端的嘗試連接,TcpListener.BeginAcceptTcpClient,入口2個參數,callback(客戶端連接上後的返調函數),state(傳遞參數,跟第二節介紹的一樣,隨便什麼都可以,建立把TcpListener自身傳遞過去),第四步,建立客戶端連接上來後的返調函數,比如我們建立個名爲void ClientAccept(IAsyncResult result)的函數,函數裏,我們要獲取客戶端的對象,第三步裏講過我們傳遞TcpListener參數進去,在這裏,我們通過入口參數獲取它TcpListener tcplst = (TcpListener)result.AsyncState,獲取客戶端對象TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result),這個bak_tcpclient我建議在類裏面建立個列表,然後把它加進去,因爲下一個客戶端連接上來後此對象就會被沖刷掉了,客戶端處理完畢後,接下來我們要啓動下一個客戶端的連接tcplst.BeginAcceptTcpClient(new AsyncCallback(sub_ClientAccept), tcplst),這個和第三步是一樣的,我就不重複了。

     第二個程序結構主要負責單個客戶端與服務器端的處理程序,主要負責數據的通訊,方法很類似客戶端的代碼,基本大同,除了不需要啓動連接的函數,因此這個程序結構主要啓動下數據的偵聽的功能、判斷斷開的功能、數據發送的功能即可,在第一個程序第四步我們獲取了客戶端的對象bak_tcpclient,在這裏,我們首先啓動數據偵聽功能NetworkStream ns= bak_tcpclient.GetStream();ns.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);這個跟我在第二節裏介紹的是一模一樣的(第二節第10行),還有數據的處理函數,數據發送函數,判斷連接已斷開的代碼與第二節也是一模一樣的,不過在這裏我們需要額外的添加一段代碼,當判斷出連接已斷開的時候,我們要將客戶端告知第一個程序結構進行刪除客戶端操作,這個方法我的實現方法是在建立第二個程序結構的時候,將第一個程序結構當參數傳遞進來,判斷連接斷開後,調用第一個程序結構的公開方法去刪除,即從客戶端列表下刪除此對象。

    第一個程序結構我們定義一個TSever的類,第二個程序結構我們一個TClient的類,代碼如下:

  1. public class TSever  
  2.     {  
  3.         public List<TClient> Clients = new List<TClient>();  //客戶端列表  
  4.         private TcpListener tcplistener = null;  //偵聽對象  
  5.         /// <summary>  
  6.         /// 構造函數  
  7.         /// </summary>  
  8.         /// <param name="port">偵聽端口</param>  
  9.         public TSever(int port)  
  10.         {  
  11.             tcplistener = new TcpListener(System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0], port);  //啓動偵聽  
  12.             tcplistener.Start(); //啓動偵聽  
  13.             tcplistener.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplistener); //開始嘗試客戶端的連接  
  14.         }  
  15.         private void ClientAccept(IAsyncResult result)  
  16.         {  
  17.             TcpListener tcplst = (TcpListener)result.AsyncState;  
  18.             TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result);  
  19.             TClient bak_client = new TClient(bak_tcpclient, this);  
  20.             Clients.Add(bak_client);  
  21.             tcplst.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplst);  
  22.         }  
  23. }  
  24.         public class TClient  
  25.         {  
  26.             private TcpClient tcpclient = null;  //客戶端對象  
  27.             private NetworkStream networkstream = null;  //數據發送對象  
  28.             private TSever m_Parent=null;  //父級類  
  29.             /// <summary>  
  30.             /// 構造函數  
  31.             /// </summary>  
  32.             /// <param name="tcpclt">客戶端對象</param>  
  33.             /// <param name="parent">父級</param>  
  34.             public TClient(TcpClient tcpclt, TSever parent)  
  35.             {  
  36.                     this.tcpclient = tcpclt;  
  37.                     this.m_Parent = parent;  
  38.                     string ip = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Address.ToString(); //獲取客戶端IP  
  39.                     string port = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Port.ToString();  //獲取客戶端端口  
  40.                     this.networkstream = tcpclt.GetStream();  //獲取數據傳輸對象  
  41.                     byte[] data = new byte[1024];  
  42.                     this.networkstream.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);//啓動數據偵聽  
  43.             }  
  44.             /// <summary>  
  45.             /// 數據接收  
  46.             /// </summary>  
  47.             /// <param name="result"></param>  
  48.             private void DataRec(IAsyncResult result)  
  49.             {  
  50.                     int length = networkstream.EndRead(result);  
  51.                     List<byte> data = new List<byte>();  
  52.                     data.AddRange((byte[])result.AsyncState);  
  53.                     byte[] data2 = new byte[1024];  
  54.                     networkstream.BeginRead(data2, 0, MaxRec, new AsyncCallback(DataRec), data2);  
  55.                     if (length == 0)  
  56.                     {  
  57.                         m_Parent.Clients.Remove(this);  //告知父類刪除此客戶端  
  58.                     }  
  59.                     else  
  60.                     {  
  61.                         data.RemoveRange(length, data.Count - length);  
  62.                          //數據處理代碼data  
  63.                     }  
  64.             }  
  65.             /// <summary>  
  66.             /// 發送數據  
  67.             /// </summary>  
  68.             /// <param name="data">數據</param>  
  69.             /// <returns></returns>  
  70.             public bool SendData(byte[] data)  
  71.             {  
  72.                 networkstream.Write(data, 0, data.Length);  
  73.                 return (true);  
  74.             }  
  75.         }  

摘自:http://hi.baidu.com/des_sky/item/a12969c83801acbc0d0a7bb2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章