asp.net平臺(tái)下C#實(shí)現(xiàn)Socket通信
TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,又名網(wǎng)絡(luò)通訊協(xié)議。簡(jiǎn)單來(lái)說(shuō):TCP控制傳輸數(shù)據(jù),負(fù)責(zé)發(fā)現(xiàn)傳輸?shù)膯?wèn)題,一旦有問(wèn)題就發(fā)出信號(hào),要求重新傳輸,直到所有數(shù)據(jù)安全正確地傳輸?shù)侥康牡?,而IP是負(fù)責(zé)給因特網(wǎng)中的每一臺(tái)電腦定義一個(gè)地址,以便傳輸。從協(xié)議分層模型方面來(lái)講:TCP/IP由:網(wǎng)絡(luò)接口層(鏈路層)、網(wǎng)絡(luò)層、傳輸層、應(yīng)用層。它和OSI的七層結(jié)構(gòu)以及對(duì)于協(xié)議族不同,下圖簡(jiǎn)單表示:
注:最上的圖示:TCP/IP的四層結(jié)構(gòu)對(duì)應(yīng)OSI七層結(jié)構(gòu)。
中間的圖示:TCP/IP協(xié)議模塊關(guān)系圖。
最下的圖示:TCP/IP協(xié)議族在OSI七層中的位置及對(duì)應(yīng)的功能。
現(xiàn)階段socket通信使用TCP、UDP協(xié)議,相對(duì)應(yīng)UDP來(lái)說(shuō),TCP則是比較安全穩(wěn)定的協(xié)議了。本文只涉及到TCP協(xié)議來(lái)說(shuō)socket通信。首先講述TCP/IP的三次握手,在握手基礎(chǔ)上延伸socket通信的基本過(guò)程。
下面介紹對(duì)于應(yīng)屆生畢業(yè)面試來(lái)說(shuō)是非常熟悉的,同時(shí)也是最臭名昭著的三次握手:
- 1 客戶端發(fā)送syn報(bào)文到服務(wù)器端,并置發(fā)送序號(hào)為x。
- 2 服務(wù)器端接收到客戶端發(fā)送的請(qǐng)求報(bào)文,然后向客戶端發(fā)送syn報(bào)文,并且發(fā)送確認(rèn)序號(hào)x+1,并置發(fā)送序號(hào)為y。
- 3 客戶端受到服務(wù)器發(fā)送確認(rèn)報(bào)文后,發(fā)送確認(rèn)信號(hào)y+1,并置發(fā)送序號(hào)為z。至此客戶端和服務(wù)器端建立連接。
在此基礎(chǔ)上,socket連接過(guò)程:
- 服務(wù)器監(jiān)聽(tīng):服務(wù)器端socket并不定位具體的客戶端socket,而是處于等待監(jiān)聽(tīng)狀態(tài),實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)。
- 客戶端請(qǐng)求:客戶端clientSocket發(fā)送連接請(qǐng)求,目標(biāo)是服務(wù)器的serverSocket。為此,clientSocket必須知道serverSocket的地址和端口號(hào),進(jìn)行掃描發(fā)出連接請(qǐng)求。
- 連接確認(rèn):當(dāng)服務(wù)器socket監(jiān)聽(tīng)到或者是受到客戶端socket的連接請(qǐng)求時(shí),服務(wù)器就響應(yīng)客戶端的請(qǐng)求,建議一個(gè)新的socket,把服務(wù)器socket發(fā)送給客戶端,一旦客戶端確認(rèn)連接,則連接建立。
注:在連接確認(rèn)階段:服務(wù)器socket即使在和一個(gè)客戶端socket建立連接后,還在處于監(jiān)聽(tīng)狀態(tài),仍然可以接收到其他客戶端的連接請(qǐng)求,這也是一對(duì)多產(chǎn)生的原因。
下圖簡(jiǎn)單說(shuō)明連接過(guò)程:
socket連接原理知道了,此處編寫(xiě)最基本最簡(jiǎn)單的socket通信:
服務(wù)器端:
int port = 6000; string host = "127.0.0.1"; IPAddress ip = IPAddress.Parse(host); IPEndPoint ipe = new IPEndPoint(ip, port); Socket sSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sSocket.Bind(ipe); sSocket.Listen(0); Console.WriteLine("監(jiān)聽(tīng)已經(jīng)打開(kāi),請(qǐng)等待"); //receive message Socket serverSocket = sSocket.Accept(); Console.WriteLine("連接已經(jīng)建立"); string recStr = ""; byte[] recByte = new byte[4096]; int bytes = serverSocket.Receive(recByte, recByte.Length, 0); recStr += Encoding.ASCII.GetString(recByte, 0, bytes); //send message Console.WriteLine("服務(wù)器端獲得信息:{0}", recStr); string sendStr = "send to client :hello"; byte[] sendByte = Encoding.ASCII.GetBytes(sendStr); serverSocket.Send(sendByte, sendByte.Length, 0); serverSocket.Close(); sSocket.Close();
客戶端:
int port = 6000; string host = "127.0.0.1";//服務(wù)器端ip地址 IPAddress ip = IPAddress.Parse(host); IPEndPoint ipe = new IPEndPoint(ip, port); Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); clientSocket.Connect(ipe); //send message string sendStr = "send to server : hello,ni hao"; byte[] sendBytes = Encoding.ASCII.GetBytes(sendStr); clientSocket.Send(sendBytes); //receive message string recStr = ""; byte[] recBytes = new byte[4096]; int bytes = clientSocket.Receive(recBytes, recBytes.Length, 0); recStr += Encoding.ASCII.GetString(recBytes, 0, bytes); Console.WriteLine(recStr); clientSocket.Close();
上述服務(wù)器端和客戶端建立通信,在互相發(fā)送一次信息后通信便結(jié)束,而在大家進(jìn)行的項(xiàng)目中,這樣的通信肯定滿足不了需求。于是接著介紹異步通信,簡(jiǎn)單來(lái)說(shuō)就是服務(wù)器端和客戶端可以進(jìn)行多次互發(fā)信息的通信而不用擔(dān)心通道會(huì)關(guān)閉。在介紹異步通信時(shí),客戶端和服務(wù)器端的連接和上面介紹的同步通信建立連接的方式是一樣的,這里只寫(xiě)出服務(wù)器端和客戶端發(fā)送信息的方法和接收信息的方法。(服務(wù)器端和客戶端的發(fā)送、接收的方法是一樣的)
首先寫(xiě)出異步連接的方法吧:
public void Connect(IPAddress ip, int port) { this.clientSocket.BeginConnect(ip, port, new AsyncCallback(ConnectCallback), this.clientSocket); } private void ConnectCallback(IAsyncResult ar) { try { Socket handler = (Socket)ar.AsyncState; handler.EndConnect(ar); } catch (SocketException ex) { } }
發(fā)送信息方法:
public void Send(string data) { Send(System.Text.Encoding.UTF8.GetBytes(data)); } private void Send(byte[] byteData) { try { int length = byteData.Length; byte[] head = BitConverter.GetBytes(length); byte[] data = new byte[head.Length + byteData.Length]; Array.Copy(head, data, head.Length); Array.Copy(byteData, 0, data, head.Length, byteData.Length); this.clientSocket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(SendCallback), this.clientSocket); } catch (SocketException ex) { } } private void SendCallback(IAsyncResult ar) { try { Socket handler = (Socket)ar.AsyncState; handler.EndSend(ar); } catch (SocketException ex) { } }
接收信息的方法:
public void ReceiveData() { clientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null); } private void ReceiveCallback(IAsyncResult ar) { try { int REnd = clientSocket.EndReceive(ar); if (REnd > 0) { byte[] data = new byte[REnd]; Array.Copy(MsgBuffer, 0, data, 0, REnd); //在此次可以對(duì)data進(jìn)行按需處理 clientSocket.BeginReceive(MsgBuffer, 0, MsgBuffer.Length, 0, new AsyncCallback(ReceiveCallback), null); } else { dispose(); } } catch (SocketException ex) { } } private void dispose() { try { this.clientSocket.Shutdown(SocketShutdown.Both); this.clientSocket.Close(); } catch (Exception ex) { } }
異步問(wèn)題解決了,再寫(xiě)一個(gè)自己在使用過(guò)程中經(jīng)常出現(xiàn)的一個(gè)問(wèn)題。接收的數(shù)據(jù)包處理問(wèn)題:在網(wǎng)絡(luò)通信中,使用異步進(jìn)行通信,那么客戶端在接收服務(wù)器發(fā)送來(lái)的數(shù)據(jù)包的處理上會(huì)有一些麻煩,比如粘包、斷包,這是一些小問(wèn)題,此處簡(jiǎn)單寫(xiě)寫(xiě)自己處理此問(wèn)題的一個(gè)方法。
粘包處理:
public Hashtable DataTable = new Hashtable();//因?yàn)橐邮盏蕉鄠€(gè)服務(wù)器(ip)發(fā)送的數(shù)據(jù),此處按照ip地址分開(kāi)存儲(chǔ)發(fā)送數(shù)據(jù) public void DataArrial(byte[] Data , string ip) { try { if (Data.Length < 12)//按照需求進(jìn)行判斷 { lock (DataTable) { if (DataTable.Contains(ip)) { DataTable[ip] = Data; return; } } } if (Data[0] != 0x1F || Data[1] != 0xF1)//標(biāo)志位(按照需求編寫(xiě)) { if (DataTable.Contains(ip)) { if (DataTable != null) { byte[] oldData = (byte[])DataTable[ip];//取出粘包數(shù)據(jù) if (oldData[0] != 0x1F || oldData[1] != 0xF1) { return; } byte[] newData = new byte[Data.Length + oldData.Length]; Array.Copy(oldData, 0, newData, 0, oldData.Length); Array.Copy(Data, 0, newData, oldData.Length, Data.Length);//組成新數(shù)據(jù)數(shù)組,先到的數(shù)據(jù)排在前面,后到的數(shù)據(jù)放在后面 lock (DataTable) { DataTable[ip] = null; } DataArrial(newData, ip); return; } } return; } int revDataLength = Data[2];//打算發(fā)送數(shù)據(jù)的長(zhǎng)度 int revCount = Data.Length;//接收的數(shù)據(jù)長(zhǎng)度 if (revCount > revDataLength)//如果接收的數(shù)據(jù)長(zhǎng)度大于發(fā)送的數(shù)據(jù)長(zhǎng)度,說(shuō)明存在多幀數(shù)據(jù),繼續(xù)處理 { byte[] otherData = new byte[revCount - revDataLength]; Data.CopyTo(otherData, revCount - 1); Array.Copy(Data, revDataLength, otherData, 0, otherData.Length); Data = (byte[])Redim(Data, revDataLength); DataArrial(otherData, ip); } if (revCount < revDataLength) //接收到的數(shù)據(jù)小于要發(fā)送的長(zhǎng)度 { if (DataTable.Contains(ip)) { DataTable[ip] = Data;//更新當(dāng)前粘包數(shù)據(jù) return; } } //此處可以按需進(jìn)行數(shù)據(jù)處理 } catch (Exception ex) { } } private Array Redim(Array origArray, Int32 desizedSize) { //確認(rèn)每個(gè)元素的類型 Type t = origArray.GetType().GetElementType(); //創(chuàng)建一個(gè)含有期望元素個(gè)數(shù)的新數(shù)組 //新數(shù)組的類型必須匹配數(shù)組的類型 Array newArray = Array.CreateInstance(t, desizedSize); //將原數(shù)組中的元素拷貝到新數(shù)組中。 Array.Copy(origArray, 0, newArray, 0, Math.Min(origArray.Length, desizedSize)); //返回新數(shù)組 return newArray; }
socket最基本的內(nèi)容終于寫(xiě)完了,結(jié)合上面的信息進(jìn)行簡(jiǎn)單的應(yīng)用應(yīng)該是沒(méi)有問(wèn)題,可是如果牽涉到比較服務(wù)的通信問(wèn)題,其解決的方法就需要委托、多線程、接口方面的知識(shí)了,這方面最近正在學(xué)習(xí),最近有一個(gè)感悟:委托是.net下C#中最基本最重要的部分了吧,應(yīng)該必須學(xué)會(huì)。
到此這篇關(guān)于asp.net平臺(tái)下C#實(shí)現(xiàn)Socket通信的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#簡(jiǎn)單獲取屏幕鼠標(biāo)坐標(biāo)點(diǎn)顏色方法介紹
C#簡(jiǎn)單獲取屏幕鼠標(biāo)坐標(biāo)點(diǎn)顏色方法介紹;有需求的朋友可以參考下2012-11-11使用vs2022在.net6中調(diào)試帶typescript的靜態(tài)頁(yè)面
這篇文章介紹了使用vs2022在.net6中調(diào)試帶typescript的靜態(tài)頁(yè)面,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12.Net報(bào)表開(kāi)發(fā)控件XtraReport介紹
這篇文章介紹了.Net報(bào)表開(kāi)發(fā)控件XtraReport,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06.Net結(jié)構(gòu)型設(shè)計(jì)模式之裝飾模式(Decorator)
這篇文章介紹了.Net結(jié)構(gòu)型設(shè)計(jì)模式之裝飾模式(Decorator),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05ASP.NET Core開(kāi)發(fā)Docker部署
這篇文章介紹了ASP.NET Core開(kāi)發(fā)Docker部署的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-12-12那些年,我還在學(xué)asp.net(一) 學(xué)習(xí)筆記
那些年到此,基本學(xué)習(xí)了前端的基本知識(shí),那些年的第四課就是asp.net,當(dāng)然那時(shí)看了很多教程,比如說(shuō):天轟穿,當(dāng)然天轟穿說(shuō)得比較多,如面向?qū)ο?,C#知識(shí),由于當(dāng)時(shí)上過(guò)C++,所以就沒(méi)有看這些,直接從asp.net開(kāi)始,主要是學(xué)習(xí)一下asp.net用到的一些基本控件2012-03-03