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

來源:互聯網
上載者:User

一、原理: 

    首先要理解基本的原理,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的類:

usingSystem;using System.Collections.Generic;using System.Net.Sockets;namespace test{  public class Test  {        protected TcpClient tcpclient = null;  //全域用戶端對象        protected NetworkStream networkstream = null;//全域資料流傳輸對象        /// <summary>        /// 進行遠程伺服器的串連        /// </summary>        /// <param name="ip">ip地址</param>        /// <param name="port">連接埠</param>        public Test(string ip, int port)        {            networkstream = null;            tcpclient = new TcpClient();  //對象轉換成實體            tcpclient.BeginConnect(System.Net.IPAddress.Parse(ip), port, new AsyncCallback(Connected), tcpclient);  //開始進行嘗試串連        }         /// <summary>        /// 發送資料        /// </summary>        /// <param name="data">資料</param>        public void SendData(byte[] data)        {              if (networkstream != null)                   networkstream.Write(data, 0, data.Length);  //向伺服器發送資料        }        /// <summary>        /// 關閉        /// </summary>        public void Close()        {            networkstream.Dispose(); //釋放資料流傳輸對象            tcpclient.Close(); //關閉串連        }        /// <summary>        /// 關閉        /// </summary>        /// <param name="result">傳入參數</param>        protected void Connected(IAsyncResult result)        {                TcpClient tcpclt = (TcpClient)result.AsyncState;  //將傳遞的參數強制轉換成TcpClient                networkstream = tcpclt.GetStream();  //擷取資料流傳輸對象                byte[] data = new byte[1000];  //建立傳輸的緩衝                networkstream.BeginRead(data, 0, 1000, new AsyncCallback(DataRec), data); //掛起資料的接收等待        }        /// <summary>        /// 資料接收委託函數        /// </summary>        /// <param name="result">傳入參數</param>        protected void DataRec(IAsyncResult result)        {                int length = networkstream.EndRead(result);  //擷取接收資料的長度                List<byte> data = new List<byte>(); //建立byte數組                data.AddRange((byte[])result.AsyncState); //擷取資料                data.RemoveRange(length, data.Count - length); //根據長度移除無效的資料                byte[] data2 = new byte[1000]; //重新定義接收緩衝                networkstream.BeginRead(data2, 0, 1000, new AsyncCallback(DataRec), data2);  //重新掛起資料的接收等待                //自訂代碼地區,處理資料data                if (length == 0)                {                    //串連已經關閉                }        }    }}

      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的類,代碼如下:

public class TSever    {        public List<TClient> Clients = new List<TClient>();  //用戶端列表        private TcpListener tcplistener = null;  //偵聽對象        /// <summary>        /// 建構函式        /// </summary>        /// <param name="port">偵聽連接埠</param>        public TSever(int port)        {            tcplistener = new TcpListener(System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0], port);  //啟動偵聽            tcplistener.Start(); //啟動偵聽            tcplistener.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplistener); //開始嘗試用戶端的串連        }        private void ClientAccept(IAsyncResult result)        {            TcpListener tcplst = (TcpListener)result.AsyncState;            TcpClient bak_tcpclient = tcplst.EndAcceptTcpClient(result);            TClient bak_client = new TClient(bak_tcpclient, this);            Clients.Add(bak_client);            tcplst.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplst);        }}        public class TClient        {            private TcpClient tcpclient = null;  //用戶端對象            private NetworkStream networkstream = null;  //資料發送對象            private TSever m_Parent=null;  //父級類            /// <summary>            /// 建構函式            /// </summary>            /// <param name="tcpclt">用戶端對象</param>            /// <param name="parent">父級</param>            public TClient(TcpClient tcpclt, TSever parent)            {                    this.tcpclient = tcpclt;                    this.m_Parent = parent;                    string ip = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Address.ToString(); //擷取用戶端IP                    string port = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Port.ToString();  //擷取用戶端連接埠                    this.networkstream = tcpclt.GetStream();  //擷取資料轉送對象                    byte[] data = new byte[1024];                    this.networkstream.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);//啟動資料偵聽            }            /// <summary>            /// 資料接收            /// </summary>            /// <param name="result"></param>            private void DataRec(IAsyncResult result)            {                    int length = networkstream.EndRead(result);                    List<byte> data = new List<byte>();                    data.AddRange((byte[])result.AsyncState);                    byte[] data2 = new byte[1024];                    networkstream.BeginRead(data2, 0, MaxRec, new AsyncCallback(DataRec), data2);                    if (length == 0)                    {                        m_Parent.Clients.Remove(this);  //告知父類刪除此用戶端                    }                    else                    {                        data.RemoveRange(length, data.Count - length);                         //資料處理代碼data                    }            }            /// <summary>            /// 發送資料            /// </summary>            /// <param name="data">資料</param>            /// <returns></returns>            public bool SendData(byte[] data)            {                networkstream.Write(data, 0, data.Length);                return (true);            }        }

摘自: http://hi.baidu.com/des_sky/item/a12969c83801acbc0d0a7bb2


 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.