JAVA NIO 服務器(二)

開發前的準備:

在寫NIO 服務器前,需要掌握IO的原理,以及那幾個常用對象的關係,這樣或許才能做到代碼在心中,就算是出了問題也可以自己調試解決。

 ByteBuffer 與Channel:在讀操作的時候,channel會把數據讀到ByteBuffer裏。在寫操作的時候,channel會把ByteBuffer發送給客戶端。

Channel與Selector:一個Selector可以管理多個Channel,一個Channel可以註冊到多個Selector.Selector用來管理Channel的4種操作.

SelectionKey:選擇鍵封裝了特定的通道與特定的選擇器的註冊關係。

服務代碼實例:

模仿了玩家登錄,向服務器發送請求包,以及服務向端客戶端返回請求結果.如標題一樣,這是很簡單的一個demo,還有很大地方封裝不夠,只是介紹一下NIO服務器怎麼樣的,暫時沒有實際的作用,這些沒完成的功能將在下一篇寫出。


服務器代碼:

package packge1;

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

/**
 *
 * @author Jack Lei
 * @see readme.txt
 * @date 2016年1月17日 下午2:47:11
 */
public class Server {
	private final Logger		logger	= Logger.getLogger(Server.class.getName());
	private ServerSocketChannel ssc;
	private Selector			selector;

	public Server(String host, int port) {
		try {
			ssc = ServerSocketChannel.open();
			ssc.socket().bind(new InetSocketAddress(host, port));
			ssc.configureBlocking(false);// 設置爲非阻塞
			selector = Selector.open();
			ssc.register(selector, SelectionKey.OP_ACCEPT);// 註冊選擇器,並註冊接受連接的鍵
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void start() throws IOException{
		logger.info("######## Server Start ########");
		while(true){
			int select = selector.select();//獲取已準備好的鍵
			if (select > 0) {
				Iterator<SelectionKey> iterator = selector.selectedKeys()
															.iterator();// 此選擇器的已選擇鍵集
				while (iterator.hasNext()) {
					SelectionKey key = iterator.next();
					if (key.isAcceptable()) {
						ServerSocketChannel ssc = (ServerSocketChannel) key.channel();// 這麼做是安全的,因爲只有ServerSocketChannel才支持ACCEPT
						onConnect(ssc.accept());
					} else if (key.isReadable()) {
						onReadDataFromSocket(key);
					}
					iterator.remove();
				}
			}
		}
	}

	public void onConnect(SocketChannel socket) {
		try {
			socket.configureBlocking(false);
			socket.register(selector,
							SelectionKey.OP_READ | SelectionKey.OP_WRITE);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void onReadDataFromSocket(SelectionKey key) {
		SocketChannel socket = (SocketChannel) key.channel();
		ByteBuffer bb = ByteBuffer.allocate(1024);
		try {
			int readCnt = socket.read(bb);
			if (readCnt > 0) {
				bb.flip();// 準備取數據
				short packetId = bb.getShort();
				int size = bb.getInt();
				byte[] body = new byte[size];
				bb.get(body);
				logger.info("recive data from client packetId = " + packetId + " size = " + size + "msg = " + new String(body));

				if (packetId == 1001) {
					String msg = "登錄服務器成功";
					byte[] bytes = msg.getBytes();
					// 數據包= 包類型+包長度+包內容。
					ByteBuffer bf = ByteBuffer.allocate(Short.SIZE / 8 + Integer.SIZE / 8 + bytes.length);
					bf.putShort((short) 1002);// 假設1001是登錄返回的包
					bf.putInt(bytes.length);// 這個包的大小,讓客戶端知道讀到哪個位置結束
					bf.put(bytes);// 寫入包的內容
					bf.flip();
					socket.write(bf);
				}

			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main(String args[]) {
		try {
			new Server("127.0.0.1", 7777).start();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


客戶端代碼:

package packge1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.logging.Logger;

/**
 *
 * @author Jack Lei
 * @see readme.txt
 * @date 2016年1月17日 下午4:27:58
 */
public class Client {
	private Logger			logger	= Logger.getLogger(Client.class.getName());
	private SocketChannel	sc;
	private Selector		selector;
	private static boolean	login	= false;
	public Client(String host, int port) {
		try {
			sc = SocketChannel.open();
			selector = Selector.open();
			sc.configureBlocking(false);
			sc.register(selector,
						SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
			sc.connect(new InetSocketAddress(host, port));


		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void start() throws IOException {
		while (true) {
			int selectCnt = selector.select();
			if (selectCnt > 0) {
				Iterator<SelectionKey> iterator = selector.selectedKeys()
															.iterator();
				while (iterator.hasNext()) {
					SelectionKey key = iterator.next();
					if (key.isConnectable()) {
						SocketChannel socket = (SocketChannel) key.channel();
						socket.finishConnect();// 完成連接,不完成會一直執行這個操作
						login = true;
						logger.info("finish connect..");
					} else if (key.isReadable()) {
						SocketChannel socket = (SocketChannel) key.channel();
						ByteBuffer bb = ByteBuffer.allocate(1024);
						int readCnt = socket.read(bb);
						if (readCnt > 0) {
							bb.flip();
							short packetId = bb.getShort();
							int size = bb.getInt();
							byte[] body = new byte[size];
							bb.get(body);
							logger.info("recive server data packetId = " + packetId + " size = " + size + " body = " + new String(body));
						}

					} else if (key.isWritable()) {
						if (login) {
							SocketChannel socket = (SocketChannel) key.channel();
							// 向服務端發起請求
							ByteBuffer bb = ByteBuffer.allocate(1024);
							String msg = "請求連接";
							bb.putShort((short) 1001);
							byte[] bytes = msg.getBytes();
							bb.putInt(bytes.length);
							bb.put(bytes);
							bb.flip();
							socket.write(bb);
							login = false;
						}
					}
					iterator.remove();
				}
			}
		}
	}

	public static void main(String args[]) throws IOException {
		new Client("127.0.0.1", 7777).start();
	}
}

控制檯信息:server/client

一月 17, 2016 10:38:51 下午 packge1.Server start
信息: ######## Server Start ########
一月 17, 2016 10:38:56 下午 packge1.Server onReadDataFromSocket
信息: recive data from client packetId = 1001 size = 8 msg = 請求連接


一月 17, 2016 10:38:56 下午 packge1.Client start
信息: finish connect..
一月 17, 2016 10:38:56 下午 packge1.Client start
信息: recive server data packetId = 1002 size = 14 body = 登錄服務器成功


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章