在 Java 的 NIO 教程中我们明白了 NIO 里面三个比较重要的元素,Buffer、Channel 和 Selector。它们之间相互协作,为 NIO 的高效提供了有力的保障。和 BIO 案例一样,它也需要客户端和服务端。需要网络通信,我们还是需要借助于 Socket。
我们创建两个 Java 项目,一个是客户端,一个是服务端,代码结构如下:
NIO 服务端:
NIO 客户端:
package net.haicoder.server;
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;
public class NioServer {
private static ByteBuffer readBuf = ByteBuffer.allocate(1024);
private static ByteBuffer writeBuf = ByteBuffer.allocate(1024);
public static void main(String[] args) {
int port = 9999;
Selector selector;
try {
//打开多路复用器
selector = Selector.open();
//定义一个 Channel
ServerSocketChannel channel = ServerSocketChannel.open();
//非阻塞模型
channel.configureBlocking(false);
//绑定端口号
channel.bind(new InetSocketAddress(port));
//channel 注册到 Selector 上面
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务已经启动了 ......");
while (true) {
//复用器开始监听
selector.select();
Iterator<SelectionKey> seletionKeys = selector.selectedKeys().iterator();
while (seletionKeys.hasNext()) {
SelectionKey key = seletionKeys.next();
if (key.isValid()) {
//监听客户端第一次端连接信息
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
System.out.println("accept a client : " + sc.socket().getInetAddress().getHostName());
} else {
read(key);
}
}
seletionKeys.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void read(SelectionKey key) {
try {
readBuf.clear();
//获取注册在通道里面的 key 对象
SocketChannel sc = (SocketChannel) key.channel();
int count = sc.read(readBuf);
if (count == -1) {
key.channel().close();
key.cancel();
return;
}
//有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
readBuf.flip();
//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
byte[] bytes = new byte[readBuf.remaining()];
//7 接收缓冲区数据
readBuf.get(bytes);
String body = new String(bytes).trim();
System.out.println("服务端接受到客户端请求的数据: " + body);
//9 告诉客户端已收到数据
writeBuf.put(("你好,客户端,我已收到数据:" + body).getBytes());
//对缓冲区进行复位
writeBuf.flip();
//写出数据到服务端
sc.write(writeBuf);
//清空缓冲区数据
writeBuf.clear();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package net.haicoder.client;
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.util.Iterator;
public class NioClient {
private static final String host = "127.0.0.1";
private static final Integer port = 9999;
public static void main(String[] args) {
try {
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress(host, port));
channel.configureBlocking(false);
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ, SelectionKey.OP_WRITE);
//开启新的线程监听,否则的话,客户端在控制台输入信息不好操作。
new Thread(() -> {
while (true) {
try {
//多路复用器开始监听
selector.select();
Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
while (selectionKeys.hasNext()) {
SelectionKey selectionKey = selectionKeys.next();
if (selectionKey.isValid() && selectionKey.isReadable()) {
//建立写缓冲区
ByteBuffer readBuf = ByteBuffer.allocate(1024);
//2 获取之前注册的socket通道对象
SocketChannel sc = (SocketChannel) selectionKey.channel();
//3 读取数据
int count = sc.read(readBuf);
if (count == -1) {
selectionKey.channel().close();
selectionKey.cancel();
return;
}
//5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
readBuf.flip();
//6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
byte[] bytes = new byte[readBuf.remaining()];
//7 接收缓冲区数据
readBuf.get(bytes);
String body = new String(bytes).trim();
System.out.println("收到服务端的数据:" + body);
}
selectionKeys.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
ByteBuffer writebuf = ByteBuffer.allocate(1024);
while (true) {
byte[] bytes = new byte[1024];
System.in.read(bytes);
//把数据放到缓冲区中
writebuf.put(bytes);
//对缓冲区进行复位
writebuf.flip();
//写出数据到服务端
channel.write(writebuf);
//清空缓冲区数据
writebuf.clear();
}
} catch (
IOException e) {
e.printStackTrace();
}
}
}
项目启动的时候,我们先启动服务端,再启动客户端。如果先启动客户端,会报连接拒绝异常。因为服务端启动的时候,需要知道服务端的地址和端口号。然后在客户端输入 你好,嗨客网!
效果如下
这样,客户端和服务端就通信了,我们看到服务端和客户端一直没有退出,一直启动着。
我们从代码中可以看到,代码的整体过程比较复杂。无论是客户端还是服务端,它们都依赖于 Selector、Channel 和 Buffer。将数据进行传递。有兴趣的同学可以实际操作一下。