NIO的三大核心对象:
缓冲区(Buffer)、选择器(Selector)、通道(Channel)
NIO 的工作原理:
- 由一个专门的线程来处理所有的 IO 事件,并负责分发。
- 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
- 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
一、服务端实现
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;
/**
* @Date: 2020/12/24
* 说明:
*/
public class ServerNIODemo {
// 端口号
private static final int PORT = 8090;
private static final int BUFFER_SIZE = 1024;
private Selector selector;
public ServerNIODemo(){
try {
// 初始化服务器
System.out.println("初始化服务器");
// 创建ServerSocketChannel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置通道为非阻塞式
serverSocketChannel.configureBlocking(false);
// 创建选择器
this.selector = Selector.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(PORT));
// 注册接收事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (Exception e){
e.printStackTrace();
}
}
public void listen() {
try {
while (true) {
if (selector.select() == 0) { continue; }
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 遍历
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 获得该通道后,就将其从迭代器中删除
iterator.remove();
//处理逻辑
process(key);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void process(SelectionKey key) {
try{
if(key.isAcceptable()){
accept(selector, key);
}
if(key.isReadable()){
read(key);
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 接收客户端请求
* @param selector
* @param key
*/
private void accept(Selector selector, SelectionKey key) throws IOException {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
// 设为非阻塞式
socketChannel.configureBlocking(false);
// 绑定读取事件
socketChannel.register(selector,SelectionKey.OP_READ);
//将此对应的channel设置为准备接受其他客户端请求
key.interestOps(SelectionKey.OP_ACCEPT);
System.out.println("客户端ip:" + socketChannel.socket().getInetAddress().getHostAddress());
}
/**
* 读取客户端信息,实现客户端断开正常运行
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException {
System.out.print("客户端信息: ");
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
//判断客户端是否断开连接
boolean flag=true;
int len=0;
String content="";
try{
len=socketChannel.read(buffer);
flag=false;
}catch (Exception e){
}
if(len<=0||flag){
key.cancel();
System.out.println("客户端关闭");
}else {
buffer.flip();
content = new String(buffer.array(), 0, len);
System.out.println(content);
buffer.clear();
// 给客户端的回应
ByteBuffer out=ByteBuffer.wrap("001".getBytes());
socketChannel.write(out);
//socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
public static void main(String[] args) {
new ServerNIODemo().listen();
}
}
二、NIO客户端实现
package com.xu.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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
/**
* @Date: 2020/12/24
* 说明:
*/
public class ClientNIODemo {
// 端口号
private static final int PORT = 8090;
// 缓存大小
private static final int BUFFER_SIZE = 1024;
// 控制台输入流
private static Scanner scanner = new Scanner(System.in);
private Charset charset=Charset.forName("UTF-8");
public void send() throws IOException {
SocketChannel socketChannel = SocketChannel.open();
// 设置为非阻塞式
socketChannel.configureBlocking(false);
// 连接服务器
socketChannel.connect(new InetSocketAddress("localhost", PORT));
// 创建选择器
Selector selector = Selector.open();
// 绑定事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 若selector未被打开,则终止连接
while (selector.isOpen()) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 迭代器
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 把key移除
iterator.remove();
//不同事件的处理逻辑
process(selector, key);
}
}
}
private void process(Selector selector, SelectionKey key) {
try{
if (key.isConnectable()) {
// 连接业务
connect(selector, key);
}
if (key.isValid() && key.isReadable()) {
System.out.print("服务端响应: ");
// 读取业务
read(selector, key);
}
if (key.isValid() && key.isWritable()) {
System.out.print("发送信息: ");
write(selector, key);
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 连接
* @param selector
* @param key
*/
private void connect(Selector selector, SelectionKey key) throws IOException {
// TCP协议的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
// 完成连接
socketChannel.finishConnect();
// 连接成功
if (socketChannel.isConnected()) {
System.out.println("连接成功");
}
// 非阻塞
socketChannel.configureBlocking(false);
//绑定事件,向服务器发送信息
socketChannel.register(selector,SelectionKey.OP_WRITE);
}
/**
* 向服务端发送数据
* @param selector
* @param key
*/
private void write(Selector selector, SelectionKey key) throws IOException {
// 获得TCP协议通信的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
buffer.clear();
// 写入数据
buffer.put(ByteBuffer.wrap(scanner.nextLine().getBytes(charset)));
buffer.flip();
if (socketChannel.isConnected()) {
socketChannel.configureBlocking(false);
// 不断写出到通道
while (buffer.hasRemaining()) {
socketChannel.write(buffer);
}
// 注册读取事件
socketChannel.register(selector,SelectionKey.OP_READ);
}
}
/**
* 读取数据
* @param selector
* @param key
*/
public void read(Selector selector, SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
// 分配缓存空间
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int len=channel.read(buffer);
String content="";
// 读取数据到缓冲区
if(len > 0) {
// 读写转换
buffer.flip();
content=new String(buffer.array(),0,len,charset);
// 清空缓冲区
buffer.clear();
}
System.out.println(content);
// 注册write事件
channel.register(selector,SelectionKey.OP_WRITE);
}
public static void main(String[] args) {
try {
new ClientNIODemo().send();
} catch (IOException e) {
e.printStackTrace();
}
}
}