BIO:阻塞IO,阻塞Socket的accept方法,直到建立连接
NIO:非阻塞IO,所有连接注册到Selector上作为一个事件,服务端通过轮询这个Selector来查看是否需要建立连接或执行某些 感兴趣的事件
NIO服务端:
public class NioServer {
public static void main(String[] args) {
Selector selector = null;
ServerSocketChannel serverSocketChannel = null;
int port = 8999;
try {
// 为服务端开启Selector以及服务端通道ServerSocketChannel
selector = Selector.open();
// 服务端通道代表所有可能注册到当前Selector中的连接请求
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 使用 ServerSocketChannel 在当前Selector上注册ACCEPT事件
// 即只关心 ServerSocketChannel 是否能建立连接
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 创建线程池用于建立连接后,每个连接的读写操作处理
ExecutorService executor = Executors.newFixedThreadPool(10);
System.err.println("服务端初始化完成,监听端口:" + port);
// 开启服务端 Selector 轮询
while (true) {
// 服务端 Selector 上没有感兴趣的事件(此处是ACCEPT事件)则 continue;
if (selector.select() <= 0) {
continue;
}
// 直到 Selector 上注册了感兴趣的事件(此处是ACCEPT事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
// 获取到每一个连接,交由线程池处理,传递连接的KEY
while (iterator.hasNext()) {
executor.submit(new NioServerHandler(iterator.next()));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
每个连接都会交由线程池去管理,所以接下来看看线程池中管理的线程在做什么;
NioServerHandler.java
public class NioServerHandler implements Runnable{
// 服务端传递的连接KEY
private SelectionKey key;
// 注意:此处的Selector不是上面服务端的Selector,而是针对于每个连接自身的Selector
// 当连接建立后(ACCEPT完成),用于 客户端连接 的读写事件注册,并轮询这个 Selector 处理
private Selector selector;
public NioServerHandler(SelectionKey key) {
this.key = key;
}
private void doAccept() throws IOException {
// 新连接进来,当前类的 Selector 属性为 null
if (selector == null) {
// 打开当前连接专属的 Selector
selector = Selector.open();
// doAccept
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// 建立连接,获取客户端通道 SocketChannel
SocketChannel accept = serverSocketChannel.accept();
accept.configureBlocking(false);
// 为客户端连接注册读事件(READ)
accept.register(selector, SelectionKey.OP_READ);
// 移除掉 服务端Selector 中传递的当前的KEY,因为已经建立了连接
key.selector().selectedKeys().remove(key);
System.err.println("Server Accepted");
}
}
@Override
public void run() {
try {
// 处理Accept事件,与客户端建立连接
doAccept();
// 开启客户端连接 Selector 轮询
while (true) {
// 没有感兴趣的事件则 continue;
if (selector.select() <= 0) {
continue;
}
// 获取到感兴趣的事件,如 READ
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// 这一步是防止客户端断开连接后,没有正确关闭Socket导致服务端报错
this.key = selectionKey;
// 可读
if (selectionKey.isReadable()) {
// 获取到客户端 SocketChannel, 执行读操作
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (channel.read(byteBuffer) > 0) {
System.out.println(new String(byteBuffer.array()));
byteBuffer.flip();
}
// 读完后去除当前KEY,并重新注册写事件
selector.selectedKeys().remove(selectionKey);
channel.register(selector, SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) { // 可写
// 获取客户端连接通道 SocketChannel
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 执行写操作,而后去除当前KEY
channel.write(ByteBuffer.wrap("Hey Client".getBytes()));
selector.selectedKeys().remove(selectionKey);
// 这里默认写操作后释放客户端连接,当然也可以重新注册别的事件
selectionKey.cancel();
channel.socket().close();
channel.close();
}
}
}
} catch (IOException e) {
// 由于key值在每次循环都有赋值,所以当客户端断开连接后,捕获到异常后正确关闭Socket
key.cancel();
System.err.println("与客户端断开连接");
}
}
}
客户端也同样使用NIO的方式处理事件:
NioClient.java
public class NioClient {
public static void main(String[] args) throws IOException {
// 客户端也可以开启Selector,通过建立连接注册自己感兴趣的事件
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
// 注册CONNECT事件,当连接建立将触发此事件
channel.register(selector, SelectionKey.OP_CONNECT);
// 建立与服务端连接
channel.connect(new InetSocketAddress(8999));
// 开启轮询
while (true) {
// 当连接正确建立并有感兴趣的事件发生
if (selector.select() > 0 && channel.finishConnect()) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
// 获取KEY
SelectionKey key = iterator.next();
// 触发连接事件
if (key.isConnectable()) {
// 获取与服务端连接通道
SocketChannel socketChannel = (SocketChannel) key.channel();
socketChannel.configureBlocking(false);
// 连接后向服务端发送数据
socketChannel.write(ByteBuffer.wrap("From NioClient...".getBytes()));
// 重新注册 READ事件到Selector,并移除原有的KEY
socketChannel.register(selector, SelectionKey.OP_READ);
selector.selectedKeys().remove(key);
} else if (key.isReadable()) {
// 当服务端响应后触发此处的 READ事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer) > 0) {
System.out.println("收到服务端响应:" + new String(byteBuffer.array()));
byteBuffer.flip();
}
// 获得响应则释放连接,请求结束
key.cancel();
socketChannel.socket().close();
socketChannel.close();
return;
}
}
}
}
}
}