异步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 = 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 ); 
}
 
}
 whiletrue ); // 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同时提供了充当服务器和客户端的功能. 当然在这个基础上你可以很方便的做出你想要的效果. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值