二、NIO 的非阻塞式网络通信
传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行 IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞 IO 的空闲时间用于在其他通道上执行 IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。
选择器(Selector)
选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。
* 一、使用NIO 完成网络通信的三个核心:
*
* 1、通道(Channel):负责连接
* java.nio.channels.Channel 接口:
* |--SelectableChannel
* |--SocketChannel
* |--ServerSocketChannel
* |--DatagramChannel
*
* |--Pipe.SinkChannel
* |--Pipe.SourceChannel
*
* 2.缓冲区(Buffer):负责数据的存取
*
* 3.选择器(Selector):是 SelectableChannel 的多路复用器。用于监控SelectableChannel的IO状况
阻塞式NIO的网络通信--无返回信息
/**
* @author qiz
* 客户端
*/
public class NIO2 {
public static void main(String[] args) throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地图片发送到服务端
FileChannel inChannel = FileChannel.open(Paths.get("E:\\IdeaProjects\\boot_mq\\boot_mq_producer\\src\\main\\java\\com\\qiz\\nio\\1.jpg"), StandardOpenOption.READ);
while (inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
inChannel.close();
sChannel.close();
}
}
/**
* @author qiz
* 服务端
*/
public class NIO21 {
public static void main(String[] args) throws IOException {
//获取通道
ServerSocketChannel sschannel = ServerSocketChannel.open();
//绑定连接
sschannel.bind(new InetSocketAddress(9898));
//获取客户端通道
SocketChannel schannel = sschannel.accept();
//接收客户端的数据,保存到本地
FileChannel outchannel = FileChannel.open(Paths.get("E:\\IdeaProjects\\boot_mq\\boot_mq_producer\\src\\main\\java\\com\\qiz\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer buf = ByteBuffer.allocate(1024);
while (schannel.read(buf) != -1){
buf.flip();
outchannel.write(buf);
buf.clear();
}
schannel.close();
outchannel.close();
sschannel.close();
}
}
阻塞式NIO的网络通信--有返回信息
/**
* @author qiz
* 客户端
*/
public class NIO2 {
public static void main(String[] args) throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地图片发送到服务端
FileChannel inChannel = FileChannel.open(Paths.get("E:\\IdeaProjects\\boot_mq\\boot_mq_producer\\src\\main\\java\\com\\qiz\\nio\\1.jpg"), StandardOpenOption.READ);
while (inChannel.read(buf) != -1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
//服务端不知道客户端是否发送数据完毕,所以没发送数据给客户端,客户端也就阻塞了。
sChannel.shutdownOutput();
//接收服务端的反馈
int len = 0;
while ((len=sChannel.read(buf)) != -1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
inChannel.close();
sChannel.close();
}
}
/**
* @author qiz
* 服务端
*/
public class NIO21 {
public static void main(String[] args) throws IOException {
//获取通道
ServerSocketChannel sschannel = ServerSocketChannel.open();
//绑定连接
sschannel.bind(new InetSocketAddress(9898));
//获取客户端通道
SocketChannel schannel = sschannel.accept();
//接收客户端的数据,保存到本地
FileChannel outchannel = FileChannel.open(Paths.get("E:\\IdeaProjects\\boot_mq\\boot_mq_producer\\src\\main\\java\\com\\qiz\\nio\\2.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer buf = ByteBuffer.allocate(1024);
while (schannel.read(buf) != -1){
buf.flip();
outchannel.write(buf);
buf.clear();
}
//发送反馈
buf.put("服务端接收数据成功!".getBytes());
buf.flip();
schannel.write(buf);
schannel.close();
outchannel.close();
sschannel.close();
}
}
SelectionKey
当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。
可以监听的事件类型(用 可使用 SelectionKey 的四个常量 表示):
读 : SelectionKey.OP_READ (1)
写 : SelectionKey.OP_WRITE (4)
连接 : SelectionKey.OP_CONNECT (8)
接收 : SelectionKey.OP_ACCEPT (16)
若注册时不止监听一个事件,则可以使用“位或”操作符连接。
SelectionKey:表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。
这里写图片描述Selector 的常用方法
非阻塞式网络通信
/**
* @author qiz
*/
public class NonBlockingNIO {
public static void main(String[] args) throws IOException {
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换非阻塞通道
sChannel.configureBlocking(false);
//分配指定大小的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//发送数据给服务端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String str = scanner.next();
byteBuffer.put(str.toString().getBytes());
byteBuffer.flip();
sChannel.write(byteBuffer);
byteBuffer.clear();
}
sChannel.close();
}
}
/**
* @author qiz
*/
public class NonBlockingNIO2 {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.bind(new InetSocketAddress(9898));
//获取选择器
Selector selector = Selector.open();
//将通道注册到选择器上
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮询式的获取选择器上已经“准备就绪”的事件
while ((selector.select() > 0)){
//获取当前选择器中所有注册的选择键(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//迭代获取
while (iterator.hasNext()){
//获取准备就绪的事件
SelectionKey next = iterator.next();
//判断具体是什么事件准备就绪
if (next.isAcceptable()){
//若接收就绪,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//切换非阻塞模式
sChannel.configureBlocking(false);
//将该通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if (next.isReadable()){
//获取当前选择器上读就绪状态的通道
SocketChannel schannel = (SocketChannel) next.channel();
//读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = schannel.read(byteBuffer)) > 0) {
byteBuffer.flip();
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
}
//取消选择器
iterator.remove();
}
}
}
}
网友评论