前言
本文旨在介绍传统Socket服务端与NIO服务端的差异.
以餐厅服务员简单举例,每个客人对应一个请求.
传统Socket / OIO
1 public class OioServer { 2 3 @SuppressWarnings("resource") 4 public static void main(String[] args) throws Exception { 5 6 ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); 7 //创建socket服务,监听10101端口 8 ServerSocket server=new ServerSocket(10101); 9 System.out.println("服务器启动!"); 10 while(true){ 11 //获取一个套接字(阻塞) 12 final Socket socket = server.accept(); 13 System.out.println("来个一个新客户端!"); 14 newCachedThreadPool.execute(new Runnable() { 15 16 @Override 17 public void run() { 18 //业务处理 19 handler(socket); 20 } 21 }); 22 23 } 24 } 25 26 /** 27 * 读取数据 28 * @param socket 29 * @throws Exception 30 */ 31 public static void handler(Socket socket){ 32 try { 33 byte[] bytes = new byte[1024]; 34 InputStream inputStream = socket.getInputStream(); 35 36 while(true){ 37 //读取数据(阻塞) 38 int read = inputStream.read(bytes); 39 if(read != -1){ 40 System.out.println(new String(bytes, 0, read)); 41 }else{ 42 break; 43 } 44 } 45 } catch (Exception e) { 46 e.printStackTrace(); 47 }finally{ 48 try { 49 System.out.println("socket关闭"); 50 socket.close(); 51 } catch (IOException e) { 52 e.printStackTrace(); 53 } 54 } 55 } 56 }
缺点
单线程情况下只能有一个客户端
用线程池可以有多个客户端连接,但是非常消耗性能
类比图
NIOServer
1 public class NIOServer { 2 // 通道管理器 3 private Selector selector; 4 5 /** 6 * 获得一个ServerSocket通道,并对该通道做一些初始化的工作 7 * 8 * @param port 9 * 绑定的端口号 10 * @throws IOException 11 */ 12 public void initServer(int port) throws IOException { 13 // 获得一个ServerSocket通道 14 ServerSocketChannel serverChannel = ServerSocketChannel.open(); 15 // 设置通道为非阻塞 16 serverChannel.configureBlocking(false); 17 // 将该通道对应的ServerSocket绑定到port端口 18 serverChannel.socket().bind(new InetSocketAddress(port)); 19 // 获得一个通道管理器 20 this.selector = Selector.open(); 21 // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, 22 // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 23 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 24 } 25 26 /** 27 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 28 * 29 * @throws IOException 30 */ 31 public void listen() throws IOException { 32 System.out.println("服务端启动成功!"); 33 // 轮询访问selector 34 while (true) { 35 // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞 36 selector.select(); 37 // 获得selector中选中的项的迭代器,选中的项为注册的事件 38 Iterator<?> ite = this.selector.selectedKeys().iterator(); 39 while (ite.hasNext()) { 40 SelectionKey key = (SelectionKey) ite.next(); 41 // 删除已选的key,以防重复处理 42 ite.remove(); 43 44 handler(key); 45 } 46 } 47 } 48 49 /** 50 * 处理请求 51 * 52 * @param key 53 * @throws IOException 54 */ 55 public void handler(SelectionKey key) throws IOException { 56 57 // 客户端请求连接事件 58 if (key.isAcceptable()) { 59 handlerAccept(key); 60 // 获得了可读的事件 61 } else if (key.isReadable()) { 62 handelerRead(key); 63 } 64 } 65 66 /** 67 * 处理连接请求 68 * 69 * @param key 70 * @throws IOException 71 */ 72 public void handlerAccept(SelectionKey key) throws IOException { 73 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 74 // 获得和客户端连接的通道 75 SocketChannel channel = server.accept(); 76 // 设置成非阻塞 77 channel.configureBlocking(false); 78 79 // 在这里可以给客户端发送信息哦 80 System.out.println("新的客户端连接"); 81 // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 82 channel.register(this.selector, SelectionKey.OP_READ); 83 } 84 85 /** 86 * 处理读的事件 87 * 88 * @param key 89 * @throws IOException 90 */ 91 public void handelerRead(SelectionKey key) throws IOException { 92 // 服务器可读取消息:得到事件发生的Socket通道 93 SocketChannel channel = (SocketChannel) key.channel(); 94 // 创建读取的缓冲区 95 ByteBuffer buffer = ByteBuffer.allocate(1024); 96 int read = channel.read(buffer); 97 if(read > 0){ 98 byte[] data = buffer.array(); 99 String msg = new String(data).trim(); 100 System.out.println("服务端收到信息:" + msg); 101 102 //回写数据 103 ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes()); 104 channel.write(outBuffer);// 将消息回送给客户端 105 }else{ 106 System.out.println("客户端关闭"); 107 key.cancel(); 108 } 109 } 110 111 /** 112 * 启动服务端测试 113 * 114 * @throws IOException 115 */ 116 public static void main(String[] args) throws IOException { 117 NIOServer server = new NIOServer(); 118 server.initServer(8000); 119 server.listen(); 120 } 121 122 }
优点
利用Selector多路复用技术, 一个线程可以处理多个客户端.
类比图