同步非阻塞IO (NIO)
- NIO是基于事件驱动思想的,实现上通常采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当发起IO的读或写操作时,是非阻塞的;当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。
- 对于网络IO而言,主要有连接建立、流读取及流写入三种事件、linux2.6以后的版本使用epoll(http://lse.sourceforge.net/epoll/index.html)方式实现NIO。
- select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
- 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。用select的优势在于它可以同时处理多个connection。
server:
1 package org.windwant.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 /**
15 * ServerSocketChannel
16 */
17 public class NIOServer {
18 /*标识数字*/
19 private int flag = 0;
20 /*缓冲区大小*/
21 private int BLOCK = 2048;
22 /*接受数据缓冲区*/
23 private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
24 /*发送数据缓冲区*/
25 private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
26 private Selector selector;
27
28 public NIOServer(int port) throws IOException {
29 // 打开服务器套接字通道
30
31 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
32 // 服务器配置为非阻塞
33
34 serverSocketChannel.configureBlocking(false);
35 // 检索与此通道关联的服务器套接字
36
37 ServerSocket serverSocket = serverSocketChannel.socket();
38 // 进行服务的绑定
39
40 serverSocket.bind(new InetSocketAddress(port));
41 // 通过open()方法找到Selector
42
43 selector = Selector.open();
44 // 注册到selector,等待连接
45
46 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
47 System.out.println("Server Start----8888:");
48 }
49
50
51 // 监听
52
53 private void listen() throws IOException {
54 while (true) {
55 // 选择一组键,并且相应的通道已经打开
56
57 selector.select();
58 // 返回此选择器的已选择键集。
59
60 Set<SelectionKey> selectionKeys = selector.selectedKeys();
61 Iterator<SelectionKey> iterator = selectionKeys.iterator();
62 while (iterator.hasNext()) {
63 SelectionKey selectionKey = iterator.next();
64 iterator.remove();
65 handleKey(selectionKey);
66 }
67 }
68 }
69
70 // 处理请求
71
72 private void handleKey(SelectionKey selectionKey) throws IOException {
73 // 接受请求
74
75 ServerSocketChannel server = null;
76 SocketChannel client = null;
77 String receiveText;
78 String sendText;
79 int count=0;
80 // 测试此键的通道是否已准备好接受新的套接字连接。
81
82 if (selectionKey.isAcceptable()) {
83 // 返回为之创建此键的通道。
84
85 server = (ServerSocketChannel) selectionKey.channel();
86 // 接受到此通道套接字的连接。
87
88 // 此方法返回的套接字通道(如果有)将处于阻塞模式。
89
90 client = server.accept();
91 // 配置为非阻塞
92
93 client.configureBlocking(false);
94 // 注册到selector,等待连接
95
96 client.register(selector, SelectionKey.OP_READ);
97 } else if (selectionKey.isReadable()) {
98 // 返回为之创建此键的通道。
99
100 client = (SocketChannel) selectionKey.channel();
101 //将缓冲区清空以备下次读取
102
103 receivebuffer.clear();
104 //读取服务器发送来的数据到缓冲区中
105
106 count = client.read(receivebuffer);
107 if (count > 0) {
108 receiveText = new String( receivebuffer.array(),0,count);
109 System.out.println("服务器端接受客户端数据--:"+receiveText);
110 client.register(selector, SelectionKey.OP_WRITE);
111 }
112 } else if (selectionKey.isWritable()) {
113 //将缓冲区清空以备下次写入
114
115 sendbuffer.clear();
116 // 返回为之创建此键的通道。
117
118 client = (SocketChannel) selectionKey.channel();
119
120
121 sendText = "<h1>message from server: this is the test message!</h1>";
122
123 // sendText="message from server--" + flag++;
124 //向缓冲区中输入数据
125
126 sendbuffer.put(sendText.getBytes());
127 //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
128
129 sendbuffer.flip();
130 //输出到通道
131
132 client.write(sendbuffer);
133 System.out.println("服务器端向客户端发送数据--:"+sendText);
134 client.register(selector, SelectionKey.OP_READ);
135 }
136 }
137
138 /**
139 * @param args
140 * @throws IOException
141 */
142 public static void main(String[] args) throws IOException {
143 int port = 8888;
144 NIOServer server = new NIOServer(port);
145 server.listen();
146 }
147 }
client:
1 package org.windwant.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 /**
13 * SocketChannel
14 */
15 public class NIOClient {
16 /*标识数字*/
17 private static int flag = 0;
18 /*缓冲区大小*/
19 private static int BLOCK = 4096;
20 /*接受数据缓冲区*/
21 private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
22 /*发送数据缓冲区*/
23 private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
24 /*服务器端地址*/
25 private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
26 "localhost", 8888);
27
28 public static void main(String[] args) throws IOException {
29 // 打开socket通道
30
31 SocketChannel socketChannel = SocketChannel.open();
32 // 设置为非阻塞方式
33
34 socketChannel.configureBlocking(false);
35 // 打开选择器
36
37 Selector selector = Selector.open();
38 // 注册连接服务端socket动作
39
40 socketChannel.register(selector, SelectionKey.OP_CONNECT);
41 // 连接
42
43 socketChannel.connect(SERVER_ADDRESS);
44 // 分配缓冲区大小内存
45
46
47 Set<SelectionKey> selectionKeys;
48 Iterator<SelectionKey> iterator;
49 SelectionKey selectionKey;
50 SocketChannel client;
51 String receiveText;
52 String sendText;
53 int count=0;
54
55 while (true) {
56 //选择一组键,其相应的通道已为 I/O 操作准备就绪。
57
58 //此方法执行处于阻塞模式的选择操作。
59
60 selector.select();
61 //返回此选择器的已选择键集。
62
63 selectionKeys = selector.selectedKeys();
64 //System.out.println(selectionKeys.size());
65
66 iterator = selectionKeys.iterator();
67 while (iterator.hasNext()) {
68 selectionKey = iterator.next();
69 if (selectionKey.isConnectable()) {
70 System.out.println("client connect");
71 client = (SocketChannel) selectionKey.channel();
72 // 判断此通道上是否正在进行连接操作。
73
74 // 完成套接字通道的连接过程。
75
76 if (client.isConnectionPending()) {
77 client.finishConnect();
78 System.out.println("完成连接!");
79 sendbuffer.clear();
80 sendbuffer.put("Hello,Server".getBytes());
81 sendbuffer.flip();
82 client.write(sendbuffer);
83 }
84 client.register(selector, SelectionKey.OP_READ);
85 } else if (selectionKey.isReadable()) {
86 client = (SocketChannel) selectionKey.channel();
87 //将缓冲区清空以备下次读取
88
89 receivebuffer.clear();
90 //读取服务器发送来的数据到缓冲区中
91
92 count=client.read(receivebuffer);
93 if(count>0){
94 receiveText = new String( receivebuffer.array(),0,count);
95 System.out.println("客户端接受服务器端数据--:"+receiveText);
96 client.register(selector, SelectionKey.OP_WRITE);
97 }
98
99 } else if (selectionKey.isWritable()) {
100 sendbuffer.clear();
101 client = (SocketChannel) selectionKey.channel();
102 sendText = "message from client--" + (flag++);
103 sendbuffer.put(sendText.getBytes());
104 //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
105
106 sendbuffer.flip();
107 client.write(sendbuffer);
108 System.out.println("客户端向服务器端发送数据--:"+sendText);
109 client.register(selector, SelectionKey.OP_READ);
110 }
111 }
112 selectionKeys.clear();
113 }
114 }
115 }