NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
下面介绍几个简单的概念:
1、缓冲区 Buffer
缓冲区,传输数据使用,本质是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。
Channel中读数据和写数据都只能通过Buffer传输。
具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。
2、通道Channel
我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。
所有的IO流在NIO中都是从Channel开始的,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中,是Buffer对象的唯一接口。
Channel主要分两大类:
SelectableChannel:用户网络读写
FileChannel:用于文件操作
这里介绍三种常用的用户网络读写Channel:
ServerSocketChannel和SocketChannel用于TCP协议,显然前者用于服务端。
DatagramChannel用于UDP协议。还有很多种Channel,可以查阅java api。
3、多路复用器Selector
选择器,它能检测一个或多个通道 (channel) 上的事件,并将事件分发出去。
使用一个 select 线程就能监听多个通道上的事件,并基于事件驱动触发相应的响应。而不需要为每个 channel 去分配一个线程。
selector.select()方法用于监听通道事件,此方法有阻塞和非阻塞两种。
4、SelectionKey
包含了事件的状态信息和时间对应的通道的绑定。
通道能够注册的事件有以下几种:
OP_ACCEPT,
OP_CONNECT
OP_READ
OP_WRITE
用register函数来为通道注册事件,如果对多个事件感兴趣,可以用位或(|)符号来注册多个事件。一个通道只注册一次,多次注册,最后一次有效。
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
5. read和write函数
因为是非阻塞socket,因此接收和发送函数需要加while循环来保证接收到完整的包。
read函数返回-1时,表明channel已到达字节流的末尾,对端已断开连接,需要重新连接。
write返回>=0的值。断开连接会抛出异常。
如果重新连接失败,节点失效,需要取消注册事件,断开连接,关闭channel。
read函数加while循环:
while(read() > 0){
…..
}
write函数加while循环:
while(sendbuffer.hasRemaining()) {
write(sendbuffer);
}
6. 关闭channel,断开连接,取消注册事件
selector.close();不关闭会不断消耗socket
channel.close();
selectionKey.cancel()
7. 示例代码
下面给出一个示例TCP服务的代码,其结构与linux中的epoll代码十分相似。
服务端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
/*标识数字*/
private int flag = 0;
/*缓冲区大小*/
private int BLOCK = 4096;
/*接受数据缓冲区*/
private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
private Selector selector;
public NIOServer(int port) throws IOException {
// 打开服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 检索与此通道关联的服务器套接字
ServerSocket serverSocket = serverSocketChannel.socket();
// 进行服务的绑定
serverSocket.bind(new InetSocketAddress(port));
// 通过open()方法找到Selector
selector = Selector.open();
// 注册到selector,等待连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start----8888:");
}
// 监听
private void listen() throws IOException {
while (true) {
// 选择一组键,并且相应的通道已经打开
selector.select();
// 返回此选择器的已选择键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleKey(selectionKey);
}
}
}
// 处理请求
private void handleKey(SelectionKey selectionKey) throws IOException {
// 接受请求
ServerSocketChannel server = null;
SocketChannel client = null;
String receiveText;
String sendText;
int count=0;
// 测试此键的通道是否已准备好接受新的套接字连接。
if (selectionKey.isAcceptable()) {
// 返回为之创建此键的通道。
server = (ServerSocketChannel) selectionKey.channel();
// 接受到此通道套接字的连接。
// 此方法返回的套接字通道(如果有)将处于阻塞模式。
client = server.accept();
// 配置为非阻塞
client.configureBlocking(false);
// 注册到selector,等待连接
client.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count = client.read(receivebuffer);
if (count > 0) {
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("服务器端接受客户端数据--:"+receiveText);
client.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
//将缓冲区清空以备下次写入
sendbuffer.clear();
// 返回为之创建此键的通道。
client = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向缓冲区中输入数据
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
//输出到通道
client.write(sendbuffer);
System.out.println("服务器端向客户端发送数据--:"+sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
客户端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOClient {
/*标识数字*/
private static int flag = 0;
/*缓冲区大小*/
private static int BLOCK = 4096;
/*接受数据缓冲区*/
private static ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
/*发送数据缓冲区*/
private static ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);
/*服务器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 8888);
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
// 打开socket通道
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞方式
socketChannel.configureBlocking(false);
// 打开选择器
Selector selector = Selector.open();
// 注册连接服务端socket动作
socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
// 连接
socketChannel.connect(SERVER_ADDRESS);
// 分配缓冲区大小内存
String receiveText;
String sendText;
int count=0;
while (true) {
//选择一组键,其相应的通道已为 I/O 操作准备就绪。
//此方法执行处于阻塞模式的选择操作。
selector.select();
//返回此选择器的已选择键集。
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//System.out.println("selectedKeys size:" + selectionKeys.size());
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
socketChannel = (SocketChannel) selectionKey.channel();
if (selectionKey.isConnectable()) {
// 判断此通道上是否正在进行连接操作。
// 完成套接字通道的连接过程。
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
System.out.println("client success connected");
sendbuffer.clear();
sendbuffer.put("Hello,Server".getBytes());
sendbuffer.flip();
socketChannel.write(sendbuffer);
}
socketChannel.register(selector, SelectionKey.OP_WRITE);
} else if (selectionKey.isReadable()) {
//将缓冲区清空以备下次读取
receivebuffer.clear();
//读取服务器发送来的数据到缓冲区中
count=socketChannel.read(receivebuffer);
if(count>0){
receiveText = new String( receivebuffer.array(),0,count);
System.out.println("客户端接受服务器端数据--:"+receiveText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendbuffer.clear();
sendText = "message from client--" + (flag++);
sendbuffer.put(sendText.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
sendbuffer.flip();
while(sendbuffer.hasRemaining()) {
socketChannel.write(sendbuffer);
}
System.out.println("客户端向服务器端发送数据--:"+sendText);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
}
}
}
详情参考java api文档:http://docs.oracle.com/javase/8/docs/api/
810

被折叠的 条评论
为什么被折叠?



