Java NIO 学习总结(二)
主要内容:
1.阻塞与非阻塞 1.阻塞与非阻塞
2.DatagramChannel
一、阻塞与非阻塞
阻塞:客户端请求服务端时,读写请求不能及时处理时,服务端处理线程与客户端请求线程就会处于占用(等待)的阻塞状态;
非阻塞:NIO提出的选择器(类似于中间异步)
1.先把客户端连Server的Channel注册到选择器上,选择器在Server与Client之间,声明在服务端
2.通道上存在IO状态(包括:读、写、连接、接收数据)可以供选择器监控
3.当通道的某个状态就绪时,选择器才会将客户端请求分配到Server的一个或者多个线程上
示例:
1.阻塞式NIO(new IO)(无选择器)实现文件传输
package com.jupiter.test;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* @author Jupiter
* @date 2019/3/6-22:05
* @description 测试NIO的阻塞方式完成数据文件的传输
*/
public class TestBlockingNIOChannel {
/**
* 1.使用NIO完成网络通信的三个核心
* |--通道:负责连接
* |--缓冲区:负责传输数据
* |--选择器:是SelectableChannel的多路复用器,用于监控SelectableChannel的状态
*/
@Test
public void client() throws IOException {
//1.建立连接服务端的通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898 ));
//获取本地文件
FileChannel inChannel = FileChannel.open(Paths.get("o:/1.txt"), StandardOpenOption.READ);
//inChannel.transferTo(0, inChannel.size(), sChannel); //利用直接缓冲区传输文件,可以实现,这样下面的步骤2可以省略
//2.建立负责数据传输的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(inChannel.read(buffer) != -1){
buffer.flip();
sChannel.write(buffer);
buffer.clear();
}
//3.关闭通道
inChannel.close();
sChannel.close();
}
@Test
public void server() throws IOException {
//1.建立服务端通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
//2.绑定链接
ssChannel.bind(new InetSocketAddress(9898));
//2.建立接收客户端数据的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
FileChannel outChannel = FileChannel.open(Paths.get("2.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//3.接收客户端的socketChannel,如果客户端一直没有请求,那么该线程一直处于阻塞状态!
SocketChannel acceptChannel = ssChannel.accept();
while(acceptChannel.read(buffer)!=-1){
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
acceptChannel.close();
outChannel.close();
ssChannel.close();
}
}
2.非阻塞式NIO实现聊天室
package com.juwenzhe.nio.test;
import org.junit.Test;
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.time.LocalTime;
import java.util.Iterator;
import java.util.Scanner;
/**
* @author Jupiter
* @date 2019/3/6-22:49
* @description 用非阻塞方式实现聊天室
*/
public class TestNonBlockingChannel {
@Test
public void client() throws IOException {
//1.创建客户端连接服务端的通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
sChannel.configureBlocking(false); //设置通道为非阻塞模式
//2.创建传输数据的buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
//可以判断输入的字符长度
String src = sc.next();
buffer.put((LocalTime.now().toString()+"\n"+src).getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();//不写的话,缓冲区一直被上一次写入占着,相当于flush,不能进行下次写入
}
sChannel.close();
sc.close();
}
@Test
public void server() throws IOException {
//1.建立服务端连接通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.bind(new InetSocketAddress(9898));
//2.切换非阻塞模式
ssChannel.configureBlocking(false);
//3.创建选择器
Selector selector = Selector.open();
//4.注册监听服务端ssChannel的接收客户端Socket事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//一直轮询监听客户端连接服务端的操作,selector.select()会阻塞,原理->参考文献
while(selector.select()>0){
//获取每次轮询得到的选择键(监听到的事件)
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey next = it.next();
//判断具体是什么事件准备就绪
if (next.isAcceptable()){
//如果是“准备接收”事件准备就绪
//建立接收客户端请求的Channel
SocketChannel sChannel = ssChannel.accept();
//切换到非阻塞模式
sChannel.configureBlocking(false);
//接下来将该通道注册到选择器上,监听socketChannel的读就绪状态
sChannel.register(selector,SelectionKey.OP_READ);
}else if(next.isReadable()){
//建立该选择键对应的通道与缓冲区
SocketChannel sChannel = (SocketChannel) next.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while(sChannel.read(buffer)>0){
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
buffer.clear();
}
}
//清除单次轮询监听到的客户端选择键
it.remove();
}
}
}
}
二、DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。
package com.juwenzhe.nio.test;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.time.LocalTime;
import java.util.Iterator;
import java.util.Scanner;
import org.junit.Test;
/**
* @author Jupiter
* @devDate 2019年3月12日
* @description 使用基于UDP的非阻塞NIO网络通信聊天室
*/
public class TestNonBolckingNIO {
@Test
public void client() throws Exception{
//1.创建通道
DatagramChannel dgc = DatagramChannel.open();
// 通道开启非阻塞模式
dgc.configureBlocking(false);
//2.创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
//向缓冲区写入数据
String src = sc.nextLine();
buffer.put((LocalTime.now()+"\n"+src).getBytes());
//切换缓冲区为读模式
buffer.flip();
//通过UDP管道写出数据
dgc.send(buffer, new InetSocketAddress("127.0.0.1", 9898));//与TCP的传输方式不同,TCP:连接的write,sChannel.write(buffer);
buffer.clear();
}
dgc.close();
sc.close();
}
@Test
public void server() throws Exception{
//1.创建通道
DatagramChannel dgc = DatagramChannel.open();
// 服务端绑定端口号
dgc.bind(new InetSocketAddress(9898));
// 管道切换为非阻塞模式
dgc.configureBlocking(false);
//2.创建选择器
Selector selector = Selector.open();
//3.服务端管道注册监听动作到选择器
dgc.register(selector, SelectionKey.OP_READ);
while(selector.select()>0){
//4.获取监听到的客户端动作
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
//5.现只判断选择器上的监听到的动作,UDP面向无连接,与TCP的传输方式不同,TCP:连接先accept建立连接,再继续监听其他动作
SelectionKey sk = iter.next();
if(sk.isReadable()){
//6.如果有客户端传来数据,直接打印数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
//向buffer中传入数据
dgc.receive(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
buffer.clear();
}
}
iter.remove();
}
}
}