1. 程式人生 > >C#中Socket通訊程式設計的非同步實現

C#中Socket通訊程式設計的非同步實現

本文將在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非同步程式設計中關於非同步方法呼叫的寫法問題。我們知道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中經常看到這種寫法的影子。

綜合以上兩個觀點,博主還是建議大家使用第二種寫法,博主打算有空的話將之前寫的程式再重新寫一遍,看看能不能找出程式碼中的問題。好了,今天的內容就是這樣了,謝謝大家,希望大家喜歡!