• 异步


    项目使用Socket与设备通信,可能同时会有成百上千个连接传送数据,当然不可能针对每个连接都要单独建立一个线程在后台接收数据,很明显的方式就是异步。

     

    1. 怎么理解异步


    这里,我举个不太恰当的例子,打电话和写信。打电话的时候,理想情况下,我们希望我问个问题,对方立马就能答复的,如果对方憋了半天,啥话也不说,我也只能一直等着,因为我接下来要说的话,是要根据他的回复来说的,这样在这期间什么事也干不了,对于程序来说,就叫同步;而写信不是这样,写信是即使我只问一个问题,我也可以在发完信以后,做其他事情,比如吃个饭啊,睡个觉什么的,不用一直守在信箱旁边,等着那边的回信,这个就可以称为异步

    所以引申到编程中来,可以知道异步实际上就是程序发送一个指令以后,并不是阻塞等待回复,而是继续运行,这样减少了资源浪费,也让程序运行更加流畅。

     

    2. Socket异步通信原理


    Socket异步通信实际上是利用了Windows系统的消息机制,当发送异步请求的时候,Socket向操作系统注册了一个回调函数,告诉操作系统,这个回调是用来接收Socket那边发来的数据;当有回复返回的时候,操作系统通过调用注册的回调函数,通知应用程序有数据返回,从而达到处理回复的目的。

     

    3. 了解.net异步编程的规则


    在实现异步通信前,我们来看看.net是如何实现异步的,以下摘自网络(自己写打字太多。。)

    .NET Framework允许异步调用任何方法,定义与需要调用的方法具有相同签名的委托,CLR将自动为该委托定义添加适当签名的BeginInvoke虚方法和EndInvoke虚方法和Invoke方法。 

    我们先来了解这2个方法和一个委托和一个接口:

    (1)
    BeginInvoke方法用于启动异步调用
    它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数,将 AsyncCallback 和 AsyncState(可通过 IAsyncResult 接口的AsyncState 属性获得)。作为最后两个参数,如没有可以为空。
    BeginInvoke立即返回,不等待异步调用完成。
    BeginInvoke返回IasyncResult,可用于监视调用进度。

    结果对象IAsyncResult是从开始操作返回的,并且可用于获取有关异步开始操作是否已完成的状态。
    结果对象被传递到结束操作,该操作返回调用的最终返回值。
    在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。

    (2)
    EndInvoke方法用于检索异步调用结果。
    在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke。
    如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。
    EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。
    要注意的是,始终在异步调用完成后调用EndInvoke

    (3)
    AsyncCallback委托用于指定在开始操作完成后应被调用的方法。
    AsyncCallback委托被作为开始操作上的第二个到最后一个参数传递。
    代码原型如下:
    [Serializable]
    public delegate void AsyncCallback(IAsyncResult ar);

    (4)

    IAsyncResult接口
    它表示异步操作的状态,该接口定义了4个公用属性。

     

    4. Socket异步通信的实现


    .net已经为我们建好了Socket的异步通信模型,并提供了相应的函数接口,我们只要直接拿来用就行了。接口主要一下三种:BeginAccept, EndAccept; BeginReceive, EndReceive; BeginSend, EndSend。使用这三种接口就能实现我们想要的异步通信方式。

    新建一个控制台程序,在服务端,代码如下: (注:为了描述方便,所有代码只写关键步骤,没有考虑异常和其他条件处理)

    建立服务端Socket,并设置好参数,绑定ip和端口后,使用BeginAccept异步监听连接,其中定义了新连接回调函数Accept,并设置参数“new socket connect”

    static Socket socket;
    static void Main(string[] args)
    {
         //新建Socket,并开始监听连接
         socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
         socket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
         socket.Listen(100);
         socket.BeginAccept(new AsyncCallback(Accept), "new socket connect");
    
         Console.ReadKey();
    }
    

     

    当有新的连接请求时,触发回调,建立完成后,启动异步接收

    static void Accept(IAsyncResult ar)
    {
        Console.WriteLine(ar.AsyncState.ToString());
    
        //结束监听
        Socket _socket = socket.EndAccept(ar);
    
        //投递接收请求
        Mark mark = new Mark(_socket);
        _socket.BeginReceive(mark.buffer, 0, mark.buffer.Length, SocketFlags.None, new AsyncCallback(Receive), mark)
    }
    

    其中,定义类Mark,用于保存新连接和要接收的数据。

    class Mark
    {
        public Socket socket;
        public byte[] buffer;
    
        public Mark(Socket socket)
        {
            this.socket = socket;
            buffer = new byte[1024];
        }
    }
    

     

    接收到数据,并打印,同时异步发送一条信息

    static void Receive(IAsyncResult ar)
    {
        Mark mark = (Mark)ar.AsyncState;
        //结束接收
        int length = mark.socket.EndReceive(ar);
        if (length > 0)
        {
            Console.WriteLine("Receive: " + Encoding.Default.GetString(mark.buffer, 0, length));
    
            byte[] senddata = Encoding.Default.GetBytes("hello, this is server.");
            Console.WriteLine("Send: hello, this is server.");
            //投递发送请求
            mark.socket.BeginSend(senddata, 0, senddata.Length, SocketFlags.None, new AsyncCallback(Send), mark);
        }
    }
    

     

    最后,结束发送

    static void Send(IAsyncResult ar)
    {
        Mark mark = (Mark)ar.AsyncState;
        //结束发送
        mark.socket.EndSend(ar);
    }
    

    这样,服务端的简单异步处理完成,客户端类似,不做赘述。

    class Program
    {
        static Socket socket;
        static void Main(string[] args)
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(IPAddress.Parse("127.0.0.1"), 1234);
    
            byte[] senddata = Encoding.Default.GetBytes("Hello, this is client.");
            Console.WriteLine("Send: Hello, this is client.");
            socket.BeginSend(senddata, 0, senddata.Length, SocketFlags.None, new AsyncCallback(Send), null);
    
            Console.ReadKey();
        }
    
        static void Send(IAsyncResult ar)
        {
            socket.EndSend(ar);
    
            byte[] buffer = new byte[1024];
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Receive), buffer);
        }
    
        static void Receive(IAsyncResult ar)
        {
            int length = socket.EndReceive(ar);
            if (length > 0)
            {
                byte[] buffer = (byte[])ar.AsyncState;
                Console.WriteLine("Receive: " + Encoding.Default.GetString(buffer, 0, length));
            }
        }
    }
    

     

    启动服务端,再启动客户端,结果如下:

    client:

     

    server: 

  • 相关阅读:
    [HDOJ4417]Super Mario(归并树)
    [POJ2104] K-th Number(归并树,二分)
    2017北理校赛G题 人民的名义(FFT)
    [CF762C] Two Strings(预处理,二分答案)
    [CF798D] Mike and distribution(贪心,鸽笼原理,随机)
    [CF798C] Mike and gcd problem(规律,gcd)
    2017北理校赛H题 青蛙过河(线段树, dp, 离散化)
    [CF798B] Mike and strings(暴力)
    [CF798A] Mike and palindrome(水题,trick)
    [CCPC2017]湘潭邀请赛
  • 原文地址:https://www.cnblogs.com/houkui/p/4213781.html
Copyright © 2020-2023  润新知