本文將在C#中Socket同步通訊的基礎上,分析和研究Socket非同步編程的實現方法,目的是深入瞭解Socket編程的基本原理,增強對網路遊戲開發相關內容的認識。 什麼是Socket編程的非同步是實現
所謂Socket編程的非同步實現是指按照非同步過程來實現Socket編程,那麼什麼是非同步過程呢,我們把在完成了一次調用後通過狀態、通知和回調來告知調用者的方式成為非同步過程,換句話說,在非同步過程中當調用一個方法時,調用者並不能夠立刻得到結果,只有當這個方法調用完畢後調用者才能獲得調用結果。這樣做的好處是什麼呢。答案是高效。相信大家還記得我們在《C#中Socket通訊編程的同步實現》這篇文章中使用多線程來實現簡單聊天的案例吧,在這個案例中我們需要開啟兩個線程來不斷監聽用戶端的串連和用戶端的訊息,這樣的效率肯定是很低的。那麼現在好了,我們可以通過非同步過程來解決這個問題,下面我們就來看看如何?Socket的非同步通訊。 如何?Socket非同步通訊 服務端 基本流程 建立通訊端 綁定通訊端的IP和連接埠號碼——Bind() 使通訊端處於監聽狀態等待用戶端的串連請求——Listen() 當請求到來後,使用BeginAccept()和EndAccept()方法接受請求,返回新的通訊端 使用BeginSend()/EndSend和BeginReceive()/EndReceive()兩組方法與用戶端進行收發通訊 返回,再次等待新的串連請求 關閉通訊端 程式碼範例
using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;namespace AsyncServer{ public class AsyncTCPServer { public void Start() { //建立通訊端 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); //設定監聽 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("用戶端{0}請求串連...", client.RemoteEndPoint)); AsyncSend(client, "伺服器收到串連請求"); 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("用戶端發送訊息:{0}", Encoding.UTF8.GetString(data))); }, null); } catch (Exception ex) { Console.WriteLine(ex.Message); } } /// <summary> /// 發送訊息 /// </summary> /// <param name="client"></param> /// <param name="p"></param> private void AsyncSend(Socket client, string p) { if (client == null || p == string.Empty) return; //資料轉碼 byte[] data = new byte[1024]; data = Encoding.UTF8.GetBytes(p); try { //開始發送訊息 client.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult => { //完成訊息發送 int length = client.EndSend(asyncResult); //輸出訊息 Console.WriteLine(string.Format("伺服器發出訊息:{0}", p)); }, null); } catch (Exception e) { Console.WriteLine(e.Message); } } }}
用戶端
基本流程 建立通訊端並保證與伺服器的連接埠一致 使用BeginConnect()和EndConnect()這組方法向服務端發送串連請求 使用BeginSend()/EndSend和BeginReceive()/EndReceive()兩組方法與服務端進行收發通訊 關閉通訊端
程式碼範例
using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Net.Sockets;namespace AsyncClient{ public class AsyncTCPClient { /// <summary> /// 串連到伺服器 /// </summary> public void AsynConnect() { //連接埠及IP IPEndPoint ipe = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6065); //建立通訊端 Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //開始串連到伺服器 client.BeginConnect(ipe, asyncResult => { client.EndConnect(asyncResult); //向伺服器發送訊息 AsynSend(client,"你好我是用戶端"); AsynSend(client, "第一條訊息"); AsynSend(client, "第二條訊息"); //接受訊息 AsynRecive(client); }, null); } /// <summary> /// 發送訊息 /// </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 => { //完成發送訊息 int length = socket.EndSend(asyncResult); Console.WriteLine(string.Format("用戶端發送訊息:{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 { //開始接收資料 socket.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult => { int length = socket.EndReceive(asyncResult); Console.WriteLine(string.Format("收到伺服器訊息:{0}", Encoding.UTF8.GetString(data))); AsynRecive(socket); }, null); } catch (Exception ex) { Console.WriteLine("異常資訊:", ex.Message); } } }}
從總體上來講Socket非同步編程的邏輯性更加明確了,因為我們只需要為每一個過程寫好回呼函數就好了。那麼這個樣本的效果如何呢。我們來看看它的示範效果:
總結
和Socket同步編程的案例相比,今天的這個案例可能只是對Socket非同步編程內容的一個簡單應用,因為博主到現在為止都還沒有寫出一個可以進行互動聊天的程式來。在Socket的非同步編程中,服務端不需要為一個用戶端單獨建立一個線程來維護其串連,可是這樣帶來的一個問題就是博主不知道該如何?一個多用戶端的非同步編程的執行個體。如果有朋友知道如何?的話,還希望能夠告訴我,畢竟學習就是一個相互促進的過程啊。好了,最後想說的是博主這段時間研究Socket非同步編程中關於非同步方法呼叫調用的寫法問題。我們知道Socket非同步編程中的方法是成對出現的,每一個方法都有一個回呼函數,對於回呼函數,這裡有兩種寫法,以BeginConnect方法為例:
m_Socket.BeginConnect(this.m_ipEndPoint, new AsyncCallback(this.ConnectCallBack), this.m_Socket);//其中ConnectCallBack是一個回呼函數
或者
m_Socket.BeginConnect(this.m_ipEndPoint,asyncResult=>{ //在這裡添加更多代碼},null)
博主為什麼要在這裡說這兩種寫法呢,有兩個原因:
* 第二種寫法更為簡潔,無需去構造容器傳遞Socket和訊息,因為它們都是局部變數。如果我們使用第一種方法,因為主函數和回呼函數是兩個不同的函數,因此如果想要共用變數就需要通過IAsyncResult介面來訪問容器中的值,這樣顯然增加了我們的工作量。
* 第二種寫法更為優雅,這似乎是C#語言中某種進階文法,具體叫什麼我忘了,反正在Linq中經常看到這種寫法的影子。
綜合以上兩個觀點,博主還是建議大家使用第二種寫法,博主打算有空的話將之前寫的程式再重新寫一遍,看看能不能找出代碼中的問題。好了,今天的內容就是這樣了,謝謝大家,希望大家喜歡。