自己对着源码敲一遍练习,写上注释。发现NIO编程难度好高啊。。虽然很复杂,但是NIO编程的有点还是很多:
1、客户端发起的连接操作是异步的,可以通过在多路复用器注册OP_CONNECTION等待后续结果,不需要像BIO的客户端一样被同步阻塞。
2、SocketChannel的读写操作都是异步的,如果没有可读写的数据它不会同步等待,直接返回,这样I/O通信模型就可以处理其他的链路,不需要同步等待这个链路可用。
3、线程模型的优化:由于JDK的Selector在Linux等主流操作系统上通过epoll实现,没有连接句柄的限制,那么Selector线程可以同时处理成千上万个客户端连接,而且性能不会随着客户端的增加而线性下降。所以它非常适合做高性能、高负载的网络服务器。
TimeClient:
package nio; public class TimeClient {
public static void main(String args[]){
int port = 8080;
if(args != null && args.length > 0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
new Thread(new TimeClientHandle("120.0.0.1",port),"TimeClient-001").start();
}
}
TimeClientHandler:
package nio; 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 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);
}
} public void run() {
//发送请求连接
try{
doConnect();
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
while(!stop){
try{
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
//当有就绪的Channel时,执行handleInput(key)方法
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(Exception e){
e.printStackTrace();
System.exit(1);
}
} //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if(selector != null){
try{
selector.close();
}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()){
sc.register(selector, SelectionKey.OP_READ);
}else{
System.exit(1);
}
} if(key.isReadable()){
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{
; //读到0字节,忽略
}
}
}
} private void doConnect() throws IOException{
//如果直接连接成功,则注册到多路复用器上,发送请求信息,读应答
if(socketChannel.connect(new InetSocketAddress(host,port))){
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
}else{
//说明服务器没有返回TCP祸首应答消息,但这并不代表连接失败,当服务器返回TCP syn-ack消息后,Selector就能够轮训这个SocketChannel处于连接就绪状态
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
} private void doWrite(SocketChannel sc) throws IOException{
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.");
}
} }
TimeServer:
package nio; import java.io.IOException; public class TimeServer { public static void main(String[] args) throws IOException{
int port = 8080;
if(args != null && args.length >0){
try{
port = Integer.valueOf(args[0]);
}catch(NumberFormatException e){
//采用默认值
}
}
//多路复用类,是一个独立的线程,负责轮训多路复用器Selctor,处理多个客户端的并发接入。
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
new Thread(timeServer,"NIO-MultiplexerTimeServer-001").start();
}
}
MultiplexerTimeServer:
package nio; 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.Iterator;
import java.util.Set; public class MultiplexerTimeServer implements Runnable { private Selector selector; private ServerSocketChannel servChannel; private volatile boolean stop; public MultiplexerTimeServer(int port){
try{ selector = Selector.open();
servChannel.configureBlocking(false);
//将ServerSocketChannel 设置为异步非阻塞,backlog设置为1024
servChannel.socket().bind(new InetSocketAddress(port),1024);
//将ServerSocket Channel注册到Selector,监听SelectionKey.OP_ACCEPT操作位,如果初始化失败,则退出
servChannel.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;
} public void run() {
while(!stop){
try{
//遍历时间设置1秒,每隔一秒唤醒一次,当有处于就绪状态的Channel时,selector将返回就绪状态的Channel的SelectionKey集合
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
//通过对就绪状态的Channel集合进行迭代,可以进行网络的异步读写操作
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(Throwable t){
t.printStackTrace();
}
} //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
if(selector != null){
try{
selector.close();
}catch(IOException e){
e.printStackTrace();
}
}
} //处理新接入的请求消息
private void handleInput(SelectionKey key) throws IOException{
if(key.isValid()){ //根据SelectionKey的操作位进行判断即可获知网络事件的类型,通过accept接收客户端的连接请求并创建SocketChannel实例,完成上述操作相当于
//完成了TCP的三次握手,TCP物理链路正式建立
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//Add the new connection tothe selector
sc.register(selector, SelectionKey.OP_READ);
} if(key.isReadable()){
//Read the data SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if(readBytes > 0){
//将缓冲区当前的limit设置为position,position设置为0,用于后续对缓冲区的读取操作
readBuffer.flip();
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 java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(sc,currentTime);
}else if(readBytes < 0){
//对端链路关闭
key.cancel();
sc.close();
}else{
; //读到0字节,忽略
}
}
}
} private void doWrite(SocketChannel channel,String response) throws IOException{
if(response != null && response.trim().length() >0){
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}