开发前的准备:
在写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 = 登录服务器成功