C#中異步Socket通信編程代碼實(shí)例
本文將在C#中Socket同步通信的基礎(chǔ)上,分析和研究Socket異步編程的實(shí)現(xiàn)方法,目的是深入了解Socket編程的基本原理,增強(qiáng)對(duì)網(wǎng)絡(luò)游戲開發(fā)相關(guān)內(nèi)容的認(rèn)識(shí)。
什么是Socket編程的異步是實(shí)現(xiàn)
所謂Socket編程的異步實(shí)現(xiàn)是指按照異步過程來實(shí)現(xiàn)Socket編程,那么什么是異步過程呢,我們把在完成了一次調(diào)用后通過狀態(tài)、通知和回調(diào)來告知調(diào)用者的方式成為異步過程,換句話說,在異步過程中當(dāng)調(diào)用一個(gè)方法時(shí),調(diào)用者并不能夠立刻得到結(jié)果,只有當(dāng)這個(gè)方法調(diào)用完畢后調(diào)用者才能獲得調(diào)用結(jié)果。這樣做的好處是什么呢?答案是高效。相信大家還記得我們?cè)凇禖#中Socket通信編程的同步實(shí)現(xiàn)》這篇文章中使用多線程來實(shí)現(xiàn)簡(jiǎn)單聊天的案例吧,在這個(gè)案例中我們需要開啟兩個(gè)線程來不斷監(jiān)聽客戶端的連接和客戶端的消息,這樣的效率肯定是很低的。那么現(xiàn)在好了,我們可以通過異步過程來解決這個(gè)問題,下面我們就來看看如何實(shí)現(xiàn)Socket的異步通信。
如何實(shí)現(xiàn)Socket異步通信
服務(wù)端
基本流程
1.創(chuàng)建套接字
2.綁定套接字的IP和端口號(hào)——Bind()
3.使套接字處于監(jiān)聽狀態(tài)等待客戶端的連接請(qǐng)求——Listen()
4.當(dāng)請(qǐng)求到來后,使用BeginAccept()和EndAccept()方法接受請(qǐng)求,返回新的套接字
5.使用BeginSend()/EndSend和BeginReceive()/EndReceive()兩組方法與客戶端進(jìn)行收發(fā)通信
6.返回,再次等待新的連接請(qǐng)求
7.關(guān)閉套接字
代碼示例
using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; namespace AsyncServer { public class AsyncTCPServer { public void Start() { //創(chuàng)建套接字 IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6065); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //綁定端口和IP socket.Bind(ipe); //設(shè)置監(jiān)聽 socket.Listen(10); //連接客戶端 AsyncAccept(socket); } /// <summary> /// 連接到客戶端 /// </summary> /// <param name="socket"></param> private void AsyncAccept(Socket socket) { socket.BeginAccept(asyncResult => { //獲取客戶端套接字 Socket client = socket.EndAccept(asyncResult); Console.WriteLine(string.Format("客戶端{(lán)0}請(qǐng)求連接...", client.RemoteEndPoint)); AsyncSend(client, "服務(wù)器收到連接請(qǐng)求"); AsyncSend(client, string.Format("歡迎你{0}",client.RemoteEndPoint)); AsyncReveive(client); }, null); } /// <summary> /// 接收消息 /// </summary> /// <param name="client"></param> private void AsyncReveive(Socket socket) { byte[] data = new byte[1024]; try { //開始接收消息 socket.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult => { int length = socket.EndReceive(asyncResult); Console.WriteLine(string.Format("客戶端發(fā)送消息:{0}", Encoding.UTF8.GetString(data))); }, null); } catch (Exception ex) { Console.WriteLine(ex.Message); } } /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="client"></param> /// <param name="p"></param> private void AsyncSend(Socket client, string p) { if (client == null || p == string.Empty) return; //數(shù)據(jù)轉(zhuǎn)碼 byte[] data = new byte[1024]; data = Encoding.UTF8.GetBytes(p); try { //開始發(fā)送消息 client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => { //完成消息發(fā)送 int length = client.EndSend(asyncResult); //輸出消息 Console.WriteLine(string.Format("服務(wù)器發(fā)出消息:{0}", p)); }, null); } catch (Exception e) { Console.WriteLine(e.Message); } } } }
客戶端
基本流程
1.創(chuàng)建套接字并保證與服務(wù)器的端口一致
2.使用BeginConnect()和EndConnect()這組方法向服務(wù)端發(fā)送連接請(qǐng)求
3.使用BeginSend()/EndSend和BeginReceive()/EndReceive()兩組方法與服務(wù)端進(jìn)行收發(fā)通信
4.關(guān)閉套接字
代碼示例
using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; namespace AsyncClient { public class AsyncTCPClient { /// <summary> /// 連接到服務(wù)器 /// </summary> public void AsynConnect() { //端口及IP IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6065); //創(chuàng)建套接字 Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //開始連接到服務(wù)器 client.BeginConnect(ipe, asyncResult => { client.EndConnect(asyncResult); //向服務(wù)器發(fā)送消息 AsynSend(client,"你好我是客戶端"); AsynSend(client, "第一條消息"); AsynSend(client, "第二條消息"); //接受消息 AsynRecive(client); }, null); } /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="socket"></param> /// <param name="message"></param> public void AsynSend(Socket socket, string message) { if (socket == null || message == string.Empty) return; //編碼 byte[] data = Encoding.UTF8.GetBytes(message); try { socket.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => { //完成發(fā)送消息 int length = socket.EndSend(asyncResult); Console.WriteLine(string.Format("客戶端發(fā)送消息:{0}", message)); }, null); } catch (Exception ex) { Console.WriteLine("異常信息:{0}", ex.Message); } } /// <summary> /// 接收消息 /// </summary> /// <param name="socket"></param> public void AsynRecive(Socket socket) { byte[] data = new byte[1024]; try { //開始接收數(shù)據(jù) socket.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult => { int length = socket.EndReceive(asyncResult); Console.WriteLine(string.Format("收到服務(wù)器消息:{0}", Encoding.UTF8.GetString(data))); AsynRecive(socket); }, null); } catch (Exception ex) { Console.WriteLine("異常信息:", ex.Message); } } } }
從總體上來講Socket異步編程的邏輯性更加明確了,因?yàn)槲覀冎恍枰獮槊恳粋€(gè)過程寫好回調(diào)函數(shù)就好了。那么這個(gè)示例的效果如何呢?我們來看看它的演示效果:
總結(jié)
和Socket同步編程的案例相比,今天的這個(gè)案例可能只是對(duì)Socket異步編程內(nèi)容的一個(gè)簡(jiǎn)單應(yīng)用,因?yàn)椴┲鞯浆F(xiàn)在為止都還沒有寫出一個(gè)可以進(jìn)行交互聊天的程序來。在Socket的異步編程中,服務(wù)端不需要為一個(gè)客戶端單獨(dú)創(chuàng)建一個(gè)線程來維護(hù)其連接,可是這樣帶來的一個(gè)問題就是博主不知道該如何實(shí)現(xiàn)一個(gè)多客戶端的異步編程的實(shí)例。如果有朋友知道如何實(shí)現(xiàn)的話,還希望能夠告訴我,畢竟學(xué)習(xí)就是一個(gè)相互促進(jìn)的過程啊。好了,最后想說的是博主這段時(shí)間研究Socket異步編程中關(guān)于異步方法調(diào)用的寫法問題。我們知道Socket異步編程中的方法是成對(duì)出現(xiàn)的,每一個(gè)方法都有一個(gè)回調(diào)函數(shù),對(duì)于回調(diào)函數(shù),這里有兩種寫法,以BeginConnect方法為例:
m_Socket.BeginConnect(this.m_ipEndPoint,
new AsyncCallback(this.ConnectCallBack),
this.m_Socket);//其中ConnectCallBack是一個(gè)回調(diào)函數(shù)
或者
m_Socket.BeginConnect(this.m_ipEndPoint,asyncResult=>
{
//在這里添加更多代碼
},null)
博主為什么要在這里說這兩種寫法呢,有兩個(gè)原因:
* 第二種寫法更為簡(jiǎn)潔,無需去構(gòu)造容器傳遞Socket和消息,因?yàn)樗鼈兌际蔷植孔兞?。如果我們使用第一種方法,因?yàn)橹骱瘮?shù)和回調(diào)函數(shù)是兩個(gè)不同的函數(shù),因此如果想要共享變量就需要通過IAsyncResult接口來訪問容器中的值,這樣顯然增加了我們的工作量。
* 第二種寫法更為優(yōu)雅,這似乎是C#語(yǔ)言中某種高級(jí)語(yǔ)法,具體叫什么我忘了,反正在Linq中經(jīng)??吹竭@種寫法的影子。
綜合以上兩個(gè)觀點(diǎn),博主還是建議大家使用第二種寫法,博主打算有空的話將之前寫的程序再重新寫一遍,看看能不能找出代碼中的問題。好了,今天的內(nèi)容就是這樣了,謝謝大家,希望大家喜歡!
相關(guān)文章
C# LINQ查詢表達(dá)式及對(duì)應(yīng)LAMBDA表達(dá)式的用法
這篇文章主要介紹了C# LINQ查詢表達(dá)式及對(duì)應(yīng)LAMBDA表達(dá)式的用法,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-04-04C#調(diào)用打印機(jī)實(shí)現(xiàn)打印
這篇文章介紹了C#調(diào)用打印機(jī)實(shí)現(xiàn)打印的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04C#獲取某路徑文件夾中全部圖片或其它指定格式的文件名的實(shí)例方法
在本篇文章里小編給大家整理的是關(guān)于C#獲取某路徑文件夾中全部圖片或其它指定格式的文件名的實(shí)例方法,需要的朋友們參考下。2019-10-10C#使用RestSharp實(shí)現(xiàn)封裝常用的http請(qǐng)求方法
這篇文章主要為大家詳細(xì)介紹了C#如何使用RestSharp實(shí)現(xiàn)封裝常用的http請(qǐng)求方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-02-02Avalonia封裝實(shí)現(xiàn)指定組件允許拖動(dòng)的工具類
這篇文章主要為大家詳細(xì)介紹了Avalonia如何封裝實(shí)現(xiàn)指定組件允許拖動(dòng)的工具類,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起來學(xué)習(xí)學(xué)習(xí)吧2023-03-03c# 如何實(shí)現(xiàn)獲取二維數(shù)組的列數(shù)
這篇文章主要介紹了c# 實(shí)現(xiàn)獲取二維數(shù)組的列數(shù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-04-04深入C# winform清除由GDI繪制出來的所有線條或圖形的解決方法
本篇文章是對(duì)在C#中使用winform清除由GDI繪制出來的所有線條或圖形的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05