概述:
NIO主要包括两个部分:java.nio.channels包介绍了Selector和Channel抽象,java.nio包介绍了Buffer抽象。这都是一些高级的特性,有许多微妙的使用细节,它与socket类似,但是它主要区别,socket是乡村公路,NIO就是高速公路,socket容易阻塞,而NIO可以设置不阻塞。NIO更加充分利用系统的资源。
术语:
Selector:选择渠道
Channel: (一个与设备连接的实例,与socket不同通常它调用静态的方法进行实例化)Channel直接使用的不是流,而是缓存区,Channel可以配置非阻塞
Buffer:缓存IO流 buffer是具体基本类型Buffer ,通过allocate进行分配,也可以包装现有的数组
1.一个简单例子package com.tcp.ip.chapter5;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class TCPEchoClientNonblocking {
public static void main(String args[]) throws Exception {
if((args.length < 2) || (args.length > 3)) {
throw new IllegalArgumentException("Parameters : <Server> <Word> [<Port>]");
}
String server = args[0];
byte[] argument = args[1].getBytes();
int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;
//socket 变成 SocketChannel ,它静态方法实例化
SocketChannel clntChan = SocketChannel.open();
//配置非阻塞快
clntChan.configureBlocking(false);
if(!clntChan.connect(new InetSocketAddress(server, servPort))) {
//一直在连接,知道结束
while (!clntChan.finishConnect()) {
System.out.println(".");
}
}
//包装流
ByteBuffer writeBuf = ByteBuffer.wrap(argument);
ByteBuffer readBuf = ByteBuffer.allocate(argument.length);
int totalBytesRcvd = 0;
int bytesRcvd;
//一直读取数据
while (totalBytesRcvd < argument.length) {
//如果写缓存流有数据,继续写
if(writeBuf.hasRemaining()) {
clntChan.write(writeBuf);
}
//读取到-1表示最后的字符
if ((bytesRcvd = clntChan.read(readBuf)) == -1) {
throw new SocketException("Connection closed prematurely");
}
totalBytesRcvd += bytesRcvd;
System.out.println(".");
}
//打印读取数据
System.out.println("Received:" + new String (readBuf.array(), 0, totalBytesRcvd));
clntChan.close();
}
}
总结:对比学习,来找茬
- 1.Socket 变成 SocketChannel ,而且不是new,是调用静态方法 open() ,(定位是高速公路)
- 2.SocketChannel可以设置是否阻塞 调用方法 configureBlocking(false), Socket不可以
- 3.对于传输的数据都是缓存,写缓存通过包装byte[] ,调用方法wrap(目标byte[]) (wrap本身就是包装,打包) ,而socket是通过getOutStream() 直接操作流
- 4.对于读缓存采用allocate(容器大小)(allocate 分配的意思) 就像你那个盒子来装东西,而socket是通过getInputStream
- 5.对于缓存区是否还有数据通过方法是 hasRemaining()
- 6.将缓存变成byte[] 调用 缓存对象.array()
服务端协议
package com.tcp.ip.chapter5;
import java.io.IOException;
import java.nio.channels.SelectionKey;
public interface TCPProtocol {
/**处理连接
* @param key
* @throws IOException
*/
void handleAccept(SelectionKey key) throws IOException;
/**
* 处理读
* @param key
* @throws IOException
*/
void handleRead(SelectionKey key) throws IOException;
/**
* 处理写
* @param key
* @throws IOException
*/
void handleWrite(SelectionKey key) throws IOException;
}
总结:服务主要的干的活分一下类,一个请求,一个读,一个写
package com.tcp.ip.chapter5;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class EchoSelectorProtocol implements TCPProtocol {
//缓存大小
private int bufSize;
public EchoSelectorProtocol(int bufSize) {
this.bufSize = bufSize;
}
public void handleAccept(SelectionKey key) throws IOException {
SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
//非阻塞注册
clntChan.configureBlocking(false);
//
clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
}
public void handleRead(SelectionKey key) throws IOException {
SocketChannel clntChan = (SocketChannel) key.channel();
ByteBuffer buf = (ByteBuffer) key.attachment();
long bytesRead = clntChan.read(buf);
if(bytesRead == -1) {
clntChan.close();
} else if (bytesRead >0) {
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
}
public void handleWrite(SelectionKey key) throws IOException {
//取回数据
ByteBuffer buf = (ByteBuffer)key.attachment();
//准备缓存区写
buf.flip();
SocketChannel clntChan = (SocketChannel)key.channel();
clntChan.write(buf);
//完全写完啦?
if(!buf.hasRemaining()) {
key.interestOps(SelectionKey.OP_READ);
}
buf.compact();
}
}
总结:具体实现 ,是不是类似医院操作
1.handleAccept() 处理请求,也就相当于医院挂号,将你的信息录入到系统的中,完成注册功能
2.handleRead() 读取数据 ,这时候你到对于科室去看医院,医生根据你描述的症状进行判断
3.handleWrite() 写数据, 这时候医生根据你的描述,确诊什么?开什么?注意什么?,写病历本上,你就下去抓药
4.key.interestOps对什么感兴趣,SelectionKey.OP_READ 读 SelectKey.OP_WRITE。
5.key.attachment() 获取缓存数据
package com.tcp.ip.chapter5;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
public class TCPServerSelector {
private static final int BUFSIZE = 256;
private static final int TIMEOUT = 3000;
public static void main(String[] args) throws IOException{
if(args.length < 1){
throw new IllegalArgumentException("Parameter(s): <Port>...");
}
Selector selector = Selector.open();
for (String arg : args){
ServerSocketChannel listnChannel = ServerSocketChannel.open();
//好像可以绑定多个端口
listnChannel.socket().bind(new InetSocketAddress(Integer.parseInt(arg)));
//非阻塞注册
listnChannel.configureBlocking(false);
//返回的key是忽略的
listnChannel.register(selector, SelectionKey.OP_ACCEPT);
TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE);
//一直运行
while(true) {
//等待一些渠道
if(selector.select(TIMEOUT) == 0) {
System.out.println(".");
continue;
}
//得到迭代IO 就是所有请求的该端口度应该经过selector管理,类似存在map中k-v键值对
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
SelectionKey key = keyIter.next();
if(key.isAcceptable()) {
protocol.handleAccept(key);
}
if(key.isReadable()){
protocol.handleRead(key);
}
if(key.isValid() && key.isWritable()){
protocol.handleWrite(key);
}
keyIter.remove();
}
}
}
}
}
总结:
1.一个channel绑定一个端口
2.一个channel注册到selector上,用selector进行管理
3.selector根据当前状态对通道进行相应的处理
自己理解不够,如果什么误导的地方,深感歉意,也欢迎指出。