July 19, 2012

Asynchronous IO (Part 3)

ပြီးခဲ့သော ဘလောဂ် နှစ်ခန်းဖြင့် Asynchronous IO အကြောင်းကို ရေးသားခဲ့သည်။ ပဋ္ဌမ အခန်းတွင် Asynchronous IO မပေါ်ခင်က အရမ်းလေးလံသော အချက်အလက်များအား Input / Output လုပ်ရာတွင် အသုံးပြုပုံတို့ကို ရေးသားခဲ့ပါသည်။ ဦးစွာ Multi Thread အား အသုံးပြု၍ ရေးသားပုံ၊ ထိုမှတဆင့် Non Blocking IO အား အသုံးပြုရေးသားပုံ တို့အား ဖော်ပြခဲ့ပါသည်။ ဒုတိယ အခန်းတွင်မှု ယခု Java 7 တွင် အသစ် ပါဝင်လာခဲ့သော Nio2 ၏ Asynchronous IO အား အသုံးပြုပုံကို ဖော်ပြခဲ့၏။

Asynchronous IO အား အသုံးပြုရာတွင် အသုံးပြုနေသော လုပ်ဆောင်ချက်၏ အခြေအနေအား သိရှိနိုင်ရန် နည်းလမ်း နှစ်ခုရှိသည်ဟု ဖော်ပြခဲ့၏။ ပဋ္ဌမ နည်းလမ်းမှာ Future Interface အား အသုံးပြု၍ သော်၎င်း၊ လုပ်ဆောင်ချက်များ ပြီးဆုံးသည့်အခါတွင် Event အား လက်ခံရယူခြင်း အားဖြင့် သော်၎င်း၊ Asynchronous လုပ်ဆောင်ချက်၏ ပြီးဆုံးသည့်အချိန်ကို သိရှိစေသည်ဟု ဖော်ပြခဲ့ပါသည်။ ဒုတိယ တစ်ခေါက်တွင် Future Interface အား အသုံးပြု၍ ရေးသားပုံကို ဖော်ပြခဲ့ပြီး ယခုတစ်ခေါက်တွင် Event အား အသုံးပြုရေးသားပုံကို ဆက်လက်ဖော်ပြပါဦးမည်။


CompletionHandler


ယခုတစ်ခေါက်တွင်လည်း ယခင် အခန်းဆက်များကဲ့သို့ Request အပေါ်တွင် Echo ပြန်လုပ်ပေးနိုင်သော Socket Server အား နမှုနာအဖြစ် ရေးသားသွားပါမည်။ Asynchronous IO တွင် Input / Output လုပ်ဆောင်ချက်များအား ပြီးဆုံးကြောင်း အသိပေးနိုင်သည်မှာ java.nio.channels.CompletionHandler အင်တာဖေစ်ပင် ဖြစ်သည်။ Input / Output လုပ်ဆောင်ချက်များ ပြီးဆုံးပါက CompletionHandler#complete အား ခေါ်ယူပြီး၊ လုပ်ဆောင်ချက်တွင် အမှားတစ်ခုခု ဖြစ်ပွားခဲ့ပါက CompletionHandler#fail အား ခေါ်ဆိုမည် ဖြစ်ပါသည်။

CompletionHandler<V,A> တွင် Generics ပါရာမီတာ နှစ်မျိုးကို အသုံးပြုရန်လိုအပ်ပြီး၊ ပဋ္ဌမပါရာမီတာ V မှာ Input / Output လုပ်ဆောင်ချက်၏ ရလဒ်ပုံစံဖြစ်ပြီး၊ ဒုတိယ ပါရာမီတာမှာ Input / Output လုပ်ဆောင်ချက်တွင် Attach လုပ်မည့် Object ၏ ပုံစံဖြစ်ပါသည်။

ကျွှန်တော်တို့သည် ဤတစ်ခေါက်တွင် accept, read နှင့် write တို့အား အသီးသီး CompleteHandler အင်တာဖေစ်အား ပံ့ပိုးထားသည့် ကလပ်စ် အနေဖြင့် ရေးသားပြီး၊ Anonymous Inner Class ရေးသားပုံကို အသုံးပြုရေးသားသွားပါမည်။ လုပ်ဆောင်ချက်များအား အသီးသီး ကလပ်စ် အနေဖြင့် ရေးသားထားပါသဖြင့် ကုဒ်များကို အများကြီးရေသားရန် လိုအပ်မည် ဖြစ်သော်လည်း၊ Anonymous Inner Class ရေးသားပုံမှာ Javascript တွင်လည်း အသုံးများသော ရေးသားနည်း ဖြစ်ပါသဖြင့် နားလည်ရ လွယ်ကူ ပါလိမ့်မည်။

CompletionHandlerServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class CompletionHandlerServer {

 private static final int PORT = 5000;

 public CompletionHandlerServer() throws IOException {
  AsynchronousServerSocketChannel serverChannel = 
    AsynchronousServerSocketChannel.open();
  serverChannel.bind(new InetSocketAddress(PORT));
  serverChannel.accept(serverChannel, new Acceptor());
 }

 // Acceptor Class
 class Acceptor
   implements
   CompletionHandler<AsynchronousSocketChannel, 
   AsynchronousServerSocketChannel> {

  private final ByteBuffer buffer = ByteBuffer.allocate(1024);

  public Acceptor() {
   System.out.println("an acceptor has created.");
  }

  public void completed(final AsynchronousSocketChannel channel,
    AsynchronousServerSocketChannel serverChannel) {
   System.out.println(String.format("write: name: %s", Thread
     .currentThread().getName()));
   channel.read(buffer, channel, new Reader(buffer));
   serverChannel.accept(serverChannel, new Acceptor());
  }

  public void failed(Throwable exception,
    AsynchronousServerSocketChannel serverChannel) {
   throw new RuntimeException(exception);
  }
 }

 // Reader Class
 class Reader implements
   CompletionHandler<Integer, AsynchronousSocketChannel> {

  private ByteBuffer buffer;

  public Reader(ByteBuffer buffer) {
   this.buffer = buffer;
  }

  public void completed(Integer result, AsynchronousSocketChannel channel) {
   System.out.println(String.format("read: name: %s", Thread
     .currentThread().getName()));
   if (result != null && result < 0) {
    try {
     channel.close();
     return;
    } catch (IOException ignore) {
    }
   }
   buffer.flip();
   channel.write(buffer, channel, new Writer(buffer));
  }

  public void failed(Throwable exception,
    AsynchronousSocketChannel channel) {
   throw new RuntimeException(exception);
  }
 }

 class Writer implements
   CompletionHandler<Integer, AsynchronousSocketChannel> {

  private ByteBuffer buffer;

  public Writer(ByteBuffer buffer) {
   this.buffer = buffer;
  }

  public void completed(Integer result, AsynchronousSocketChannel channel) {
   System.out.println(String.format("write: name: %s", Thread
     .currentThread().getName()));
   buffer.clear();
   channel.read(buffer, channel, new Reader(buffer));
  }

  public void failed(Throwable exception,
    AsynchronousSocketChannel channel) {
   throw new RuntimeException(exception);
  }
 }
 
    public static void main(String[] args) {
        try {
            new CompletionHandlerServer();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
CompletionHandlerServer ကွန်စတရက်တာထဲတွင် AsynchronousServerSocketCnannel အား ဖွင့်ပြီး၊ Port နံပါတ် 5000 တွင် ဆာဗာ ပရိုဆက်အား Bind လုပ်ပါသည်။ ဤနေရာအထိကတော့ အခြားသော ရေးသားနည်းများတွင်လည်း ရေးသားခဲ့ပြီး ဖြစ်ပါသည်။

စာကြောင်း ၁၆တွင် ဆာဗာအော့ဘဂျက်၏ accept လုပ်ဆောင်ချက်ဖြင့်၊ connection များ ဖြစ်ပေါ်လာပါက Asynchronous အနေဖြင့် လက်ခံပြီး လုပ်ဆောင်ပေးမည့် လုပ်ဆောင်ချက်ကို ခေါ်ယူပါသည်။ ထိုသို့ခေါ်ယူရာတွင် Attachment အနေဖြင့် အသုံးပြုမည့် ဆာဗာ အော့ဘဂျက်နှင့်၊ လုပ်ဆောင်ချက်ပြီးဆုံးသည့် အခါတွင် လုပ်ဆောင်နိုင်မည့် CompletionHandler အင်တာဖေစ်အား ပံ့ပုံထားသော အော့ဘဂျက်အား ပါရာမီတာ အဖြစ်အသုံးပြုပါသည်။ ဤနေရာတွင် Accept လုပ်ဆောင်ချက်များအား လုပ်ဆောင်ရန် Acceptor ကလပ်စ်အား new လုပ်၍ Anonymous ကလပ်စ် ပုံစံ အဖြစ်အသုံးပြုထားပါသည်။

SocketClient ထံမှ Connection တစ်ခု ရောက်ရှိလာသောအခါ ဤလုပ်ဆောင်ချက်ကို ခေါ်ယူပြီး၊ ပြီးဆုံးသွားသော အခါ Acceptor#complete အား ခေါ်ယူမည် ဖြစ်ပါသည်။ စာကြောင်း ၃၅ တွင် AsynchronousSocketCnennel#read ဖြင့် Input လုပ်ငန်းများကို လုပ်ဆောင်စေပါသည်။ ဤနေရာတွင်လည်း CompletionHandler အား ပံ့ပိုးထားသော Reader ကလပ်စ်အား အသုံးပြု၍ Input လုပ်ငန်းများ ပြီးဆုံးသည့်အခါများတွင် အလုပ်လုပ်နိုင်သည့် call back အနေဖြင့် အသုံးပြုထားပါသည်။ Asynchronous IO ဖြင့် read လုပ်ဆောင်ချက်အား လုပ်ဆောင်စေပါသဖြင့် read လုပ်ဆောင်ချက်အား ပြီးဆုံးအောင် စောင့်စရာမလိုပဲ၊ စာကြောင်း ၃၆ ဖြင့် ဆာဗာ အင်းစတန့်စ်အား accept လုပ်စေပါသည်။

ဤနည်းအားဖြင့် input လုပ်ငန်းများ ပြီးဆုံးပါက Reader#complete အား ခေါ်ယူမည် ဖြစ်ပါသည်။ စာကြောင်း ၅၈ တွင် Input လုပ်ငန်း၏ ရလဒ်ဖြစ်သော result သည် null ဒါမှမဟုတ် 0 ထက်ငယ်ပါက (Input လုပ်ထားသည်မှာ မကျန်တော့ပါက) စာကြောင်း ၆၀ ဖြင့် AsynchronousSocketChannel အား ပိတ်ပစ်မည်ဖြစ်ပါသည်။ သို့မဟုတ်ပါက စာကြောင်း ၆၆ ဖြင့် AsynchronousSocketChannel#write အား လုပ်ဆောင်စေမည် ဖြစ်ပါသည်။ ဤနေရာတွင်လည်း CompletionHandler အား ပံ့ပိုးထားသော Writer အား အသုံးပြုပါသည်။

Output လုပ်ဆောင်ချက်များ ပြီးဆုံးပါက Writer#complete အား ခေါ်ဆိုမည် ဖြစ်သည်။ အတွင်းပိုင်းတွင် စာကြောင်း ၈၇ဖြင့် အသုံးပြုထားသော Buffer အား clear လုပ်ပြီး တဖန် socket Channel အား read လုပ်စေပြန်သည်။ ဤနည်းအားဖြင့် Client ဆီမှ Connect လုပ်လာသော အချက်အလက်များ မကုန်မချင်း read လုပ်လိုက် write လုပ်လိုက်ဖြင့် အလုပ်လုပ်နေစေခြင်း ဖြစ်ပါသည်။


နောက်ဆုံးတွင်

ကျွှန်တော်တို့သည် တောက်လျှောက် နမှုနာအဖြစ် ဆာဗာ အပလီကေးရှင်းကြီးပဲ ရေးသားလာခဲ့ပါသည်။ စမ်းသပ်ကြည့်ရန် နမှုနာအပလီ Scoket Client အား မရေးသားခဲ့ပါ။ အဲ့ဒီအတွက် နမှုနာဆာဗာ၏ Port Number အား 5000 ဟု အတူတူရေသားထားပါသဖြင့် Port 5000 အား connect လုပ်သည့် Client ပရိုဂရမ်တစ်ခုဖြင့် အသုံးပြုခဲ့သော Server အပလီကေးရှင်းများအား စမ်းသပ်နိုင်ပါမည်။ Client အပလီကေးရှင်းမှာ အောက်ပါအတိုင်းဖြစ်ပါသည်။

TestMain.java
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;

public class TestMain {

 public static void main(String[] args) {
  
  Socket socket = null;
  
  try {
   String server = "localhost";
   int servPort = 5000;
   byte[] data = "Hello, Net world".getBytes();
   byte[] msg = new byte[data.length];

   socket = new Socket(server, servPort);
   System.out.println("Connection To Server");

   InputStream in = socket.getInputStream();
   OutputStream out = socket.getOutputStream();

   out.write(data);
   System.out.println("Send:" + new String(data));

   // get Server Message
   int totalBytesRcvd = 0;
   int bytesRcvd;
   while (totalBytesRcvd < data.length) { 
    if ((bytesRcvd = in.read( 
      msg, totalBytesRcvd, data.length - totalBytesRcvd)) == -1) {
     throw new SocketException("Fail");
    }
    totalBytesRcvd += bytesRcvd;
   } // while end
   System.out.println("Recieve:" + new String(msg));

   socket.close();
   
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   if(null != socket) {
    try {
     socket.close();
    } catch (IOException e) {}
   }
  }
 }
}
ကျွှန်တော်တို့သည် ဤအခန်းဆက် ၃ခန်းဖြင့် Nio တွင် ထည့်သွင်းလာခဲ့သော Non-Blocking IO နှင့် Nio2 တွင် ထည့်သွင်းလာခဲ့သော Asynchronous IO အား အသုံးပြုပုံနှင့် တကွ လေ့လာခဲ့၏။ နှစ်မျိုးစလုံးသည် Input Output ကြောင့် Block ဖြစ်နေသော အချိန်အား အတတ်နိုင်ဆုံး လျှော့ချနိုင်ရန် စီမံ၍ ရေးသားထားသည်ကို တွေ့ရပါသည်။ သို့ရာတွင် ဘယ် IO က အကောင်းဆုံးလည်း ဆိုသည်မှာ တစ်ခုနှင့် တစ်ခု တည်ဆောက်ပုံ ရည်ရွယ်ချက်ချင်း မတူညီပါသဖြင့် နှိုင်းယှဉ်၍မရနိုင်ပါ။ ကိုယ် အသုံးပြုလိုသည့် အပလီကေးရှင်းအပေါ် မှုတည်၍ ရွေးချယ်သင့်ပါသည်။


ကိုးကား

http://masayuki038.github.com/blog/2012/04/30/nio2-asyncio/
http://itpro.nikkeibp.co.jp/article/COLUMN/20110927/369451/?ST=develop&P=8

ဆက်ပါဦးမည်။ လေးစားစွာဖြင့်။
မင်းလွင်

No comments:

Post a Comment