程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 異步Socket

異步Socket

編輯:關於C#

在網絡通訊的編程中我們經常使用到Socket, 這種情況下我們往往需要長期的監聽某個端口, 以獲得相應的Socket, 然後再利用它進行相關操作.但是這樣的話, 主線程就會被阻塞.無法對其他時間做出相應.其實在.Net的Socket類中提供了對異步操作的支持.下面將介紹其基本原理, 以及利用它做的一個P2P的實現.

背景知識:

你需要了解有關Socket的基本知識, 以及Delegate的異步調用操作.

在這個例子中, 我們實現了一個利用非阻塞(non-blocking)的Socket進行局域網通訊的P2P應用.每個客戶擁有一個Grid(類似於一個二維數組), 當它啟動Grid設置服務的時候,一旦別的客戶與它相連就可以查詢並修改某個網格中的數值.(比如查詢 grid[1][2]的值).

運行步驟:

1.啟動服務 在某個客戶端輸入 start 400 (400是端口號, 你可以任意指定)

2.連接其他Peer 在另一個客戶端中輸入 connect 202.119.9.12 400 (202.119.9.12 400是某個開啟服務的客戶端的IP地址)

3.輸入 get 1 1 表示你想獲得grid[1][1]這個網格中的數值.默認情況下得到0

4.輸入 set 1 1 5 表示你想設置grid[1][1]這個網格中的數值為5 .

5.再次輸入 get 1 1 查詢到結果為已修改的5

6.輸入shutdown 關閉與剛才與當前的Peer的連接.你可以再次連接別的Peer

運行示意圖.

 

在通常的應用中Server往往需要長期處於監聽狀態, 以等待Client的連接.下面是一個典型的應用.

private Socket client = null;
const int nPortListen = 399;
try
{
TcpListener listener = new TcpListener( nPortListen );
Console.WriteLine( "Listening as {0}", listener.LocalEndpoint );
listener.Start();
do
{
byte [] m_byBuff = new byte[127];
if( listener.Pending() )
{
client = listener.AcceptSocket();
// Get current date and time.
DateTime now = DateTime.Now;
string strDateLine = "Welcome " + now.ToString("G") + "nr";
// Convert to byte array and send.
Byte[] byteDateLine = System.Text.Encoding.ASCII.GetBytes( strDateLine.ToCharArray() );
client.Send( byteDateLine, byteDateLine.Length, 0 );
}
else
{
Thread.Sleep( 100 );
}
} while( true ); // Don't use this.
}
catch( Exception ex )
{
Console.WriteLine ( ex.Message );
}

看到那個do {} while( true )了嗎?

只要if( listener.Pending() )的條件不被滿足,這個過程中,主線程就處於被阻塞的狀態, 當然很不利於與用戶的交互(還以為死機了呢).

於是就希望有一種非阻塞的機制來實現網絡間的通訊.如果你熟悉java的話, 你可能用過java1.4中的nio (new io).其中的select機制就是用於解決此問題的.其實在.net中也有類似於它的一個機制, 而且通過事件觸發的異步操作, 使得它更方便被使用, 也更容易被理解.

首先來看看服務器是如何監聽客戶端的連接的.

const int nPortListen = 399;
// Create the listener socket in this machines IP address
Socket listener = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
listener.Bind( new IPEndPoint( aryLocalAddr[0], 399 ) );
//listener.Bind( new IPEndPoint( IPAddress.Loopback, 399 ) ); // For use with localhost 127.0.0.1
listener.Listen( 10 );
// Setup a callback to be notified of connection requests
listener.BeginAccept( new AsyncCallback( OnConnectRequest ), listener );

注意最後一行代碼, BeginAccept 為以後client真正接入的時候設置好了回調函數, 也就是說一旦server發現有client連接它, server端的 OnConnectRequest方法就將被調用.

那麼OnConnectRequest方法中又將做一些什麼事呢?

Socket client;
public void OnConnectRequest( IAsyncResult ar )
{
Socket listener = (Socket)ar.AsyncState;
client = listener.EndAccept( ar );
Console.WriteLine( "Client {0}, joined", client.RemoteEndPoint );
// Get current date and time.
DateTime now = DateTime.Now;
string strDateLine = "Welcome " + now.ToString("G") + "nr";
// Convert to byte array and send.
Byte[] byteDateLine = System.Text.Encoding.ASCII.GetBytes( strDateLine.ToCharArray() );
client.Send( byteDateLine, byteDateLine.Length, 0 );
listener.BeginAccept( new AsyncCallback( OnConnectRequest ), listener );
}

這裡利用連接獲得的socket, 向client發回了連接成功的信息.

隨後又跳回了BeginAccept的狀態, 繼續監聽, 也就是允許有多用戶連接.

再來看看連接的那方.

/**//// <summary>
    /// Connect to the server, setup a callback to connect
    /// </summary>
    /// <param name="serverAdd">server ip address</param>
    /// <param name="port">port</param>
    public void Connect(string serverAdd, int port)
    {
      try
      {
        // Create the socket object
        clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        // Define the Server address and port
        IPEndPoint epServer = new IPEndPoint(IPAddress.Parse(serverAdd), port);
        // Connect to server non-Blocking method
        clientSock.Blocking = false;

 // Setup a callback to be notified of connection success
        clientSock.BeginConnect(epServer, new AsyncCallback(OnConnect), clientSock);
      }
      catch (Exception ex)
      {
        Console.WriteLine("Server Connect failed!");
        Console.WriteLine(ex.Message);
      } 
   }

BeginConnect為連接成功設置了回調方法OnConnect, 一旦與服務器連接成功就會執行該方法.來看看OnConnect具體做了什麼

    /**//// <summary>
    /// Callback used when a server accept a connection.
    /// setup to receive message
    /// </summary>
    /// <param name="ar"></param>
    public void OnConnect(IAsyncResult ar)
    {
      // Socket was the passed in object
      Socket sock = (Socket)ar.AsyncState;
      // Check if we were sucessfull
      try
      {
        //sock.EndConnect( ar );
        if (sock.Connected)
        {
          AsyncCallback recieveData = new AsyncCallback(OnRecievedData);
          sock.BeginReceive(msgBuff, 0, msgBuff.Length, SocketFlags.None, recieveData, sock);
        }        
else
          Console.WriteLine("Unable to connect to remote machine", "Connect Failed!");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message, "Unusual error during Connect!");
      }
    }

它在檢測確實連接成功後, 又使用BeginReceive注冊了接受數據的回調函數.

/**//// <summary>
    /// Callback used when receive data., both for server or client
    /// Note: If not data was recieved the connection has probably died.
    /// </summary>
    /// <param name="ar"></param>
    public void OnRecievedData(IAsyncResult ar)
    {
      Socket sock = (Socket)ar.AsyncState;
      // Check if we got any data
      try
      {
        int nBytesRec = sock.EndReceive(ar);
        if (nBytesRec > 0)
        {
          // Wrote the data to the List
          string sRecieved = Encoding.ASCII.GetString(msgBuff, 0, nBytesRec);
          ParseMessage(sock ,sRecieved);
          // If the connection is still usable restablish the callback
          SetupRecieveCallback(sock);
        }
        else
        {
          // If no data was recieved then the connection is probably dead
          Console.WriteLine("disconnect from server {0}", sock.RemoteEndPoint);
          sock.Shutdown(SocketShutdown.Both);
          sock.Close();
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message, "Unusual error druing Recieve!");
      }
    }

它在檢測確實連接成功後又使用注冊了接受數據的回調函數

我們可以發現在整個過程中就是通過事件的不斷觸發, 然後在預先設置好的回調函數中做相應的處理工作,比如發送接受數據.下面這幅圖將讓你對這個事件觸發的過程有一個形象的認識.

配合附帶的源代碼, 相信可以讓你對此過程有更加深入的了解.

至於本文有關P2P的示例, 其實還很不完善.只是為每個Peer同時提供了充當服務器和客戶端的功能.當然在這個基礎上你可以很方便的做出你想要的效果.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved