使用SOCKET实现TCP/IP协议的通讯
一、原理:?
?? ?首先要理解基本的原理,2臺(tái)電腦間實(shí)現(xiàn)TCP通訊,首先要建立起連接,在這里要提到服務(wù)器端與客戶(hù)端,兩個(gè)的區(qū)別通俗講就是主動(dòng)與被動(dòng)的關(guān)系,兩個(gè)人對(duì)話(huà),肯定是先有人先發(fā)起會(huì)話(huà),要不然誰(shuí)都不講,談什么話(huà)題,呵呵!一樣,TCPIP下建立連接首先要有一個(gè)服務(wù)器,它是被動(dòng)的,它只能等待別人跟它建立連接,自己不會(huì)去主動(dòng)連接,那客戶(hù)端如何去連接它呢,這里提到2個(gè)東西,IP地址和端口號(hào),通俗來(lái)講就是你去拜訪某人,知道了他的地址是一號(hào)大街2號(hào)樓,這個(gè)是IP地址,那么1號(hào)樓這么多門(mén)牌號(hào)怎么區(qū)分,嗯!門(mén)牌號(hào)就是端口(這里提到一點(diǎn),我們?cè)L問(wèn)網(wǎng)頁(yè)的時(shí)候也是IP地址和端口號(hào),IE默認(rèn)的端口號(hào)是80),一個(gè)服務(wù)器可以接受多個(gè)客戶(hù)端的連接,但是一個(gè)客戶(hù)端只能連接一臺(tái)服務(wù)器,在連接后,服務(wù)器自動(dòng)劃分內(nèi)存區(qū)域以分配各個(gè)客戶(hù)端的通訊,那么,那么多的客戶(hù)端服務(wù)器如何區(qū)分,你可能會(huì)說(shuō),根據(jù)IP么,不是很完整,很簡(jiǎn)單的例子,你一臺(tái)計(jì)算機(jī)開(kāi)3個(gè)QQ,服務(wù)器怎么區(qū)分?所以準(zhǔn)確的說(shuō)是IP和端口號(hào),但是客戶(hù)端的端口號(hào)不是由你自己定的,是由計(jì)算機(jī)自動(dòng)分配的,要不然就出現(xiàn)端口沖突了,說(shuō)的這么多,看下面的這張圖就簡(jiǎn)單明了了。
????在上面這張圖中,你可以理解為程序A和程序B是2個(gè)SOCKET程序,服務(wù)器端程序A設(shè)置端口為81,已接受到3個(gè)客戶(hù)端的連接,計(jì)算機(jī)C開(kāi)了2個(gè)程序,分別連接到E和D,而他的端口是計(jì)算機(jī)自動(dòng)分配的,連接到E的端口為789,連接到D的為790。
????了解了TCPIP通訊的基本結(jié)構(gòu)后,接下來(lái)講解建立的流程,首先聲明一下我用的開(kāi)發(fā)環(huán)境是Visual?Studio2008版的,語(yǔ)言C#,組件System.Net.Sockets,流程的建立包括服務(wù)器端的建立和客戶(hù)端的建立,如圖所示:
二、實(shí)現(xiàn):
? ? ? 1.客戶(hù)端:
? ? ? 第一步,要?jiǎng)?chuàng)建一個(gè)客戶(hù)端對(duì)象TcpClient(命名空間在System.Net.Sockets),接著,調(diào)用對(duì)象下的方法BeginConnect進(jìn)行嘗試連接,入口參數(shù)有4個(gè),address(目標(biāo)IP地址),port(目標(biāo)端口號(hào)),requestCallback(連接成功后的返調(diào)函數(shù)),state(傳遞參數(shù),是一個(gè)對(duì)象,隨便什么都行,我建議是將TcpClient自己傳遞過(guò)去),調(diào)用完畢這個(gè)函數(shù),系統(tǒng)將進(jìn)行嘗試連接服務(wù)器。
? ? ? 第二步,在第一步講過(guò)一個(gè)入口參數(shù)requestCallback(連接成功后的返調(diào)函數(shù)),比如我們定義一個(gè)函數(shù)void?Connected(IAsyncResult?result),在連接服務(wù)器成功后,系統(tǒng)會(huì)調(diào)用此函數(shù),在函數(shù)里,我們要獲取到系統(tǒng)分配的數(shù)據(jù)流傳輸對(duì)象(NetworkStream),這個(gè)對(duì)象是用來(lái)處理客戶(hù)端與服務(wù)器端數(shù)據(jù)傳輸?shù)?#xff0c;此對(duì)象由TcpClient獲得,在第一步講過(guò)入口參數(shù)state,如果我們傳遞了TcpClient進(jìn)去,那么,在函數(shù)里我們可以根據(jù)入口參數(shù)state獲得,將其進(jìn)行強(qiáng)制轉(zhuǎn)換TcpClient?tcpclt?=?(TcpClient)result.AsyncState,接著獲取數(shù)據(jù)流傳輸對(duì)象NetworkStream?ns?=?tcpclt.GetStream(),此對(duì)象我建議弄成全局變量,以便于其他函數(shù)調(diào)用,接著我們將掛起數(shù)據(jù)接收等待,調(diào)用ns下的方法BeginRead,入口參數(shù)有5個(gè),buff(數(shù)據(jù)緩沖),offset(緩沖起始序號(hào)),size(緩沖長(zhǎng)度),callback(接收到數(shù)據(jù)后的返調(diào)函數(shù)),state(傳遞參數(shù),一樣,隨便什么都可以,建議將buff傳遞過(guò)去),調(diào)用完畢函數(shù)后,就可以進(jìn)行數(shù)據(jù)接收等待了,在這里因?yàn)橐呀?jīng)創(chuàng)建了NetworkStream對(duì)象,所以也可以進(jìn)行向服務(wù)器發(fā)送數(shù)據(jù)的操作了,調(diào)用ns下的方法Write就可以向服務(wù)器發(fā)送數(shù)據(jù)了,入口參數(shù)3個(gè),buff(數(shù)據(jù)緩沖),offset(緩沖起始序號(hào)),size(緩沖長(zhǎng)度)。
? ? ? 第三步,在第二步講過(guò)調(diào)用了BeginRead函數(shù)時(shí)的一個(gè)入口參數(shù)callback(接收到數(shù)據(jù)后的返調(diào)函數(shù)),比如我們定義了一個(gè)函數(shù)void?DataRec(IAsyncResult?result),在服務(wù)器向客戶(hù)端發(fā)送數(shù)據(jù)后,系統(tǒng)會(huì)調(diào)用此函數(shù),在函數(shù)里我們要獲得數(shù)據(jù)流(byte數(shù)組),在上一步講解BeginRead函數(shù)的時(shí)候還有一個(gè)入口參數(shù)state,如果我們傳遞了buff進(jìn)去,那么,在這里我們要強(qiáng)制轉(zhuǎn)換成byte[]類(lèi)型byte[]?data=?(byte[])result.AsyncState,轉(zhuǎn)換完畢后,我們還要獲取緩沖區(qū)的大小int?length?=?ns.EndRead(result),ns為上一步創(chuàng)建的NetworkStream全局對(duì)象,接著我們就可以對(duì)數(shù)據(jù)進(jìn)行處理了,如果獲取的length為0表示客戶(hù)端已經(jīng)斷開(kāi)連接。
????具體實(shí)現(xiàn)代碼,在這里我建立了一個(gè)名稱(chēng)為T(mén)est的類(lèi):
usingSystem; using System.Collections.Generic; using System.Net.Sockets; namespace test {public class Test{protected TcpClient tcpclient = null; //全局客戶(hù)端對(duì)象protected NetworkStream networkstream = null;//全局?jǐn)?shù)據(jù)流傳輸對(duì)象/// <summary>/// 進(jìn)行遠(yuǎn)程服務(wù)器的連接/// </summary>/// <param name="ip">ip地址</param>/// <param name="port">端口</param>public Test(string ip, int port){networkstream = null;tcpclient = new TcpClient(); //對(duì)象轉(zhuǎn)換成實(shí)體tcpclient.BeginConnect(System.Net.IPAddress.Parse(ip), port, new AsyncCallback(Connected), tcpclient); //開(kāi)始進(jìn)行嘗試連接}/// <summary>/// 發(fā)送數(shù)據(jù)/// </summary>/// <param name="data">數(shù)據(jù)</param>public void SendData(byte[] data){if (networkstream != null) networkstream.Write(data, 0, data.Length); //向服務(wù)器發(fā)送數(shù)據(jù)}/// <summary>/// 關(guān)閉/// </summary>public void Close(){networkstream.Dispose(); //釋放數(shù)據(jù)流傳輸對(duì)象tcpclient.Close(); //關(guān)閉連接}/// <summary>/// 關(guān)閉/// </summary>/// <param name="result">傳入?yún)?shù)</param>protected void Connected(IAsyncResult result){TcpClient tcpclt = (TcpClient)result.AsyncState; //將傳遞的參數(shù)強(qiáng)制轉(zhuǎn)換成TcpClientnetworkstream = tcpclt.GetStream(); //獲取數(shù)據(jù)流傳輸對(duì)象byte[] data = new byte[1000]; //新建傳輸?shù)木彌_networkstream.BeginRead(data, 0, 1000, new AsyncCallback(DataRec), data); //掛起數(shù)據(jù)的接收等待}/// <summary>/// 數(shù)據(jù)接收委托函數(shù)/// </summary>/// <param name="result">傳入?yún)?shù)</param>protected void DataRec(IAsyncResult result){int length = networkstream.EndRead(result); //獲取接收數(shù)據(jù)的長(zhǎng)度List<byte> data = new List<byte>(); //新建byte數(shù)組data.AddRange((byte[])result.AsyncState); //獲取數(shù)據(jù)data.RemoveRange(length, data.Count - length); //根據(jù)長(zhǎng)度移除無(wú)效的數(shù)據(jù)byte[] data2 = new byte[1000]; //重新定義接收緩沖networkstream.BeginRead(data2, 0, 1000, new AsyncCallback(DataRec), data2); //重新掛起數(shù)據(jù)的接收等待//自定義代碼區(qū)域,處理數(shù)據(jù)dataif (length == 0){//連接已經(jīng)關(guān)閉}}} }
? ? ? 2.服務(wù)器端:
? ??相對(duì)于客戶(hù)端的實(shí)現(xiàn),服務(wù)器端的實(shí)現(xiàn)稍復(fù)雜一點(diǎn),因?yàn)榍懊嬷v過(guò),一個(gè)服務(wù)器端可以接受N個(gè)客戶(hù)端的連接,因此,在服務(wù)器端,有必要對(duì)每個(gè)連接上來(lái)的客戶(hù)端進(jìn)行登記,因此服務(wù)器端的程序結(jié)構(gòu)包括了2個(gè)程序結(jié)構(gòu),第一個(gè)程序結(jié)構(gòu)主要負(fù)責(zé)啟動(dòng)服務(wù)器、對(duì)來(lái)訪的客戶(hù)端進(jìn)行登記和撤銷(xiāo),因此我們需要建立2個(gè)類(lèi)。
????第一個(gè)程序結(jié)構(gòu)負(fù)責(zé)服務(wù)器的啟動(dòng)與客戶(hù)端連接的登記,首先建立TcpListener網(wǎng)絡(luò)偵聽(tīng)類(lèi),建立的時(shí)候構(gòu)造函數(shù)分別包括localaddr和port2個(gè)參數(shù),localaddr指的是本地地址,也就是服務(wù)器的IP地址,有人會(huì)問(wèn)為什么它自己不去自動(dòng)獲得本機(jī)的地址?關(guān)于這個(gè)舉個(gè)很簡(jiǎn)單的例子,服務(wù)器安裝了2個(gè)網(wǎng)卡,也就有了2個(gè)IP地址,那建立服務(wù)器的時(shí)候就可以選擇偵聽(tīng)的使用的是哪個(gè)網(wǎng)絡(luò)端口了,不過(guò)一般的電腦只有一個(gè)網(wǎng)絡(luò)端口,你可以懶點(diǎn)直接寫(xiě)個(gè)固定的函數(shù)直接獲取IP地址System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0],GetHostAddresses函數(shù)就是獲取本機(jī)的IP地址,默認(rèn)選擇第一個(gè)端口于是后面加個(gè)[0],第2個(gè)參數(shù)port是真?zhèn)陕?tīng)的端口,這個(gè)簡(jiǎn)單,自己決定,如果出現(xiàn)端口沖突,函數(shù)自己會(huì)提醒錯(cuò)誤的。第二步,啟動(dòng)服務(wù)器,TcpListener.Start()。第三步,啟動(dòng)客戶(hù)端的嘗試連接,TcpListener.BeginAcceptTcpClient,入口2個(gè)參數(shù),callback(客戶(hù)端連接上后的返調(diào)函數(shù)),state(傳遞參數(shù),跟第二節(jié)介紹的一樣,隨便什么都可以,建立把TcpListener自身傳遞過(guò)去),第四步,建立客戶(hù)端連接上來(lái)后的返調(diào)函數(shù),比如我們建立個(gè)名為void?ClientAccept(IAsyncResult?result)的函數(shù),函數(shù)里,我們要獲取客戶(hù)端的對(duì)象,第三步里講過(guò)我們傳遞TcpListener參數(shù)進(jìn)去,在這里,我們通過(guò)入口參數(shù)獲取它TcpListener?tcplst?=?(TcpListener)result.AsyncState,獲取客戶(hù)端對(duì)象TcpClient?bak_tcpclient?=?tcplst.EndAcceptTcpClient(result),這個(gè)bak_tcpclient我建議在類(lèi)里面建立個(gè)列表,然后把它加進(jìn)去,因?yàn)橄乱粋€(gè)客戶(hù)端連接上來(lái)后此對(duì)象就會(huì)被沖刷掉了,客戶(hù)端處理完畢后,接下來(lái)我們要啟動(dòng)下一個(gè)客戶(hù)端的連接tcplst.BeginAcceptTcpClient(new?AsyncCallback(sub_ClientAccept),?tcplst),這個(gè)和第三步是一樣的,我就不重復(fù)了。
?????第二個(gè)程序結(jié)構(gòu)主要負(fù)責(zé)單個(gè)客戶(hù)端與服務(wù)器端的處理程序,主要負(fù)責(zé)數(shù)據(jù)的通訊,方法很類(lèi)似客戶(hù)端的代碼,基本大同,除了不需要啟動(dòng)連接的函數(shù),因此這個(gè)程序結(jié)構(gòu)主要啟動(dòng)下數(shù)據(jù)的偵聽(tīng)的功能、判斷斷開(kāi)的功能、數(shù)據(jù)發(fā)送的功能即可,在第一個(gè)程序第四步我們獲取了客戶(hù)端的對(duì)象bak_tcpclient,在這里,我們首先啟動(dòng)數(shù)據(jù)偵聽(tīng)功能NetworkStream?ns=?bak_tcpclient.GetStream();ns.BeginRead(data,?0,?1024,?new?AsyncCallback(DataRec),?data);這個(gè)跟我在第二節(jié)里介紹的是一模一樣的(第二節(jié)第10行),還有數(shù)據(jù)的處理函數(shù),數(shù)據(jù)發(fā)送函數(shù),判斷連接已斷開(kāi)的代碼與第二節(jié)也是一模一樣的,不過(guò)在這里我們需要額外的添加一段代碼,當(dāng)判斷出連接已斷開(kāi)的時(shí)候,我們要將客戶(hù)端告知第一個(gè)程序結(jié)構(gòu)進(jìn)行刪除客戶(hù)端操作,這個(gè)方法我的實(shí)現(xiàn)方法是在建立第二個(gè)程序結(jié)構(gòu)的時(shí)候,將第一個(gè)程序結(jié)構(gòu)當(dāng)參數(shù)傳遞進(jìn)來(lái),判斷連接斷開(kāi)后,調(diào)用第一個(gè)程序結(jié)構(gòu)的公開(kāi)方法去刪除,即從客戶(hù)端列表下刪除此對(duì)象。
????第一個(gè)程序結(jié)構(gòu)我們定義一個(gè)TSever的類(lèi),第二個(gè)程序結(jié)構(gòu)我們一個(gè)TClient的類(lèi),代碼如下:
public class TSever{public List<TClient> Clients = new List<TClient>(); //客戶(hù)端列表private TcpListener tcplistener = null; //偵聽(tīng)對(duì)象/// <summary>/// 構(gòu)造函數(shù)/// </summary>/// <param name="port">偵聽(tīng)端口</param>public TSever(int port){tcplistener = new TcpListener(System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())[0], port); //啟動(dòng)偵聽(tīng)tcplistener.Start(); //啟動(dòng)偵聽(tīng)tcplistener.BeginAcceptTcpClient(new AsyncCallback(ClientAccept), tcplistener); //開(kāi)始嘗試客戶(hù)端的連接}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; //客戶(hù)端對(duì)象private NetworkStream networkstream = null; //數(shù)據(jù)發(fā)送對(duì)象private TSever m_Parent=null; //父級(jí)類(lèi)/// <summary>/// 構(gòu)造函數(shù)/// </summary>/// <param name="tcpclt">客戶(hù)端對(duì)象</param>/// <param name="parent">父級(jí)</param>public TClient(TcpClient tcpclt, TSever parent){this.tcpclient = tcpclt;this.m_Parent = parent;string ip = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Address.ToString(); //獲取客戶(hù)端IPstring port = ((IPEndPoint)tcpclient.Client.RemoteEndPoint).Port.ToString(); //獲取客戶(hù)端端口this.networkstream = tcpclt.GetStream(); //獲取數(shù)據(jù)傳輸對(duì)象byte[] data = new byte[1024];this.networkstream.BeginRead(data, 0, 1024, new AsyncCallback(DataRec), data);//啟動(dòng)數(shù)據(jù)偵聽(tīng)}/// <summary>/// 數(shù)據(jù)接收/// </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); //告知父類(lèi)刪除此客戶(hù)端}else{data.RemoveRange(length, data.Count - length);//數(shù)據(jù)處理代碼data}}/// <summary>/// 發(fā)送數(shù)據(jù)/// </summary>/// <param name="data">數(shù)據(jù)</param>/// <returns></returns>public bool SendData(byte[] data){networkstream.Write(data, 0, data.Length);return (true);}}摘自:http://hi.baidu.com/des_sky/item/a12969c83801acbc0d0a7bb2
?
總結(jié)
以上是生活随笔為你收集整理的使用SOCKET实现TCP/IP协议的通讯的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 每天读5分钟,受益匪浅、
- 下一篇: 论“性能需求分析”系列专题(一)之 性能