開發前的準備:
在寫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 = 登錄服務器成功