zoukankan      html  css  js  c++  java
  • 异步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; constint 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中也有类似于它的一个机制, 而且通过事件触发的异步操作, 使得它更方便被使用, 也更容易被理解.

    首先来看看服务器是如何监听客户端的连接的.

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

    源代码下载

    参考资料

  • 相关阅读:
    hdu 6188 Duizi and Shunzi
    区间第k大
    AtCoder Regular Contest 081 E
    hdu 6170 Two strings
    hdu 6156 Palindrome Function
    2017百度之星初赛(B)-1006-小小粉丝度度熊 hdu 6119
    AtCoder Regular Contest 080 E
    hdu 6069 Counting Divisors
    hdu 6058 Kanade's sum (多校3)
    苹果曼和树
  • 原文地址:https://www.cnblogs.com/tearer/p/2523621.html
Copyright © 2011-2022 走看看