Mina、Netty、Twisted一塊兒學(九):異步IO和回調函數

用過JavaScript或者jQuery的同窗都知道,JavaScript特別是jQuery中存在大量的回調函數,例如Ajax、jQuery的動畫等。react

$.get(url, function() {
    doSomething1(); // (3)
}); // (1)
doSomething2();  // (2)

上面的代碼是jQuery的Ajax,因爲Ajax是異步的,因此在請求URL的過程當中並不會阻塞程序,也就是程序運行到(1)並不用等待Ajax請求的結果,就繼續往下執行(2)。而$.get的第二個參數是一個回調函數,當Ajax請求完成後,纔會調用這個回調函數執行(3)。git

這個例子只是用來理解一下異步的概念,沒玩過JS看不懂的同窗也不要緊。github

在傳統的IO(BIO/阻塞IO)中,全部IO操做都會阻塞當前線程,直到操做完成,全部步驟都是一步一步進行。例如:編程

OutputStream output = socket.getOutputstream();
out.write(data); // IO操做完成後返回
out.flush();
System.out.println("消息發送完成");

可是在異步網絡編程中,因爲IO操做是異步的,也就是一個IO操做不會阻塞去等待操做結果,程序就會繼續向下執行。服務器

在MINA、Netty、Twisted中,不少網絡IO操做都是異步的,好比向網絡的另外一端write寫數據、客戶端鏈接服務器的connect操做等。網絡

例如Netty的write方法(以及writeAndFlush方法),執行完write語句後並不表示數據已經發送出去,而僅僅是開始發送這個數據,程序並不阻塞等待發送完成,而是繼續往下執行。因此若是有某些操做想在write完成後再執行,例如write完成後關閉鏈接,下面這些寫法就有問題了,它可能會在數據write出去以前先關閉鏈接:session

Channel ch = ...;
ch.writeAndFlush(message);
ch.close();

那麼問題就來了,挖掘機...既然上面的寫法不正確,那麼如何在IO操做完成後再作一些其餘操做?異步

當異步IO操做完成後,不管成功或者失敗,都會再通知程序。至於如何處理髮送完成的通知,在MINA、Netty、Twisted中,都會有相似回調函數的實現方式。socket

Netty:ide

在Netty中,write及writeAndFlush方法有個返回值,類型是ChannelFuture。ChannelFuture的addListener方法能夠添加ChannelFutureListener監聽器。ChannelFutureListener接口的抽象方法operationComplete會在write完成(不管成功或失敗)時被調用,咱們只須要實現這個方法便可處理一些write完成後的操做,例如write完成後關閉鏈接。

@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg)
        throws UnsupportedEncodingException {
    
    // 讀操做省略...
    
    // 發送數據到客戶端
    ChannelFuture future = ctx.writeAndFlush("message"); // 返回值類型爲ChannelFuture
    future.addListener(new ChannelFutureListener() {
        
        // write操做完成後調用的回調函數
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            if(future.isSuccess()) { // 是否成功
                System.out.println("write操做成功");
            } else {
                System.out.println("write操做失敗");
            }
            ctx.close(); // 若是須要在write後關閉鏈接,close應該寫在operationComplete中。注意close方法的返回值也是ChannelFuture
            }
    });
        
}

上面代碼中,close操做是在operationComplete中進行,這樣能夠保證不會在write完成以前close鏈接。注意close關閉鏈接一樣是異步的,其返回值也是ChannelFuture。

MINA:

MINA和Netty相似,session.write返回值是WriteFuture類型。WriteFuture也能夠經過addListener方法添加IoFutureListener監聽器。IoFutureListener接口的抽象方法operationComplete會在write完成(不管成功或失敗)時被調用。若是須要在write完成後進行一些操做,只需實現operationComplete方法。

@Override
public void messageReceived(IoSession session, Object message)
        throws Exception {

    // 讀操做省略...

    // 發送數據到客戶端
    WriteFuture future = session.write("message");
    future.addListener(new IoFutureListener<WriteFuture>() {

        // write操做完成後調用的回調函數
        @Override
        public void operationComplete(WriteFuture future) {
            if(future.isWritten()) {
                System.out.println("write操做成功");
            } else {
                System.out.println("write操做失敗");
            }
        }
    });
}

MINA和Netty不一樣在於關閉鏈接的close方法。其中無參數的close()方法已經棄用,而是使用帶一個boolean類型參數的close(boolean immediately)方法。immediately爲true則直接關閉鏈接,爲false則是等待全部的異步write完成後再關閉鏈接。

因此下面這種寫法是正確的:

session.write("message");
session.close(false); // 雖然write是異步的,可是immediately參數爲false會等待write完成後再關閉鏈接

Twisted:

在Twisted中,twisted.internet.defer.Deferred相似於MINA、Netty中的Future,能夠用於添加在異步IO完成後的回調函數。可是Twisted並無在write方法中返回Deffered,雖然write方法也是異步的。下面就用Twisted實現一個簡單的TCP客戶端來學習Deffered的用法。

# -*- coding:utf-8 –*- 

from twisted.internet import reactor
from twisted.internet.protocol import Protocol
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol

class ClientProtocol(Protocol):
    def sendMessage(self):
        self.transport.write("Message") # write也是異步的
        self.transport.loseConnection() # loseConnection會等待write完成後再關閉鏈接

# 鏈接服務器成功的回調函數
def connectSuccess(p):
    print "connectSuccess"
    p.sendMessage()

# 鏈接服務器失敗的回調函數
def connectFail(failure):
    print "connectFail"

point = TCP4ClientEndpoint(reactor, "localhost", 8080)
d = connectProtocol(point, ClientProtocol()) # 異步鏈接到服務器,返回Deffered
d.addCallback(connectSuccess) # 設置鏈接成功後的回調函數
d.addErrback(connectFail) # 設置鏈接失敗後的回調函數
reactor.run()

在Twisted中,關閉鏈接的loseConnection方法會等待異步的write完成後再關閉,相似於MINA中的session.close(false)。Twisted中想要直接關閉鏈接可使用abortConnection,相似MINA中的session.close(true)。

MINA、Netty、Twisted一塊兒學系列

MINA、Netty、Twisted一塊兒學(一):實現簡單的TCP服務器

MINA、Netty、Twisted一塊兒學(二):TCP消息邊界問題及按行分割消息

MINA、Netty、Twisted一塊兒學(三):TCP消息固定大小的前綴(Header)

MINA、Netty、Twisted一塊兒學(四):定製本身的協議

MINA、Netty、Twisted一塊兒學(五):整合protobuf

MINA、Netty、Twisted一塊兒學(六):session

MINA、Netty、Twisted一塊兒學(七):發佈/訂閱(Publish/Subscribe)

MINA、Netty、Twisted一塊兒學(八):HTTP服務器

MINA、Netty、Twisted一塊兒學(九):異步IO和回調函數

MINA、Netty、Twisted一塊兒學(十):線程模型

MINA、Netty、Twisted一塊兒學(十一):SSL/TLS

MINA、Netty、Twisted一塊兒學(十二):HTTPS

源碼

https://github.com/wucao/mina-netty-twisted

相關文章
相關標籤/搜索