寫這片文章是因爲自己昨天剛解決了一個十個月前碰到的問題!當時苦於網上高手無人回答,也苦於自己當時沒有時間去鑽研爲什麼
!
問題是:通過網上實例以及java網絡編程這本書寫java nio簡單的測試服務器時發現,都是註冊讀寫事件後然後分別處理相應的事件
就行了,這樣本沒錯,可對於後續對註冊事件的操作,再也沒有看到與之相關實例與說明。然而,就這樣完事以後,服務器端要麼是
無限讀事件與寫事件有效,要麼是客戶端接收的數據是重複的好幾條,在高併發時甚至上百上千。
一般網絡通訊程序是,客戶端請求,然後服務端回覆響應,即key.isReadable()時,服務器端接收請求,key.isWriteable()時,服
務端返回響應!一般我們在處理完接收後,我們就註冊寫事件,然後有寫事件時處理寫事件,正常網上的實例都是這樣寫的,可是這
樣的話,客戶端第二次以後發送時,服務器端再也沒有進入到讀事件中,還寫事件回覆的數據也很怪異,返回很多重複的數據。
再改進一點的是,寫事件完事以後,又註冊一個讀事件,這樣又可以讀了,然後讀了以後再註冊寫,這樣反覆重複,的確,能很好的
解決問題了。可是是因爲什麼原因呢?當時我是這樣解決的,湊合用着,但是不知道爲什麼!
昨天偶爾想起這個問題,然後查看mina源碼時,發現其只註冊過讀事件,而對寫事件並沒有註冊過,只是發現有一些
key.interestOps(SelectionKey.OP_WRITE)的操作,不是很明白,後來查看幫忙文檔,知道這是管道當前感興趣的事件時突然有些明
白了,後來又查看jdk源碼,發現,每當重複註冊時,都會檢測事件是否已經註冊過,如果已經註冊過的事件,會把當前感興趣的事
件切換成現在感興趣的事件,這樣所有的都明白了。
即:管道註冊事件,可以有四種事件,它可以註冊所有的事件,但是管道每次只能對一種事件有效,即註冊讀事件時,此時只對讀事
件有效,註冊寫事件時,此時只對寫事件有效,這樣上面的笨方法來回註冊剛好達到這種效果,多的只是每次註冊時的一次判斷是否
已經註冊過。當然我們也有更好的方法,那就是通過key.interestOps方法來切換當前感興趣的事件,這樣就可以避免每次註冊時都
需要判斷了。
以上把問題與解決方法說明,現在附一下網上經常看到的服務器端實例!只是做了一些簡單的修改。另外客戶端網上很多也用非阻塞
實現的,我感覺沒有必要,因爲非阻塞只是爲了解決高併發的問題。下面附上的客戶端是我用socket寫的一個簡單實例!
//服務器
package com.netease.nio;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.CharBuffer;
import java.util.Iterator;
import java.io.IOException;
//基於非阻塞的服務器實現
public class NioServer {
private static int DEFAULT_PORT = 512;
private static int DEFAULT_BUFFERSIZE = 1023;
private static String DEFAULT_CHARSET = "UTF-8";
private ServerSocketChannel ss;
private Selector selector;
private ByteBuffer buffer;
private Charset charset;
private CharsetDecoder decoder;
private int port;
public NioServer() throws IOException{
this.port = DEFAULT_PORT;
this.ss = null;
this.selector = Selector.open();
this.buffer = ByteBuffer.allocate(DEFAULT_BUFFERSIZE);
this.charset = Charset.forName(DEFAULT_CHARSET);
this.decoder = this.charset.newDecoder();
}
//處理key
public void handlerKey(SelectionKey key) throws IOException{
if(key.isAcceptable()){
System.out.println("one client coming");
//取出對應的服務器通道
ServerSocketChannel server = (ServerSocketChannel)key.channel();
//對應socketchannel
SocketChannel channel = server.accept();
channel.configureBlocking(false);
//可讀
channel.register(selector, SelectionKey.OP_READ);
}else if(key.isReadable()){
SocketChannel channel = (SocketChannel) key.channel();
int count = channel.read(this.buffer);
System.out.println("開始read");
if (count > 0)
{
//把極限設爲位置,把位置設爲0,這樣讀取buffer時,剛好讀取其已經讀取多少內容的buffer
this.buffer.flip();
CharBuffer charBuffer = decoder.decode(this.buffer);
String message = charBuffer.toString();
//服務端接收客戶端來的數據
System.out.println("server:" + message);
// NioSession data = new NioSession(message);
//切換爲寫事件
key.interestOps(SelectionKey.OP_WRITE);
// channel.register(selector, SelectionKey.OP_WRITE);
key.attach(message);
}
else
{ //客戶已經斷開
channel.close();
}
//把極限設爲容量,再把位置設爲0。
// this.buffer.clear();//清空緩衝區,爲下一次接收數據做準備
//極限不變,再把位置設爲0,這樣跟初始化時buffer是一樣的
this.buffer.rewind();
}else if(key.isWritable() && key.isValid()){
SocketChannel channel = (SocketChannel) key.channel();
// NioSession handle = (NioSession) key.attachment();//取出處理者
String message = "re:"+((String)key.attachment());
// handle.setGreeting(message);
ByteBuffer block = ByteBuffer.wrap(message.getBytes());
channel.write(block);
//切換爲讀事件
key.interestOps(SelectionKey.OP_READ);
//此時重新註冊一個讀事件時,當調用此方法時,register當檢測讀事件已經存在時,
// channel.register(selector,SelectionKey.OP_READ);
}
}
public void listen() throws IOException
{ //服務器開始監聽端口,提供服務
ServerSocket socket;
ss = ServerSocketChannel.open(); // 打開通道
socket = ss.socket(); //得到與通到相關的socket對象
socket.bind(new InetSocketAddress(port)); //將scoket榜定在制定的端口上
//配置通到使用非阻塞模式,在非阻塞模式下,可以編寫多道程序同時避免使用複雜的多線程
ss.configureBlocking(false);
ss.register(selector, SelectionKey.OP_ACCEPT);
try
{
while(true)
{// 與通常的程序不同,這裏使用channel.accpet()接受客戶端連接請求,而不是在socket對象上調用accept(),
這裏在調用accept()方法時如果通道配置爲非阻塞模式,那麼accept()方法立即返回null,並不阻塞
this.selector.select();
Iterator iter = this.selector.selectedKeys().iterator();
while(iter.hasNext())
{
SelectionKey key = (SelectionKey)iter.next();
iter.remove();
this.handlerKey(key);
}
}
}
catch(IOException ex)
{
ex.printStackTrace();
this.ss.close();
}
}
public static void main(String[] args) throws IOException
{
System.out.println("服務器啓動 ");
NioServer server = new NioServer();
server.listen(); //服務器開始監聽端口,提供服務
}
}
//客戶端
package com.netease.nio;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.nio.CharBuffer;
import java.util.Scanner;
//nio 連接服務器
public class NioClient {
public static void main(String[] args) throws IOException{
String host = "127.0.0.1";
int port = 512;
Socket socket = new Socket(host,port);
System.out.println("客戶端起動");
while(true){
Scanner in = new Scanner(System.in);
String message = in.nextLine();
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.write(message);
pw.flush();
InputStreamReader reader = new InputStreamReader(socket.getInputStream());
CharBuffer buf = CharBuffer.allocate(100);
reader.read(buf);
buf.flip();
System.out.println("client:"+String.valueOf(buf));
if(message.equals("quit")){
break;
}
buf.clear();
}
socket.close();
}
}
以上有什麼問題,請指出,謝謝!