NIO概述
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
三大核心部分
- Channel(通道)
- Buffer(缓冲区)
- Selector(多路复用器)
NIO聊天室
# 服务端
package com.milla.study.netbase.expert.io.nio;
import com.google.code.yanf4j.util.ConcurrentHashSet;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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;
import java.util.Set;
/**
* @Package: com.milla.study.netbase.expert.netty.nio.server
* @Description: <NIO模式下的Server>
* @Author: milla
* @CreateDate: 2020/08/07 10:32
* @UpdateUser: milla
* @UpdateDate: 2020/08/07 10:32
* @UpdateRemark: <>
* @Version: 1.0
*/
@Slf4j
public class NioServer {
/**
* 消息缓存区
*/
private static ByteBuffer readBuf = ByteBuffer.allocate(1024);
/**
* 保存的客户端
*/
static Set<SocketChannel> clients = new ConcurrentHashSet<>();
/**
* 选择器
*/
private static Selector selector;
public static void main(String[] args) throws Exception {
server();
}
private static void server() throws Exception {
//获取一个channel
ServerSocketChannel channel = ServerSocketChannel.open();
//配置是否阻塞
channel.configureBlocking(false);
//获取socket
ServerSocket server = channel.socket();
//绑定服务及端口
server.bind(new InetSocketAddress(InetAddress.getLocalHost(), 10010));
//获取到选择器
selector = Selector.open();
//注册到选择器用以接受连接
channel.register(selector, SelectionKey.OP_ACCEPT);
log.info("server is start....");
//一直能处理
while (true) {
//等待需要处理的新事件,阻塞将一直持续到下一个传入事件
selector.select();
//获取所有接收事件的selection-key实例
Set<SelectionKey> readKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readKeys.iterator();
while (iterator.hasNext()) {
try {
SelectionKey key = iterator.next();
//检查时间是否是一个新的已经就绪可以被接受的连接
if (key.isAcceptable()) {
getConnect(key, selector);
}
//是否可以读取数据
if (key.isReadable()) {
getMessage(key);
}
//检查套接字是否已经准备写数据
// if (key.isWritable()) {
//写操作
// }
//处理完成之后要将key删除防止重复处理
iterator.remove();
} catch (Exception e) {
log.info("错误:{}", e);
e.printStackTrace();
}
}
}
}
/**
* 获取连接
*
* @param key
* @param selector
* @throws Exception
*/
private static void getConnect(SelectionKey key, Selector selector) throws Exception {
//要操作的message
ByteBuffer msg = ByteBuffer.wrap("connect success ".getBytes());
ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
//接收一个连接
SocketChannel client = serverSocket.accept();
clients.add(client);
//设置非阻塞
client.configureBlocking(false);
//监听读/写事件
client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ);
//发送连接成功给客户端
client.write(msg);
log.info(" a new socket connected...{}", client);
}
/**
* 读取数据
*
* @param key
* @throws IOException
*/
private static void getMessage(SelectionKey key) throws IOException {
readBuf.clear();
SocketChannel client = (SocketChannel) key.channel();
try {
int count = client.read(readBuf);
if (count == -1) {
client.shutdownOutput();
client.shutdownInput();
client.close();
log.info("断开连接....");
clients.remove(key);
}
byte[] bytes = new byte[count];
readBuf.flip();
readBuf.get(bytes);
String message = new String(bytes, 0, count);
log.info("接收到信息:{}", message);
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
sendMessages2Clients(message);
} catch (IOException e) {
key.cancel();
client.close();
log.error("端开连接");
clients.remove(key);
}
}
/**
* 接收到数据将数据群发到各个客户端
*
* @param message
*/
private static void sendMessages2Clients(String message) {
clients.stream().forEach(client -> {
try {
client.write(ByteBuffer.wrap(message.getBytes()));
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
# 客户端
package com.milla.study.netbase.expert.io.nio;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetAddress;
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;
import java.util.Scanner;
import java.util.Set;
/**
* @Package: com.milla.study.netbase.expert.io.nio
* @Description: <NIO下的client>
* @Author: milla
* @CreateDate: 2020/08/07 11:16
* @UpdateUser: milla
* @UpdateDate: 2020/08/07 11:16
* @UpdateRemark: <>
* @Version: 1.0
*/
@Slf4j
public class NioClient {
/**
* 退出聊天的标识
*/
private static volatile boolean connected = true;
/**
* 输入流
*/
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) throws Exception {
init();
new Thread(() -> write()).start();
new Thread(() -> read()).start();
}
/**
* 写数据线程
*
* @throws IOException
*/
private static void write() {
while (connected) {
try {
//获取选择器
selector.select();
//获取选择器的已选择键集
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//处理完当前的key需要删除,防止重复处理
it.remove();
if (key.isConnectable()) {
log.info("try connecting .... ");
SocketChannel channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
channel.finishConnect();
}
//数据是否可写
if (key.isWritable()) {
sendMessage(key);
}
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Selector selector;
/**
* 初始化连接服务器
*
* @throws IOException
*/
private static void init() throws IOException {
//打开socket通道
SocketChannel sc = SocketChannel.open();
//设置非阻塞
sc.configureBlocking(false);
//连接到服务器-指定主机名称和端口
sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), 10010));
//打开选择器
selector = Selector.open();
//注册到服务器上的socket动作
sc.register(selector, SelectionKey.OP_CONNECT);
}
/**
* 读数据线程
*
* @throws IOException
*/
private static void read() {
while (connected) {
try {
//获取选择器
selector.select();
//获取选择器的已选择键集
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
//处理完当前的key需要删除,防止重复处理
it.remove();
if (key.isConnectable()) {
log.info("try connecting .... ");
SocketChannel channel = (SocketChannel) key.channel();
channel.configureBlocking(false);
//完成连接
channel.finishConnect();
//下面这种方式也可以
//sc.finishConnect();
//sc.register(selector, SelectionKey.OP_WRITE);
}
//数据是否可读
if (key.isReadable()) {
getMessage(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发送数据
*
* @param key
* @throws IOException
*/
private static void sendMessage(SelectionKey key) {
SocketChannel channel = (SocketChannel) key.channel();
try {
String requestLine = scanner.nextLine();
//写数据
channel.write(ByteBuffer.wrap(requestLine.getBytes()));
key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//设置标识符退出聊天
if ("quit".equals(requestLine)) {
connected = false;
log.info("退出聊天...", Thread.currentThread().getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据
*
* @param key
* @throws IOException
*/
private static void getMessage(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//清空缓存数据
buffer.clear();
//读取数据
int count = channel.read(buffer);
byte[] bytes = new byte[count];
//弹出数据
buffer.flip();
buffer.get(bytes);
log.info("接收到服务器消息:{}", new String(bytes));
}
}
PS : 在连接的时候需要使用finishConnect()函数,多开几个客户端,就可以实现聊天了