NIO学习

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 }

 

上一篇:1. Netty准备知识:Java NIO


下一篇:非阻塞式网络编程