NIO
成 员:
- 通道( Channel)
- 缓冲区(buffer) 缓冲区本质上是一个内存块
- 选择器 (selector) 【多路复用】
通道 ->注册(事件) ->选择器 ;
通道(数据) <-写入 <- 缓存区 -> 读-> 通道(数据)
选择器 -> 监听 -> 通道注册 ((数据))
读写通常步骤:
1. 将数据写入缓冲区
2. 叫buffer.flip()
3. 读取缓冲区中的数据
4. 呼叫或buffer.clear() //重置缓冲区 buffer.compact() //重置已读数据,未读数据保留
文件方式NIO:
*通过文件获取通道,然后操作
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//缓冲区反转,将读模式变为写模式
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
//重置缓冲区,将标记位变为0;
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
SocketChannel:
打开套接字通道
以下是如何打开 :SocketChannel
SocketChannel socketChannel = SocketChannel.open(); //打开通道
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));//设置连接的ip 和 端口
socketChannel.configureBlocking(false);//设置非阻塞
关闭套接字通道
通过调用 方法关闭使用后。以下是如何完成:SocketChannelSocketChannel.close()
socketChannel.close();
从套接字通道阅读
若要从 读取数据,请调用其中一种方法。下面是一个示例:SocketChannelread()
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
ServerSocketChannel:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999)); //设置监听ip 和 端口
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
if(socketChannel != null){
//do something with socketChannel...
}
}
ServerSocketChannel 与Selector :
private ServerSocketChannel ssc;
private ServerSocket serverSocket;
private Selector selector;
(){
//打开socket通道
ssc=ServerSocketChannel.open();
//获取通道关联的Socket对象
serverSocket = ssc.socket();
//设置非阻塞
ssc.configureBlocking(false);
//绑定端口
serverSocket.bind(new InetSocketAddress(NioConfig.NIO_HOST,NioConfig.NIO_PORT));//监听ip和端口
//设置选择器
selector=Selector.open();
// int selectkeys=(SelectionKey.OP_ACCEPT);
//获取到所有注册到选择器selector 中的 SelectionKey 然后可以获取 socketChannel
// selector.keys();
//通道绑定选择器
ssc.register(selector,SELECT_KEYS);//SELECT_KEYS =SelectionKey.OP_ACCEPT; //首先要监听连接事件才能监听其他事件
// ssc.register(selector,SelectionKey.OP_READ);
//获取选择器就绪的事件
while (true){
//if(selector.select() > -1)
if(selector.select(2000) == 0){
//监听2s 无监听事件发生
continue;
}
//选择器事件处理
selectHandle();
}
}
/**
* 选择器事件处理
* @throws IOException
*/
private void selectHandle() throws IOException {
//获取所有就绪的事件的迭代器
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
//循环还有没有下一个就绪的事件
while (selectionKeys.hasNext()){
//获取下一个事件1 ---selectionKey可以获取到SocketChannel
SelectionKey selectionKey=selectionKeys.next();
//判断该事件的类型-
if (selectionKey.isAcceptable()){
//可连接事件处理
acceptableHandle(selectionKey);
}else if (selectionKey.isReadable()){
//可读事件处理
readableHandle(selectionKey);
}else if (selectionKey.isConnectable()){
}else if (selectionKey.isWritable()){
}
//删除调用 next方法 返回的元素; 防止重复执行相同事件
//建议加上,否则有可能事件处理事件
selectionKeys.remove();
}
}
/**
* 可连接事件处理
* @param selectionKey
* @return
* @throws IOException
*/
private boolean acceptableHandle(SelectionKey selectionKey) throws IOException {
SocketChannel sc=ssc.accept();
if (sc == null){
return false;
}
sc.configureBlocking(false); //一定要设置非阻塞,不然可能报错
// int op =SelectionKey.OP_READ | SelectionKey.OP_WRITE; //可以同时设置监听多个事件
sc.register(selector,SelectionKey.OP_READ); //监听读 事件
return true;
}
/**
* 可读事件处理
* @param selectionKey
* @throws IOException
*/
private void readableHandle(SelectionKey selectionKey) throws IOException {
SocketChannel sc=(SocketChannel) selectionKey.channel();
if (sc == null){
selectionKey.cancel(); //取消Selector 注册
}
//定义1024缓冲区
ByteBuffer bbut=ByteBuffer.allocate(1024);
// sc1=sc;
int len=0;
List<byte[]> listbs=new ArrayList<>();
try{
while ((len=sc.read(bbut))>0){
//切换模式
// bbut.flip();
//附带标识,根据标识取数据
//将ByteBuffer字节数据 转成 byte数组存入 list
listbs.add(ConvertUtils.bytebufferByteArray(bbut));
bbut.clear();//重置byteBuffer
}
}catch (IOException e){
selectionKey.cancel();//取消注册
sc.close(); //断开连接
return;
}
}
SokcetChannel与 Selector:
**
* 步骤
* 打开SocketChannel通道
* 设置通道为非阻塞
* -获取缓冲区ByteBuffer
* -为缓冲区添加内容
* -更换缓冲区状态
* -将缓冲区推入SocketChannel通道
* -清空缓冲器
* 设置选择器
* 通道绑定选择器和监听事件类型
* 获取所有就绪事件
* 处理所有就绪事件
*/
public static SocketChannel ssc;
(){
//打开socket通道
ssc=SocketChannel.open();
ssc.configureBlocking(false);
// 非阻塞连接,如果立即连接成功返回true
if (!ssc.connect(new InetSocketAddress(NioConfig.NIO_HOST,NioConfig.NIO_PORT))){
while (!ssc.finishConnect()){ //如果 非 已连接
//暂未连接,非阻塞,循环
}
}
//连接成功之后
ReturnDate returnDate=new ReturnDate();
returnDate.setId(-1000);
byte[] bytes = iSerializedObject.toByteArray(returnDate);
ByteBuffer bb=ByteBuffer.allocate(bytes.length);
bb.put(bytes);
bb.flip();//byteBuffer 读写反转 变为写
ssc.write(bb);
bb.clear(); //重置
bb=null;
bytes=null;
returnDate=null;
//设置选择器
Selector selector=Selector.open();
int selectkeys=(SelectionKey.OP_READ);//设置监听事件
//通道绑定选择器
ssc.register(selector,selectkeys);
//是否有就绪的事件
while (true) {
if(selector.select(2000) == 0){//监听2s 没有事件
//可执行其他操作
continue;
}
//监听到事件
//获取所有就绪的事件的迭代器
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
//循环还有没有下一个就绪的事件
while (selectionKeys.hasNext()) {
//获取下一个事件1
SelectionKey selectionKey = selectionKeys.next();
//读事件
if (selectionKey.isReadable()) {
SocketChannel sc = (SocketChannel) selectionKey.channel();
ByteBuffer bbut = ByteBuffer.allocate(1024);
// ByteBuffer bbut1 = ByteBuffer.allocate(1024);
int len = 0;
List<byte[]> listbs=new ArrayList<>(8);
while ((len = sc.read(bbut)) > 0) {
// 切换模式
// bbut.flip();
// bbut1.flip();
listbs.add(ConvertUtils.bytebufferByteArray(bbut));
// System.out.println("---");
bbut.clear();
// selector.close();
}
byte[] bs=ConvertUtils.mergeByte(listbs);
if (bs == null){
continue;
}
//接收外网消息
ReturnDate returnDate1= (ReturnDate) iSerializedObject.ByteArrayFrom(bs, ReturnDate.class);
//交给其他线程处理
TheadPoolFactory.t.execute(new DisposeTask(returnDate1));
//System.out.println("收到外网消息:"+returnDate1.getId()+";"+returnDate1.getMessage());
}
//
}
}
}