熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

運用異步輸入輸出流編寫Socket進程通信

2013-11-23 19:01:26  來源: Java核心技術 

  楊健 ()
  中南工業大學
  
  
  摘要代碼號為Merlin的JSE帶來了一些激動人心的新特性諸如對正則表達式的支持異步輸入輸出流通道(Channel)字符集等雖然該版本還處在測試階段但這些新特性早已讓開發人員們躍躍欲試在Merlin發布之前異步輸入輸出流的應用還只是CC++程序員的特殊武器;在Merlin中引入異步輸入輸出機制之後Java程序員也可以利用它完成很多簡潔卻是高質量的代碼了本文將介紹怎樣使用異步輸入輸出流來編寫Socket進程通信程序
  同步?異步輸入輸出機制的引入
  在Merlin之前編寫Socket程序是比較繁瑣的工作因為輸入輸出都必須同步這樣對於多客戶端客戶/服務器模式不得不使用多線程即為每個連接的客戶都分配一個線程來處理輸入輸出由此而帶來的問題是可想而知的程序員不得不為了避免死鎖線程安全等問題進行大量的編碼和測試很多人都在抱怨為什麼不在Java中引入異步輸入輸出機制比較官方的解釋是任何一種應用程序接口的引入都必須兼容任何操作平台因為Java是跨平台的而當時支持異步輸入輸出機制的操作平台顯然不可能是全部自Java Platform以後分離出JSEJMEJEE三種不同類型的應用程序接口以適應不同的應用開發Java標准的制訂者們意識到了這個問題並且支持異步輸入輸出機制的操作平台在當今操作平台中處於主流地位於是Jdk(JSE) 的第五次發布中引入了異步輸入輸出機制
  
  以前的Socket進程通信程序設計中一般客戶端和服務器端程序設計如下
  
  服務器端
  //服務器端監聽線程
  while (true) {
       
        Socket clientSocket;
        clientSocket = socketaccept(); //取得客戶請求Socket如果沒有//客戶請求連接線程在此處阻塞
        //用取得的Socket構造輸入輸出流
        PrintStream os = new PrintStream(new
        BufferedOutputStream(clientSocketgetOutputStream()
        ) false);
        BufferedReader is = new BufferedReader(new
        InputStreamReader(clientSocketgetInputStream()));
        //創建客戶會話線程進行輸入輸出控制為同步機制
        new ClientSession();
       
        }
  
  
  
  客戶端
   
   clientSocket = new Socket(HOSTNAME LISTENPORT);//連接服務器套接字
   //用取得的Socket構造輸入輸出流
   PrintStream os = new PrintStream(new
        BufferedOutputStream(clientSocketgetOutputStream()
        ) false);
        BufferedReader is = new BufferedReader(new
        InputStreamReader(clientSocketgetInputStream()));
   //進行輸入輸出控制
  
  
  
  
  以上代碼段只是用同步機制編寫Socket進程通信的一個框架實際上要考慮的問題要復雜的多(有興趣的讀者可以參考我的一篇文章《Internet 實時通信系統設計與實現》)將這樣一個框架列出來只是為了與用異步機制實現的Socket進程通信進行比較下面將介紹使用異步機制的程序設計
  
  用異步輸入輸出流編寫Socket進程通信程序
  在Merlin中加入了用於實現異步輸入輸出機制的應用程序接口包javanio(新的輸入輸出包定義了很多基本類型緩沖(Buffer))javaniochannels(通道及選擇器等用於異步輸入輸出)javaniocharset(字符的編碼解碼)通道(Channel)首先在選擇器(Selector)中注冊自己感興趣的事件當相應的事件發生時選擇器便通過選擇鍵(SelectionKey)通知已注冊的通道然後通道將需要處理的信息通過緩沖(Buffer)打包編碼/解碼完成輸入輸出控制
  
  通道介紹
  這裡主要介紹ServerSocketChannel和 SocketChannel它們都是可選擇的(selectable)通道分別可以工作在同步和異步兩種方式下(注意這裡的可選擇不是指可以選擇兩種工作方式而是指可以有選擇的注冊自己感興趣的事件)可以用nfigureBlocking(Boolean )來設置其工作方式與以前版本的API相比較ServerSocketChannel就相當於ServerSocket(ServerSocketChannel封裝了ServerSocket)而SocketChannel就相當於Socket(SocketChannel封裝了Socket)當通道工作在同步方式時編程方法與以前的基本相似這裡主要介紹異步工作方式
  
  所謂異步輸入輸出機制是指在進行輸入輸出處理時不必等到輸入輸出處理完畢才返回所以異步的同義語是非阻塞(None Blocking)在服務器端ServerSocketChannel通過靜態函數open()返回一個實例serverChl然後該通道調用serverChlsocket()bind()綁定到服務器某端口並調用register(Selector sel SelectionKeyOP_ACCEPT)注冊OP_ACCEPT事件到一個選擇器中(ServerSocketChannel只可以注冊OP_ACCEPT事件)當有客戶請求連接時選擇器就會通知該通道有客戶連接請求就可以進行相應的輸入輸出控制了在客戶端clientChl實例注冊自己感興趣的事件後(可以是OP_CONNECTOP_READOP_WRITE的組合)調用nnect(InetSocketAddress )連接服務器然後進行相應處理注意這裡的連接是異步的即會立即返回而繼續執行後面的代碼
  
  選擇器和選擇鍵介紹
  選擇器(Selector)的作用是將通道感興趣的事件放入隊列中而不是馬上提交給應用程序等已注冊的通道自己來請求處理這些事件換句話說就是選擇器將會隨時報告已經准備好了的通道而且是按照先進先出的順序那麼選擇器是通過什麼來報告的呢?選擇鍵(SelectionKey)選擇鍵的作用就是表明哪個通道已經做好了准備准備干什麼你也許馬上會想到那一定是已注冊的通道感興趣的事件不錯例如對於服務器端serverChl來說可以調用keyisAcceptable()來通知serverChl有客戶端連接請求相應的函數還有SelectionKeyisReadable()SelectionKeyisWritable()一般的在一個循環中輪詢感興趣的事件(具體可參照下面的代碼)如果選擇器中尚無通道已注冊事件發生調用Selectorselect()將阻塞直到有事件發生為止另外可以調用selectNow()或者select(long timeout)前者立即返回沒有事件時返回後者等待timeout時間後返回一個選擇器最多可以同時被個通道一起注冊使用
  
  應用實例
  下面是用異步輸入輸出機制實現的客戶/服務器實例程序――程序清單(限於篇幅只給出了服務器端實現讀者可以參照著實現客戶端代碼)
  
  程序類圖
  public class NBlockingServer {
  int port = ;
  int BUFFERSIZE = ;
  Selector selector = null;
  ServerSocketChannel serverChannel = null;
  HashMap clientChannelMap = null;//用來存放每一個客戶連接對應的套接字和通道
  
  public NBlockingServer( int port ) {
    thisclientChannelMap = new HashMap();
  thisport = port;
  }
  
  public void initialize() throws IOException {
   //初始化分別實例化一個選擇器一個服務器端可選擇通道
   thisselector = Selectoropen();
   thisserverChannel = ServerSocketChannelopen();
   thnfigureBlocking(false);
   InetAddress localhost = InetAddressgetLocalHost();
   InetSocketAddress isa = new InetSocketAddress(localhost thisport );
   thisserverChannelsocket()bind(isa);//將該套接字綁定到服務器某一可用端口
  }
  //結束時釋放資源
  public void finalize() throws IOException {
    thisserverChannelclose();
    thisselectorclose();
  }
  //將讀入字節緩沖的信息解碼
  public String decode( ByteBuffer byteBuffer ) throws
  CharacterCodingException {
    Charset charset = CharsetforName( ISO );
    CharsetDecoder decoder = charsetnewDecoder();
    CharBuffer charBuffer = decoderdecode( byteBuffer );
    String result = charBuffertoString();
    return result;
  }
  //監聽端口當通道准備好時進行相應操作
  public void portListening() throws IOException InterruptedException {
   //服務器端通道注冊OP_ACCEPT事件
   SelectionKey acceptKey =thisserverChannelregister( thisselector
                     SelectionKeyOP_ACCEPT );
    //當有已注冊的事件發生時select()返回值將大於
    while (acceptKeyselector()select() > ) {
      Systemoutprintln(event happened);
      //取得所有已經准備好的所有選擇鍵
      Set readyKeys = thisselectorselectedKeys();
      //使用迭代器對選擇鍵進行輪詢
      I
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26159.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.