NIO简介
NIO,有人称之为New IO 因为相对于之前是新增的,这也是它的官方改叫法,又因NIO的目标是让java支持非阻塞IO,故也有人称之为Non-block/IO,他在标准java代码中提供了告诉的,面向块的I/o.通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO不用使用本机代码就可以利用低级优化.
NIO进行服务端开发步骤:
(1)创建ServerSocketChannel,配置它为非阻塞模式;
(2)绑定监听,配置tcp参数,例如backlog大小
(3)创建一个独立的I/o线程,用户轮询多路服用器Selector
(4)创建Selector,将之前的ServerSocketChannel注册到Selector上,监听SelectionKey.ACCEPT
(5)启动IO线程,在循环体中执行Selector.select()方法,轮询就绪的Channel
(6)当轮询到处于就绪状态的Channel时,需要对器进行判断,如果是OP_ACCEPT状态,说明是新的客户端接入的,则调用ServerSocketChannel.accept()方法接受新的客户端
(7)设置新接入的客户端链路SocketChannel为非阻塞模式,配置其他的一些TCP参数
(8)将SocketChannel注册到Selector,监听OP_READ操作位
(9)如果轮询Channel为OP_READ,则说明socketChannel中有新的就绪的数据包需要读取,则构造ByteBuffer对象,读取数据包
(10)如果轮询的Channel为OP_WRITE,说明还有数据没有发送完成,需要继续发送.
package javanio;
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
/**
* Created by suhao on 14-11-11.
*/
public class MultiplexerTimeServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean stop;
/**
* 初始化多路复用器,绑定监听端口
*
* @param port
*/
public MultiplexerTimeServer(int port) {
try {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("The time Server is start in port:" + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
System.out.println("TimeServer");
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
System.out.println("Time Server HandleInput");
if (key.isValid()) {
if (key.isAcceptable()) {
System.out.println("Time Server isAcceptable");
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
}
if (key.isReadable()) {
System.out.println("Time Server Read");
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();//切换到读状态 limit-->position position-->0
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(sc, currentTime);
} else if (readBytes < 0) {
key.cancel();
sc.close();
}
} else {
;
}
}
private void doWrite(SocketChannel sc, String response) throws IOException {
System.out.println("Time Server Write");
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.flip();
sc.write(writeBuffer);
}
}
}
package javanio;
/**
* Created by suhao on 14-11-11.
*/
public class TimeServer {
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (Exception e) {
//defalult
}
}
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
new Thread(timeServer,"NIO-MultiplexerTimeServer-001").start();
}
}
TimeClient开发
package javanio;
/**
* Created by suhao on 14-11-11.
*/
public class TimeClient {
public static void main(String[] args) {
int port = 8080;
try {
if (args != null && args.length > 0) {
port = Integer.parseInt(args[0]);
}
} catch (Exception e) {
e.printStackTrace();
//default
}
new Thread(new TimeClientHandle("127.0.0.1", port), "TimeClient-001").start();
}
}
package javanio;
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;
/**
* Created by suhao on 14-11-11.
*/
public class TimeClientHandle implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private volatile boolean stop;
public TimeClientHandle(String host, int port) {
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
}
while (!stop) {
try {
selector.select(1000);
System.out.println("TimeClient");
Set<SelectionKey> selectedKeys = selector.selectedKeys();
System.out.println("************************" + selectedKeys);
Iterator<SelectionKey> iter = selectedKeys.iterator();
SelectionKey key = null;
while (iter.hasNext()) {
key = iter.next();
iter.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.out.println(1);
}
}
if (selector != null) {
try {
selector.close();//多路复用器关闭之后,所有在上面注册的Channel和Pipe等资源都会自动去注册并关闭,所以不需要重复释放资源
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
System.out.println("Time Clinet finish conncet");
SelectionKey register = sc.register(selector, SelectionKey.OP_READ);//没有注册成功
System.out.println("**************register*****************:" + register);
doWrite(sc);
} else
System.exit(1);
}
System.out.println(key.isReadable());
if (key.isReadable()) {
System.out.println("Time Client Read");
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "utf-8");
System.out.println("Now is : " + body);
this.stop = true;
} else if (readBytes < 0) {
key.cancel();
sc.close();
} else {
;//没有读到任何字节则不处理
}
}
}
}
private void doWrite(SocketChannel sc) throws IOException {//存在半包写的问题
System.out.println("Time Client Write");
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
System.out.println("Send order 2 server succeed");
}
}
private void doConnect() throws IOException {
System.out.println("Time Client Connect");
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
}
以上程序作为一个入门的小例子,没有加入处理"半包"的情况,由此可见JAVA 原生NIO编程十分的繁琐.下一步学习NIO框架Netty