異步Socket


在網絡通訊的編程中我們經常使用到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 =newbyte[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同時提供了充當服務器和客戶端的功能. 當然在這個基礎上你可以很方便的做出你想要的效果.

源代碼下載

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM