本文转载自:http://blog.csdn.net/historyasamirror/article/details/6159248
所谓同步,简单的说,A告诉B去做某件事情,然后就一直等待,直到B做完后返回给A,A才继续做其它的事情;
所谓异步,相反的,A告诉B做某件事情,然后就去干其它的事情了,B做完后再通知A。
无论是同步还是异步,其实都是指两个对象之间的交互。所以,判断什么是同步还是异步,首先要先明确指的是哪两个对象之间的关系。
举个例子:
- socket.write(request);
- response = socket.read();
这是一段常见的伪代码,用于Client向Server发送请求。在这里,可以说socket.write是一个同步IO,因为调用write操作以后,内核会将要写的数据放入到网卡的缓冲区中,然后再返回,所以,这里同步的两个对象分别是应用程序和内核;也可以说这段代码是一个同步消息机制,因为client将request发送给server后,一直等待直到server将response返回,这里同步的两个对象分别是client和server。
之前的那篇blog专门论述同步IO和异步IO,它指的是应用程序和操作系统内核间的关系。这里就不多谈了。这里谈谈Mina。Mina是一个非常流行的网络程序的框架,它提供的是异步的API(It provides an abstract • event-driven • asynchronous API)。
比如,如果client想要创建一个到server的连接,用mina可以这样写:
- NioSocketConnector connector = new NioSocketConnector();
- // 初始化connector...
- //..........
- connector.connect(address);
看上去好像和一般的写法没什么两样。但是,这里的connector.connect()方法是一个异步的调用,意思是程序告诉mina要去连接address,mina返回说它会做这件事,但它可能还没有做完。所以,即便“connector.connect(address);”这行代码结束了,也并不意味着连接成功了(mina这时候可能还正在创建连接的过程中)。完整的写法应该是:
- ConnectFuture connFuture = connector.connect(address);
- connFuture.addListener(new ConnectListener());
- private class ConnectListener implements IoFutureListener<ConnectFuture>{
- public void operationComplete(ConnectFuture future) {
- if (future.isConnected()) {
- //get session
- IoSession session = future.getSession();
- session.write(...);
- } else {
- logger.error("can not create the connection .");
- }
- }
- }
- }
- }
上面代码是原文我稍微做了下修改
//future.awaitUninterruptibly(); future.addListener(new IoFutureListener<ConnectFuture>() { @Override public void operationComplete(ConnectFuture future) { if(future.isConnected()) { System.out.println("connected"); }else { System.out.println("disconnected"); } } }); System.out.println("end");
这里地址对网络通的情况下,future.awaitUninterruptibly()一行,注释掉则打印 end connected ,不注释掉则打印 connected end 可以稍微注意下
这里面最重要的一个类是ConnectListener,它实现了IoFutureListener<ConnectFuture>这个接口。这个类其实只有一个函数 – operationComplete,这是一个回调函数,它告诉mina一旦connect完成以后,就调用这个函数。我们这里的回调函数,首先判断一下连接是否成功,如果成功,那么就向这个链接中写入数据(session.write)。
回调函数在异步机制中扮演着非常重要的角色。 因为在同步机制中,调用者会等到结果返回然后自己执行接下来的操作,比如,上面这段代码如果写成同步的,大概是这个样子:
- boolean status = connector.connect(address);
- if(status) {
- session.write(...);
- } else {
- logger.error("can not create the connection .");
- }
但是在异步机制中,就只能将connect后面的代码做成回调函数,注册到mina中。这样,当mina完成工作后它才知道接下去该干什么。
值得一提的是,虽然Mina号称是Asynchronous API,但它也提供了同步的方法。比如,上面这段代码,如果用Mina的同步机制是这样写的:
- ConnectFuture future = connector.connect(address);
- future.awaitUninterruptibly();
- IoSession session = future.getSession();
- // Send the first ping message
- session.write(....);
重点在于“future.awaitUninterruptibly();”这行代码,它会将程序阻塞住,直到连接创建好,所以,当这行代码结束后,就可以直接获取session并执行write操作。
我在网上找到的大部分Mina示例代码都是基于同步的调用。所以,虽然很多人用mina,可是还是更习惯于同步的机制。至于为什么会这样,以后会再讨论。
上篇说了半天,却回避了一个重要的问题:为什么要用异步呢,它有什么样的好处?坦率的说,我对这点的认识不是太深刻(套句俗语,只可意会,不可言传)。还是举个例子吧:
比如Client向Server发送一个request,Server收到后需要100ms的处理时间,为了方便起见,我们忽略掉网络的延迟,并且,我们认为Server端的处理能力是无穷大的。在这个use case下,如果采用同步机制,即Client发送request -> 等待结果 -> 继续发送,那么,一个线程一秒钟之内只能够发送10个request,如果希望达到10000 request/s的发送压力,那么Client端就需要创建1000个线程,而这么多线程的context switch就成为client的负担了。而采用异步机制,就不存在这个问题了。Client将request发送出去后,立即发送下一个request,理论上,它能够达到网卡发送数据的极限。当然,同时需要有机制不断的接收来自Server端的response。
以上的例子其实就是这篇的主题,异步的消息机制,基本的流程是这样的:
如果仔细琢磨的话,会发现这个流程中有两个很重要的问题需要解决:
1. 当client接收到response后,怎样确认它到底是之前哪个request的response呢?
2. 如果发送一个request后,这个request对应的response由于种种原因(比如server端出问题了)一直没有返回。client怎么能够发现类似这样长时间没有收到response的request呢?
对于第一个问题,一般会尝试给每个request分配一个独一无二的ID,返回的Response会同时携带这个ID,这样就能够将request和response对应上了。
对于第二个问题,需要有一个timeout机制,对于每一个request都有一个定时器,如果到指定时间仍然没有返回结果,那么会触发timeout操作。多说一句,timeout机制其实对于涉及网络的同步机制也是非常有必要的,因为有可能client与server之间的链接坏了,在极端情况下,client会被一直阻塞住。