Java网络编程-(1)-NIO基础

Java BIO

传统的网络模式是BIO的模式
BIO

我们发现BIO编程模型中最为致命的部分就是 我们需要为 Stream 使用一个 Thread
所以在JDK1.6中引入了NIO,优化这一部分。

Java NIO

NIO 的网络模型对比BIO模式,主要采用 面向缓存 的方式。
NIO

Java NIO 编程抽象

Java将NIO抽象为三个模型 Channels Buffers Selectors

  • Channels: 提供Buffers的读写能力
  • Buffers: 面向缓存的数据
  • Selectors: 选择器

Selector 可以让一个线程处理多个 Channels
overview-selectors

关于Channels,如果我一点Linux编程的经验,我们应该也会猜测出Channel中是储存具体的FD(文件句柄)。
overview-channels-buffers

Java NIO 的编程模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import javax.swing.text.html.HTMLDocument.Iterator;

/**
* Simple echo-back server which listens for incoming stream connections and
* echoes back whatever it reads. A single Selector object is used to listen to
* the server socket (to accept new connections) and all the active socket
* channels.
* @author zale (zalezone.cn)
*/
public class SelectSockets {
public static int PORT_NUMBER = 1234;
public static void main(String[] argv) throws Exception
{
new SelectSockets().go(argv);
}
public void go(String[] argv) throws Exception
{
int port = PORT_NUMBER;
if (argv.length > 0)
{ // 覆盖默认的监听端口
port = Integer.parseInt(argv[0]);
}
System.out.println("Listening on port " + port);
ServerSocketChannel serverChannel = ServerSocketChannel.open();// 打开一个未绑定的serversocketchannel
ServerSocket serverSocket = serverChannel.socket();// 得到一个ServerSocket去和它绑定
Selector selector = Selector.open();// 创建一个Selector供下面使用
serverSocket.bind(new InetSocketAddress(port));//设置server channel将会监听的端口
serverChannel.configureBlocking(false);//设置非阻塞模式
serverChannel.register(selector, SelectionKey.OP_ACCEPT);//将ServerSocketChannel注册到Selector
while (true)
{
// This may block for a long time. Upon returning, the
// selected set contains keys of the ready channels.
int n = selector.select();
if (n == 0)
{
continue; // nothing to do
}
java.util.Iterator<SelectionKey> it = selector.selectedKeys().iterator();// Get an iterator over the set of selected keys
//在被选择的set中遍历全部的key
while (it.hasNext())
{
SelectionKey key = (SelectionKey) it.next();
// 判断是否是一个连接到来
if (key.isAcceptable())
{
ServerSocketChannel server =(ServerSocketChannel) key.channel();
SocketChannel channel = server.accept();
registerChannel(selector, channel,SelectionKey.OP_READ);//注册读事件
sayHello(channel);//对连接进行处理
}
//判断这个channel上是否有数据要读
if (key.isReadable())
{
readDataFromSocket(key);
}
//从selected set中移除这个key,因为它已经被处理过了
it.remove();
}
}
}
// ----------------------------------------------------------
/**
* Register the given channel with the given selector for the given
* operations of interest
*/
protected void registerChannel(Selector selector,SelectableChannel channel, int ops) throws Exception
{
if (channel == null)
{
return; // 可能会发生
}
// 设置通道为非阻塞
channel.configureBlocking(false);
// 将通道注册到选择器上
channel.register(selector, ops);
}
// ----------------------------------------------------------
// Use the same byte buffer for all channels. A single thread is
// servicing all the channels, so no danger of concurrent acccess.
//对所有的通道使用相同的缓冲区。单线程为所有的通道进行服务,所以并发访问没有风险
private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
/**
* Sample data handler method for a channel with data ready to read.
* 对于一个准备读入数据的通道的简单的数据处理方法
* @param key
*
A SelectionKey object associated with a channel determined by
the selector to be ready for reading. If the channel returns
an EOF condition, it is closed here, which automatically
invalidates the associated key. The selector will then
de-register the channel on the next select call.

一个选择器决定了和通道关联的SelectionKey object是准备读状态。如果通道返回EOF,通道将被关闭。
并且会自动使相关的key失效,选择器然后会在下一次的select call时取消掉通道的注册
*/
protected void readDataFromSocket(SelectionKey key) throws Exception
{
SocketChannel socketChannel = (SocketChannel) key.channel();
int count;
buffer.clear(); // 清空Buffer
// Loop while data is available; channel is nonblocking
//当可以读到数据时一直循环,通道为非阻塞
while ((count = socketChannel.read(buffer)) > 0)
{
buffer.flip(); // 将缓冲区置为可读
// Send the data; don't assume it goes all at once
//发送数据,不要期望能一次将数据发送完
while (buffer.hasRemaining())
{
socketChannel.write(buffer);
}
// WARNING: the above loop is evil. Because
// it's writing back to the same nonblocking
// channel it read the data from, this code can
// potentially spin in a busy loop. In real life
// you'd do something more useful than this.
//这里的循环是无意义的,具体按实际情况而定
buffer.clear(); // Empty buffer
}
if (count < 0)
{
// Close channel on EOF, invalidates the key
//读取结束后关闭通道,使key失效
socketChannel.close();
}
}
// ----------------------------------------------------------
/**
* Spew a greeting to the incoming client connection.
*
* @param channel
*
The newly connected SocketChannel to say hello to.
*/
private void sayHello(SocketChannel channel) throws Exception
{
buffer.clear();
buffer.put("Hi there!\r\n".getBytes());
buffer.flip();
channel.write(buffer);
}
}

参考资料

  1. Java NIO Tutorial
  2. java NIO详解