目录
IO模型
BIO
NIO
常见问题
IO模型
Java共支持3种网络编程IO模式:BIO,NIO,AIO
BIO
同步阻塞模型,一个客户端连接对应一个处理线程
代码示例:
Server端:
public class BioServer {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动成功");
while (true) {
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功");
executorService.execute(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
String message;
while ((message = reader.readLine()) != null) {
System.out.println("收到消息: " + message);
writer.write("已收到消息: " + message);
writer.newLine();
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
Client端:
public class BioClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8888);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String message;
while ((message = consoleReader.readLine()) != null) {
writer.write(message);
writer.newLine();
writer.flush();
System.out.println("发送消息: " + message);
System.out.println("收到回复: " + reader.readLine());
}
socket.close();
}
}
执行结果:
存在问题:
- IO操作是阻塞操作, 连接时读写会造成线程阻塞
- 线程太多会造成CPU异常
应用场景:
BIO 方式适用连接数较小且固定的架构, 这种方式对服务器资源要求比较高, 但代码简单易理解。
NIO
同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接),客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,JDK1.4开始引入。
应用场景:
NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂。
代码
public class BioSelectorServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
// 设置非阻塞
serverSocket.configureBlocking(false);
// 创建多路复用器
Selector selector = Selector.open();
// 注册serversocket到selector, 关注连接事件
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while (true) {
// 阻塞等待需要处理的事件
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是连接事件 > 获取连接并且注册读事件
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接成功");
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
if (len > 0) {
System.out.println("接收到消息:" + new String(byteBuffer.array()));
} else {
System.out.println("客户端断开连接");
socketChannel.close();
}
}
// 从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}
NIO的三大核心组件:
- channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组
- channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理
- NIO 的 Buffer 和 channel 都是既可以读也可以写
常见问题
为什么Netty使用NIO而不是AIO?
在Linux系统上,AIO的底层实现仍使用Epoll,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层,不容易深度优化,Linux上AIO还不够成熟。Netty是异步非阻塞框架,Netty在NIO上做了很多异步的封装。