楊健 (ytjcopy@china.com)
中南工業(yè)大學(xué)
2001 年 11 月
摘要:代碼號為”Merlin”的J2SE1.4帶來(lái)了一些激動(dòng)人心的新特性,諸如對正則表達式的支持,異步輸入輸出流,通道(Channel),字符集等.雖然該版本還處在測試階段,但這些新特性早已讓開(kāi)發(fā)人員們躍躍欲試.在Merlin發(fā)布之前,異步輸入輸出流的應用還只是C,C++程序員的特殊武器;在Merlin中引入異步輸入輸出機制之后,Java程序員也可以利用它完成很多簡(jiǎn)潔卻是高質(zhì)量的代碼了.本文將介紹怎樣使用異步輸入輸出流來(lái)編寫(xiě)Socket進(jìn)程通信程序.
同步?異步輸入輸出機制的引入
在Merlin之前,編寫(xiě)Socket程序是比較繁瑣的工作.因為輸入輸出都必須同步.這樣,對于多客戶(hù)端客戶(hù)/服務(wù)器模式,不得不使用多線(xiàn)程.即為每個(gè)連接的客戶(hù)都分配一個(gè)線(xiàn)程來(lái)處理輸入輸出.由此而帶來(lái)的問(wèn)題是可想而知的.程序員不得不為了避免死鎖,線(xiàn)程安全等問(wèn)題,進(jìn)行大量的編碼和測試.很多人都在抱怨為什么不在Java中引入異步輸入輸出機制.比較官方的解釋是,任何一種應用程序接口的引入,都必須兼容任何操作平臺.因為Java是跨平臺的.而當時(shí)支持異步輸入輸出機制的操作平臺顯然不可能是全部.自Java 2 Platform以后,分離出J2SE,J2ME,J2EE三種不同類(lèi)型的應用程序接口,以適應不同的應用開(kāi)發(fā).Java標準的制訂者們意識到了這個(gè)問(wèn)題,并且支持異步輸入輸出機制的操作平臺在當今操作平臺中處于主流地位.于是,Jdk(J2SE) 的第五次發(fā)布中引入了異步輸入輸出機制.
feedom.net
以前的Socket進(jìn)程通信程序設計中,一般客戶(hù)端和服務(wù)器端程序設計如下:
服務(wù)器端:
//服務(wù)器端監聽(tīng)線(xiàn)程
while (true) {
.............
Socket clientSocket;
clientSocket = socket.accept(); //取得客戶(hù)請求Socket,如果沒(méi)有//客戶(hù)請求連接,線(xiàn)程在此處阻塞
//用取得的Socket構造輸入輸出流
PrintStream os = new PrintStream(new
BufferedOutputStream(clientSocket.getOutputStream(),
1024), false);
BufferedReader is = new BufferedReader(new
InputStreamReader(clientSocket.getInputStream()));
//創(chuàng )建客戶(hù)會(huì )話(huà)線(xiàn)程,進(jìn)行輸入輸出控制,為同步機制
new ClientSession();
.......
}
客戶(hù)端:
............
clientSocket = new Socket(HOSTNAME, LISTENPORT);//連接服務(wù)器套接字
//用取得的Socket構造輸入輸出流
PrintStream os = new PrintStream(new 54ne.com
BufferedOutputStream(clientSocket.getOutputStream(),
1024), false);
BufferedReader is = new BufferedReader(new
InputStreamReader(clientSocket.getInputStream()));
//進(jìn)行輸入輸出控制
.......
以上代碼段只是用同步機制編寫(xiě)Socket進(jìn)程通信的一個(gè)框架,實(shí)際上要考慮的問(wèn)題要復雜的多(有興趣的讀者可以參考我的一篇文章《Internet 實(shí)時(shí)通信系統設計與實(shí)現》)。將這樣一個(gè)框架列出來(lái),只是為了與用異步機制實(shí)現的Socket進(jìn)程通信進(jìn)行比較。下面將介紹使用異步機制的程序設計。
用異步輸入輸出流編寫(xiě)Socket進(jìn)程通信程序
在Merlin中加入了用于實(shí)現異步輸入輸出機制的應用程序接口包:java.nio(新的輸入輸出包,定義了很多基本類(lèi)型緩沖(Buffer)),java.nio.channels(通道及選擇器等,用于異步輸入輸出),java.nio.charset(字符的編碼解碼)。通道(Channel)首先在選擇器(Selector)中注冊自己感興趣的事件,當相應的事件發(fā)生時(shí),選擇器便通過(guò)選擇鍵(SelectionKey)通知已注冊的通道。然后通道將需要處理的信息,通過(guò)緩沖(Buffer)打包,編碼/解碼,完成輸入輸出控制。 中國網(wǎng)管論壇bbs.bitsCN.com
通道介紹:
這里主要介紹ServerSocketChannel和 SocketChannel.它們都是可選擇的(selectable)通道,分別可以工作在同步和異步兩種方式下(注意,這里的可選擇不是指可以選擇兩種工作方式,而是指可以有選擇的注冊自己感興趣的事件)??梢杂胏hannel.configureBlocking(Boolean )來(lái)設置其工作方式。與以前版本的API相比較,ServerSocketChannel就相當于ServerSocket(ServerSocketChannel封裝了ServerSocket),而SocketChannel就相當于Socket(SocketChannel封裝了Socket)。當通道工作在同步方式時(shí),編程方法與以前的基本相似,這里主要介紹異步工作方式。
所謂異步輸入輸出機制,是指在進(jìn)行輸入輸出處理時(shí),不必等到輸入輸出處理完畢才返回。所以異步的同義語(yǔ)是非阻塞(None Blocking)。在服務(wù)器端,ServerSocketChannel通過(guò)靜態(tài)函數open()返回一個(gè)實(shí)例serverChl。然后該通道調用serverChl.socket().bind()綁定到服務(wù)器某端口,并調用register(Selector sel, SelectionKey.OP_ACCEPT)注冊OP_ACCEPT事件到一個(gè)選擇器中(ServerSocketChannel只可以注冊OP_ACCEPT事件)。當有客戶(hù)請求連接時(shí),選擇器就會(huì )通知該通道有客戶(hù)連接請求,就可以進(jìn)行相應的輸入輸出控制了;在客戶(hù)端,clientChl實(shí)例注冊自己感興趣的事件后(可以是OP_CONNECT,OP_READ,OP_WRITE的組合),調用clientChl.connect(InetSocketAddress )連接服務(wù)器然后進(jìn)行相應處理。注意,這里的連接是異步的,即會(huì )立即返回而繼續執行后面的代碼。 中國網(wǎng)管論壇bbs.bitsCN.com
選擇器和選擇鍵介紹:
選擇器(Selector)的作用是:將通道感興趣的事件放入隊列中,而不是馬上提交給應用程序,等已注冊的通道自己來(lái)請求處理這些事件。換句話(huà)說(shuō),就是選擇器將會(huì )隨時(shí)報告已經(jīng)準備好了的通道,而且是按照先進(jìn)先出的順序。那么,選擇器是通過(guò)什么來(lái)報告的呢?選擇鍵(SelectionKey)。選擇鍵的作用就是表明哪個(gè)通道已經(jīng)做好了準備,準備干什么。你也許馬上會(huì )想到,那一定是已注冊的通道感興趣的事件。不錯,例如對于服務(wù)器端serverChl來(lái)說(shuō),可以調用key.isAcceptable()來(lái)通知serverChl有客戶(hù)端連接請求。相應的函數還有:SelectionKey.isReadable(),SelectionKey.isWritable()。一般的,在一個(gè)循環(huán)中輪詢(xún)感興趣的事件(具體可參照下面的代碼)。如果選擇器中尚無(wú)通道已注冊事件發(fā)生,調用Selector.select()將阻塞,直到有事件發(fā)生為止。另外,可以調用selectNow()或者select(long timeout)。前者立即返回,沒(méi)有事件時(shí)返回0值;后者等待timeout時(shí)間后返回。一個(gè)選擇器最多可以同時(shí)被63個(gè)通道一起注冊使用。
應用實(shí)例:
下面是用異步輸入輸出機制實(shí)現的客戶(hù)/服務(wù)器實(shí)例程序――程序清單1(限于篇幅,只給出了服務(wù)器端實(shí)現,讀者可以參照著(zhù)實(shí)現客戶(hù)端代碼): 網(wǎng)管網(wǎng)bitsCN_com
程序類(lèi)圖
public class NBlockingServer {
int port = 8000;
int BUFFERSIZE = 1024;
Selector selector = null;
ServerSocketChannel serverChannel = null;
HashMap clientChannelMap = null;//用來(lái)存放每一個(gè)客戶(hù)連接對應的套接字和通道
public NBlockingServer( int port ) {
this.clientChannelMap = new HashMap();
this.port = port;
}
public void initialize() throws IOException {
//初始化,分別實(shí)例化一個(gè)選擇器,一個(gè)服務(wù)器端可選擇通道
this.selector = Selector.open();
this.serverChannel = ServerSocketChannel.open();
this.serverChannel.configureBlocking(false);
InetAddress localhost = InetAddress.getLocalHost();
InetSocketAddress isa = new InetSocketAddress(localhost, this.port );
this.serverChannel.socket().bind(isa);//將該套接字綁定到服務(wù)器某一可用端口
}
//結束時(shí)釋放資源
public void finalize() throws IOException { feedom.net
this.serverChannel.close();
this.selector.close();
}
//將讀入字節緩沖的信息解碼
public String decode( ByteBuffer byteBuffer ) throws
CharacterCodingException {
Charset charset = Charset.forName( "ISO-8859-1" );
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode( byteBuffer );
String result = charBuffer.toString();
return result;
}
//監聽(tīng)端口,當通道準備好時(shí)進(jìn)行相應操作
public void portListening() throws IOException, InterruptedException {
//服務(wù)器端通道注冊OP_ACCEPT事件
SelectionKey acceptKey =this.serverChannel.register( this.selector,
SelectionKey.OP_ACCEPT );
//當有已注冊的事件發(fā)生時(shí),select()返回值將大于0
while (acceptKey.selector().select() > 0 ) {
System.out.println("event happened");
//取得所有已經(jīng)準備好的所有選擇鍵
Set readyKeys = this.selector.selectedKeys();
中國網(wǎng)管聯(lián)盟www.bitscn.com
//使用迭代器對選擇鍵進(jìn)行輪詢(xún)
I
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。