quinn.hong

do it, if you can or want
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

异步

Posted on 2015-01-09 17:08  quinn.hong  阅读(574)  评论(0编辑  收藏  举报

项目使用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: