JAVA NIO 服務器與客戶端實現示例

以下代碼只兼容Java 7及以上版本,對於一些關鍵地方請看註釋說明。


公共類:

package com.stevex.app.nio;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class CharsetHelper {
	private static final String UTF_8 = "UTF-8";
	private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
	private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
	
	public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
		return encoder.encode(in);
	}

	public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
		return decoder.decode(in);
	}
}


服務器代碼:

package com.stevex.app.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class XiaoNa {
	private ByteBuffer readBuffer;
	private Selector selector;
	
	public static void main(String[] args){
		XiaoNa xiaona = new XiaoNa();
		xiaona.init();
		xiaona.listen();
	}
	
	private void init(){
		readBuffer = ByteBuffer.allocate(1024);
		ServerSocketChannel servSocketChannel;
		
		try {
			servSocketChannel = ServerSocketChannel.open();
			servSocketChannel.configureBlocking(false);
			//綁定端口
			servSocketChannel.socket().bind(new InetSocketAddress(8383));
			
			selector = Selector.open();
			servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}

	private void listen() {
		while(true){
			try{
				selector.select();				
				Iterator ite = selector.selectedKeys().iterator();
				
				while(ite.hasNext()){
					SelectionKey key = (SelectionKey) ite.next();					
					ite.remove();//確保不重複處理
					
					handleKey(key);
				}
			}
			catch(Throwable t){
				t.printStackTrace();
			}							
		}				
	}

	private void handleKey(SelectionKey key)
			throws IOException, ClosedChannelException {
		SocketChannel channel = null;
		
		try{
			if(key.isAcceptable()){
				ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
				channel = serverChannel.accept();//接受連接請求
				channel.configureBlocking(false);
				channel.register(selector, SelectionKey.OP_READ);
			}
			else if(key.isReadable()){
				channel = (SocketChannel) key.channel();
				readBuffer.clear();
				/*當客戶端channel關閉後,會不斷收到read事件,但沒有消息,即read方法返回-1
				 * 所以這時服務器端也需要關閉channel,避免無限無效的處理*/				
				int count = channel.read(readBuffer);
				
				if(count > 0){
					//一定需要調用flip函數,否則讀取錯誤數據
					readBuffer.flip();
					/*使用CharBuffer配合取出正確的數據
					String question = new String(readBuffer.array());  
					可能會出錯,因爲前面readBuffer.clear();並未真正清理數據
					只是重置緩衝區的position, limit, mark,
					而readBuffer.array()會返回整個緩衝區的內容。
					decode方法只取readBuffer的position到limit數據。
					例如,上一次讀取到緩衝區的是"where", clear後position爲0,limit爲	1024,
					再次讀取“bye"到緩衝區後,position爲3,limit不變,
					flip後position爲0,limit爲3,前三個字符被覆蓋了,但"re"還存在緩衝區中,
					所以 new String(readBuffer.array()) 返回 "byere",
					而decode(readBuffer)返回"bye"。				
					*/
					CharBuffer charBuffer = CharsetHelper.decode(readBuffer); 
					String question = charBuffer.toString(); 
					String answer = getAnswer(question);
					channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));
				}
				else{
					//這裏關閉channel,因爲客戶端已經關閉channel或者異常了
					channel.close();				
				}						
			}
		}
		catch(Throwable t){
			t.printStackTrace();
			if(channel != null){
				channel.close();
			}
		}		
	}
	
	private String getAnswer(String question){
		String answer = null;
		
		switch(question){
		case "who":
			answer = "我是小娜\n";
			break;
		case "what":
			answer = "我是來幫你解悶的\n";
			break;
		case "where":
			answer = "我來自外太空\n";
			break;
		case "hi":
			answer = "hello\n";
			break;
		case "bye":
			answer = "88\n";
			break;
		default:
				answer = "請輸入 who, 或者what, 或者where";
		}
		
		return answer;
	}
}


客戶端代碼:

package com.stevex.app.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;


public class Client implements Runnable{
	private BlockingQueue<String> words;
	private Random random;
	
	public static void main(String[] args) {		
		//種多個線程發起Socket客戶端連接請求
		for(int i=0; i<10; i++){
			Client c = new Client();
			c.init();
			new Thread(c).start();
		}		
	}

	@Override
	public void run() {		
		SocketChannel channel = null;
		Selector selector = null;
		try {
			channel = SocketChannel.open();
			channel.configureBlocking(false);
			//請求連接
			channel.connect(new InetSocketAddress("localhost", 8383));
			selector = Selector.open();
			channel.register(selector, SelectionKey.OP_CONNECT);
			boolean isOver = false;
			
			while(! isOver){
				selector.select();
				Iterator ite = selector.selectedKeys().iterator();
				while(ite.hasNext()){
					SelectionKey key = (SelectionKey) ite.next();
					ite.remove();
					
					if(key.isConnectable()){
						if(channel.isConnectionPending()){
							if(channel.finishConnect()){
								//只有當連接成功後才能註冊OP_READ事件
								key.interestOps(SelectionKey.OP_READ);
								
								channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));
								sleep();
							}
							else{
								key.cancel();
							}
						}												
					}
					else if(key.isReadable()){
						ByteBuffer byteBuffer = ByteBuffer.allocate(128);						
						channel.read(byteBuffer);
						byteBuffer.flip();
						CharBuffer charBuffer = CharsetHelper.decode(byteBuffer);
						String answer = charBuffer.toString(); 
						System.out.println(Thread.currentThread().getId() + "---" + answer);
						
						String word = getWord();
						if(word != null){
							channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));
						}
						else{
							isOver = true;
						}
						sleep();						
					}
				}
			}							
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally{
			if(channel != null){
				try {
					channel.close();
				} catch (IOException e) {						
					e.printStackTrace();
				}					
			}
			
			if(selector != null){
				try {
					selector.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void init() {
		words = new ArrayBlockingQueue<String>(5);
		try {
			words.put("hi");
			words.put("who");
			words.put("what");
			words.put("where");
			words.put("bye");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		
		random = new Random();
	}
	
	private String getWord(){
		return words.poll();
	}

	private void sleep() {
		try {
			TimeUnit.SECONDS.sleep(random.nextInt(3));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}	
	
	private void sleep(long l) {
		try {
			TimeUnit.SECONDS.sleep(l);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章