1. NIO客户端与服务端网络编程关键:
理解各个监听事件的驱动事件,总结以下几点:
(1)ServerSocketChannel注册了OP_ACCEPT事件,需要客户端发起连接请求,服务端selector才能监听到(阻塞在selector.select()才能继续执行)
(2)服务端获取客户端SocketChannel,注册读事件后,只有客户端写事件,服务端selector才能监听到(阻塞在selector.select()才能继续执行,读事件是被动的,需要写事件驱动)
(3)不管服务端还是客户端,本端注册的管道写事件,selector马上能监听到(总结:写事件是主动的,只要注册,就能监听到)
(4)SelectionKey.OP_CONNECT(主动事件,客户端注册完之后,本端selector马上能监听到)-->isConnectable()为true
SelectionKey.OP_ACCEPT(被动事件,需服务端注册accepte事件,还需要客户端发连接请求触发)-->isAcceptable()为true
SelectionKey.OP_WRITE(主动事件,客户端注册完之后,本端selector马上能监听到)-->isWritable()为true
SelectionKey.OP_READ(被动事件,需本端注册读事件,并且对端有写事件才触发)-->isReadable()为true
2. 服务端代码:
1 package com.hw.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.net.ServerSocket; 6 import java.nio.ByteBuffer; 7 import java.nio.channels.SelectionKey; 8 import java.nio.channels.Selector; 9 import java.nio.channels.ServerSocketChannel; 10 import java.nio.channels.SocketChannel; 11 import java.util.Iterator; 12 import java.util.Set; 13 /** 14 * NIO客户端与服务端网络编程关键: 15 * 理解各个监听事件的驱动事件,例如: 16 * (1)ServerSocketChannel注册了OP_ACCEPT事件,需要客户端发起连接请求,服务端selector才能监听到(阻塞在selector.select()才能继续执行) 17 * (2)服务端获取客户端SocketChannel,注册读事件后,只有客户端写事件,服务端selector才能监听到(阻塞在selector.select()才能继续执行)(总结:读事件是被动的,需要写事件驱动) 18 * (3)不管服务端还是客户端,本端注册的管道写事件,selector马上能监听到(总结:写事件是主动的,只要注册,就能监听到) 19 * (5)SelectionKey.OP_CONNECT(主动事件,客户端注册完之后,本端selector马上能监听到)-->isConnectable()为true 20 * SelectionKey.OP_ACCEPT(被动事件,需服务端注册accepte事件,还需要客户端发连接请求触发)-->isAcceptable()为true 21 * SelectionKey.OP_WRITE(主动事件,客户端注册完之后,本端selector马上能监听到)-->isWritable()为true 22 * SelectionKey.OP_READ(被动事件,需本端注册读事件,并且对端有写事件才触发)-->isReadable()为true 23 * @author thinkpad 24 * 25 */ 26 public class NIOServer { 27 28 /*标识数字*/ 29 private int flag = 0; 30 /*缓冲区大小*/ 31 private int BLOCK = 4096; 32 /*接受数据缓冲区*/ 33 private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); 34 /*发送数据缓冲区*/ 35 private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); 36 private Selector selector; 37 38 public NIOServer(int port) throws IOException { 39 // 打开服务端套接字通道 40 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 41 // 服务器配置为非阻塞 42 serverSocketChannel.configureBlocking(false); 43 // 检索与此通道关联的服务器套接字 44 ServerSocket serverSocket = serverSocketChannel.socket(); 45 // 进行服务的绑定 46 serverSocket.bind(new InetSocketAddress(port)); 47 // 通过open()方法找到Selector 48 selector = Selector.open(); 49 // 注册通道到selector,等待连接 50 // self:1.如果不注册OP_ACCEPT,即使客户端发连接请求,selector也无法监听到连接事件,导致selector.select()一直阻塞等待 51 // 2.即使注册了OP_ACCEPTE,客户端如果不发送连接请求,服务端也会一直阻塞在selector.select(); 52 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 53 System.out.println("Server Start----8888:"); 54 } 55 56 57 // 监听 58 private void listen() throws IOException { 59 while (true) { 60 // 选择一组键,并且相应的通道已经打开(服务端如果注册了OP_ACCEPTE,第一次等待客户端连接,若客户端无连接请求,阻塞在此处) 61 selector.select(); 62 // 返回此选择器的已选择键集。 63 Set<SelectionKey> selectionKeys = selector.selectedKeys(); 64 Iterator<SelectionKey> iterator = selectionKeys.iterator(); 65 while (iterator.hasNext()) { 66 SelectionKey selectionKey = iterator.next(); 67 iterator.remove(); 68 handleKey(selectionKey); 69 } 70 } 71 } 72 73 // 处理请求 74 private void handleKey(SelectionKey selectionKey) throws IOException { 75 // 接受请求 76 ServerSocketChannel server = null; 77 SocketChannel client = null; 78 String receiveText; 79 String sendText; 80 int count=0; 81 // 测试此键的通道是否已准备好接受新的套接字连接。 82 if (selectionKey.isAcceptable()) { 83 // 返回为之创建此键的通道。 84 server = (ServerSocketChannel) selectionKey.channel(); 85 // 接受到此通道套接字的连接。 86 // 此方法返回的套接字通道(如果有)将处于阻塞模式。 87 client = server.accept(); 88 // 配置为非阻塞 89 client.configureBlocking(false); 90 // 注册到selector,等待连接 91 client.register(selector, SelectionKey.OP_READ); 92 } else if (selectionKey.isReadable()) { // 仅仅注册了读事件还不行,还需要客户端写才能触发 93 // 返回为之创建此键的通道。 94 client = (SocketChannel) selectionKey.channel(); 95 //将缓冲区清空以备下次读取 96 receivebuffer.clear(); 97 //读取服务器发送来的数据到缓冲区中 98 count = client.read(receivebuffer); 99 if (count > 0) { 100 receiveText = new String( receivebuffer.array(),0,count); 101 System.out.println("服务器端接受客户端数据--:"+receiveText); 102 client.register(selector, SelectionKey.OP_WRITE); 103 } 104 } else if (selectionKey.isWritable()) { 105 //将缓冲区清空以备下次写入 106 sendbuffer.clear(); 107 // 返回为之创建此键的通道。 108 client = (SocketChannel) selectionKey.channel(); 109 sendText="message from server--" + flag++; 110 //向缓冲区中输入数据 111 sendbuffer.put(sendText.getBytes()); 112 //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位 113 sendbuffer.flip(); 114 //输出到通道 115 client.write(sendbuffer); 116 System.out.println("服务器端向客户端发送数据--:"+sendText); 117 client.register(selector, SelectionKey.OP_READ); 118 } 119 } 120 121 /** 122 * @param args 123 * @throws IOException 124 */ 125 public static void main(String[] args) throws IOException { 126 int port = 8888; 127 NIOServer server = new NIOServer(port); 128 server.listen(); 129 } 130 }
3. 客户端代码:
1 package com.hw.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.SocketChannel; 9 import java.util.Iterator; 10 import java.util.Set; 11 12 public class NIOClient { 13 14 /*标识数字*/ 15 private static int flag = 0; 16 /*缓冲区大小*/ 17 private static int BLOCK = 4096; 18 /*接受数据缓冲区*/ 19 private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK); 20 /*发送数据缓冲区*/ 21 private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK); 22 /*服务器端地址*/ 23 private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress( 24 "localhost", 8888); 25 26 public static void main(String[] args) throws IOException { 27 // TODO Auto-generated method stub 28 // 打开socket通道 29 SocketChannel socketChannel = SocketChannel.open(); 30 // 设置为非阻塞方式 31 socketChannel.configureBlocking(false); 32 // 打开选择器 33 Selector selector = Selector.open(); 34 // 注册连接服务端socket动作(self:只有将管道注册到selector上,selector.select()才会执行,否则阻塞,selector只会监听本端的 35 // channel) 36 socketChannel.register(selector, SelectionKey.OP_CONNECT); 37 // 连接 38 socketChannel.connect(SERVER_ADDRESS); 39 // 分配缓冲区大小内存 40 41 Set<SelectionKey> selectionKeys; 42 Iterator<SelectionKey> iterator; 43 SelectionKey selectionKey; 44 SocketChannel client; 45 String receiveText; 46 String sendText; 47 int count=0; 48 49 while (true) { 50 //选择一组键,其相应的通道已为 I/O 操作准备就绪。 51 //此方法执行处于阻塞模式的选择操作。 52 selector.select(); 53 //返回此选择器的已选择键集。 54 selectionKeys = selector.selectedKeys(); 55 //System.out.println(selectionKeys.size()); 56 iterator = selectionKeys.iterator(); 57 while (iterator.hasNext()) { 58 selectionKey = iterator.next(); 59 if (selectionKey.isConnectable()) { 60 System.out.println("client connect"); 61 client = (SocketChannel) selectionKey.channel(); 62 // 判断此通道上是否正在进行连接操作。 63 // 完成套接字通道的连接过程。 64 if (client.isConnectionPending()) { 65 client.finishConnect(); 66 System.out.println("完成连接!"); 67 sendbuffer.clear(); 68 sendbuffer.put("Hello,Server".getBytes()); 69 sendbuffer.flip(); 70 // sendbuffer = null;如果写个空,服务端也不理 71 client.write(sendbuffer); 72 } 73 // self:向服务器端写完数据,用于客户端Socket管道注册读事件,如果本端(客户端)selector监听到服务端向客户端写数据,阻塞在selector.select()处的代码继续执行,则可以读取数据 74 // 本端注册读事件,是为了响应对端的写事件 75 client.register(selector, SelectionKey.OP_READ); 76 } else if (selectionKey.isReadable()) { 77 client = (SocketChannel) selectionKey.channel(); 78 //将缓冲区清空以备下次读取 79 receivebuffer.clear(); 80 //读取服务器发送来的数据到缓冲区中 81 count=client.read(receivebuffer); 82 if(count>0){ 83 receiveText = new String( receivebuffer.array(),0,count); 84 System.out.println("客户端接受服务器端数据--:"+receiveText); 85 // self:写事件注册完之后,selector马上能监听到,本端的写事件是主动的发起,读事件需要对端的写事件驱动 86 client.register(selector, SelectionKey.OP_WRITE); 87 } 88 89 } else if (selectionKey.isWritable()) { 90 sendbuffer.clear(); 91 client = (SocketChannel) selectionKey.channel(); 92 sendText = "message from client--" + (flag++); 93 sendbuffer.put(sendText.getBytes()); 94 //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位 95 sendbuffer.flip(); 96 client.write(sendbuffer); 97 System.out.println("客户端向服务器端发送数据--:"+sendText); 98 client.register(selector, SelectionKey.OP_READ); 99 } 100 } 101 selectionKeys.clear(); 102 } 103 } 104 }
4. 测试代码:
1 package com.hw.nio; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.net.InetSocketAddress; 6 import java.net.ServerSocket; 7 import java.net.Socket; 8 import java.net.SocketAddress; 9 import java.nio.ByteBuffer; 10 import java.nio.channels.SocketChannel; 11 import java.util.concurrent.TimeUnit; 12 13 public class NIOTest { 14 15 public static void client() { 16 ByteBuffer buffer = ByteBuffer.allocate(1024); 17 SocketChannel socketChannel = null; 18 try { 19 socketChannel = SocketChannel.open(); 20 socketChannel.configureBlocking(false); 21 socketChannel.connect(new InetSocketAddress("localhost", 8080)); 22 23 if (socketChannel.finishConnect()) { 24 int i = 0; 25 while (true) { 26 TimeUnit.SECONDS.sleep(1); 27 String info = "I'm " + i++ + "-th information from client"; 28 buffer.clear(); 29 buffer.put(info.getBytes()); 30 buffer.flip(); 31 while (buffer.hasRemaining()) { 32 System.out.println(buffer); 33 socketChannel.write(buffer); 34 } 35 } 36 } 37 } catch (IOException | InterruptedException e) { 38 e.printStackTrace(); 39 } finally { 40 try { 41 if (socketChannel != null) { 42 socketChannel.close(); 43 } 44 } catch (IOException e) { 45 e.printStackTrace(); 46 } 47 } 48 } 49 50 public static void server() { 51 ServerSocket serverSocket = null; 52 InputStream in = null; 53 try { 54 serverSocket = new ServerSocket(8080); 55 int recvMsgSize = 0; 56 byte[] recvBuf = new byte[1024]; 57 while (true) { 58 Socket clntSocket = serverSocket.accept(); 59 SocketAddress clientAddress = clntSocket.getRemoteSocketAddress(); 60 System.out.println("Handling client at " + clientAddress); 61 in = clntSocket.getInputStream(); 62 while ((recvMsgSize = in.read(recvBuf)) != -1) { 63 byte[] temp = new byte[recvMsgSize]; 64 System.arraycopy(recvBuf, 0, temp, 0, recvMsgSize); 65 System.out.println(new String(temp)); 66 } 67 } 68 } catch (IOException e) { 69 e.printStackTrace(); 70 } finally { 71 try { 72 if (serverSocket != null) { 73 serverSocket.close(); 74 } 75 if (in != null) { 76 in.close(); 77 } 78 } catch (IOException e) { 79 e.printStackTrace(); 80 } 81 } 82 } 83 public static void main(String[] args) { 84 if("client".equals(args[0])) { 85 client(); 86 } 87 server(); 88 if("server".equals(args[0])) { 89 server(); 90 } 91 } 92 }