在Merlin中增加了實現異步輸入輸出機制的應用程序接口包:java.nio(壹個新的輸入輸出包,定義了很多基本類型的緩沖區),java.nio.channels(通道和選擇器等。,用於異步輸入和輸出),以及java.nio.charset(字符的編碼和解碼)。壹個通道首先在選擇器中註冊它感興趣的事件,當相應的事件發生時,選擇器通過SelectionKey通知註冊的通道。然後通道通過緩沖區將待處理的信息打包,進行編碼/解碼,完成輸入輸出控制。
渠道介紹:
本文主要介紹ServerSocketChannel和SocketChannel。兩者都是可選通道,可以分別工作在同步和異步兩種模式(註意,這裏的可選不是指可以選擇兩種工作模式,而是可以選擇性地註冊自己感興趣的事件)。妳可以使用頻道。ConfigureBlocking (Boolean)設置其工作模式。與以前版本的API相比,ServerSocketChannel相當於ServerSocket,SocketChannel相當於Socket。當通道工作在同步模式時,編程方法基本和前面類似,這裏主要介紹異步工作模式。
所謂異步I/O機制,就是在處理I/O的時候,我不用等到I/O完成了再返回。因此,異步同義詞沒有阻塞。在服務器端,ServerSocketChannel通過靜態函數open()返回壹個實例serverChl。然後通道調用serverChl.socket()。bind()綁定到服務器端口,並調用register (selector sel,selectionkey。OP_ACCEPT)在選擇器中註冊OP_ACCEPT事件(ServerSocketChannel只能註冊op _ accept事件)。當有客戶請求連接時,選擇器會通知通道有客戶連接請求,從而進行相應的輸入輸出控制;在客戶端,clientChl實例註冊了自己感興趣的事件(可以是op _ connect、op _ read和op _ write的組合)後,調用客戶端Chl。Connect (inetsocketaddress)連接到服務器,然後對其進行相應的處理。註意,這裏的連接是異步的,也就是說,它會立即返回並繼續執行下面的代碼。
選擇器和選擇鍵介紹:
選擇器的作用是將通道感興趣的事件放入隊列,而不是立即提交給應用程序,等待註冊的通道請求處理這些事件。換句話說,選擇器將隨時報告準備好的通道,並且按照先進先出的順序。那麽,選擇器通過什麽來報告呢?選擇鍵。選擇鍵的功能是指示哪個通道準備好了以及做什麽。妳可能會馬上想到,這壹定是註冊頻道感興趣的事件。可以,比如對於服務器端的serverChl,可以調用key.isAcceptable()通知serverChl有客戶端連接請求。對應的功能有:選擇鍵。isreadable(),selectionkey。isreatable()。通常,感興趣的事件在壹個循環中被輪詢(有關詳細信息,請參考下面的代碼)。如果選擇器中沒有發生通道註冊事件,調用Selector.select()將被阻塞,直到事件發生。此外,還可以調用selectNow()或select(長超時)。前者立即返回,無事件時返回值0;後者等待超時並返回。壹個選擇器可以被多達63個通道同時使用。
應用示例:
下面是壹個異步輸入/輸出機制實現的客戶機/服務器示例程序——程序列表1(限於篇幅,僅給出服務器端實現,讀者可參考客戶端代碼):
程序類圖
公共類NBlockingServer {
int port = 8000
int buffer size = 1024;
選擇器selector = null
serversocket channel server channel = null;
HashMap clientChannelMap = null//用於存儲每個客戶端連接對應的套接字和通道。
公共NBlockingServer( int端口){
this . clientchannelmap = new HashMap();
this.port = port
}
公共void initialize()引發IOException {
//初始化:分別實例化壹個選擇器,壹個服務器可以選擇頻道。
this.selector =選擇器. open();
this . server channel = serversocketchannel . open();
this . server channel . configure blocking(false);
inet address localhost = inet address . get localhost();
InetSocketAddress isa = new InetSocketAddress(localhost,this . port);
this.serverChannel.socket()。bind(isa);//將套接字綁定到服務器的可用端口。
}
//最後釋放資源
公共void finalize()引發IOException {
this . server channel . close();
this.selector.close()。
}
//解碼讀入字節緩沖區的信息
公共字符串解碼(ByteBuffer byteBuffer)引發
字符編碼異常{
charset charset = charset . forname(" ISO-8859-1 ");
charset decoder decoder = charset . new decoder();
char buffer char buffer = decoder . decode(byte buffer);
字符串result = char buffer . tostring();
返回結果;
}
//監聽端口,當通道準備好時執行相應的操作。
public void portListening()拋出IOException,InterruptedException {
//服務器端通道註冊OP_ACCEPT事件。
selection key accept key = this . server channel . register(this . selector,
選擇鍵。OP _ ACCEPT);
//當註冊的事件發生時,select()的返回值將大於0。
while (acceptKey.selector()。select()& gt;0 ) {
System.out.println("事件發生");
//獲取所有準備好的選擇鍵。
set readyKeys = this . selector . selected keys();
//使用叠代器輪詢選擇鍵。
叠代器I = readykeys . iterator();
而(我
Else if (key.isReadable()) {//如果是通道讀就緒事件,
System.out.println("可讀");
//獲取選擇鍵對應的通道和套接字。
SelectableChannel nextReady =
(SelectableChannel)key . channel();
Socket Socket =(Socket)key . attachment();
//處理此事件,處理方法已經封裝在ClientChInstance類中。
this . readfromchannel(socket . get channel(),
(客戶端實例)
this . clientchannelmap . get(socket));
}
else If(key . is write able()){//如果是通道寫就緒事件,
system . out . println(" writeable ");
//套接字獲取後的後處理,如上。
Socket Socket =(Socket)key . attachment();
socket channel channel =(socket channel)
socket . get channel();
this.writeToChannel( channel,“這是來自服務器的!”);
}
}
}
}
//對通道的寫操作
public void writeToChannel(socket channel通道,字符串消息)
引發IOException {
byte buffer buf = byte buffer . wrap(message . getbytes());
int nbytes = channel . write(buf);
}
//通道上的讀取操作
public void readFromChannel(socket channel channel,clientchininstance client instance)
引發IOException,InterruptedException {
byte buffer byte buffer = byte buffer . allocate(buffer size);
int nbytes = channel . read(byte buffer);
byte buffer . flip();
字符串結果= this . decode(byte buffer);
//當客戶端發出“@exit”退出命令時,關閉其通道。
if(result . index of(" @ exit ")& gt;= 0 ) {
channel . close();
}
否則{
client instance . append(result . tostring());
//讀取壹行後,執行相應的操作。
if(result . index of(" \ n ")& gt;= 0 ){
System.out.println("客戶端輸入"+結果);
client instance . execute();
}
}
}
//這個類封裝了如何操作客戶端的通道,可以通過重載execute()方法來實現。
公共類ClientChInstance {
SocketChannel通道;
string buffer buffer = new string buffer();
公共客戶端實例(SocketChannel通道){
this.channel =頻道;
}
public void execute()拋出IOException {
String message = "這是從通道讀取後的響應!";
writeToChannel( this.channel,message);
buffer = new string buffer();
}
//當壹行沒有結束時,將當前字放在緩沖區的末尾。
公共void追加(字符串值){
buffer.append(值);
}
}
//主程序
公共靜態void main( String[] args ) {
NBlockingServer nbServer = new NBlockingServer(8000);
嘗試{
nbserver . initialize();
} catch(異常e ) {
e . printstacktrace();
system . exit(-1);
}
嘗試{
nbserver . port listening();
}
捕捉(異常e ) {
e . printstacktrace();
}
}
}
節目列表1
總結:
從上面的程序段可以看出,服務器在沒有引入冗余線程的情況下,完成了多客戶端的客戶端/服務器模式。在這個程序中使用了回調模式。需要註意的是,請不要把原來的I/O包和新添加的I/O包混在壹起,因為兩個包由於某些原因是不兼容的。即在使用通道時,請使用緩沖區來完成輸入輸出控制。該程序在Windows 2000和J2SE1.4下通過telnet測試成功。