BIO
在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行,这就是阻塞式IO。
以下示例了一个服务端程序:绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里。
//BIO 服务器端程序
public class TCPServer {
public static void main(String[] args) throws Exception {
//1.创建ServerSocket对象
System.out.println("服务端 启动....");
System.out.println("初始化端口 9999 ");
ServerSocket ss=new ServerSocket(9999); //端口号
while (true) {
//2.监听客户端
Socket s = ss.accept(); //阻塞
//3.从连接中取出输入流来接收消息
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[10];
is.read(b);
String clientIP = s.getInetAddress().getHostAddress();
System.out.println(clientIP + "说:" + new String(b).trim());
//4.从连接中取出输出流并回话
OutputStream os = s.getOutputStream();
os.write("没钱".getBytes());
//5.关闭
s.close();
}
}
}
BIO 客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。:
//BIO 客户端程序
public class TCPClient {
public static void main(String[] args) throws Exception {
while (true) {
//1.创建Socket对象
Socket s = new Socket("127.0.0.1", 9999);
//2.从连接中取出输出流并发消息
OutputStream os = s.getOutputStream();
System.out.println("请输入:");
Scanner sc = new Scanner(System.in);
String msg = sc.nextLine();
os.write(msg.getBytes());
//3.从连接中取出输入流并接收回话
InputStream is = s.getInputStream(); //阻塞
byte[] b = new byte[20];
is.read(b);
System.out.println("老板说:" + new String(b).trim());
//4.关闭
s.close();
}
}
}
NIO
java.nio 全称 Java Non-Blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出的新特性,被统称为 NIO(即 New IO)。新增了许多用于处理输入输出的类,这些类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写,新增了满足 NIO 的功能。
NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同;
- BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 IO 的效率比流 IO 高很多。
- NIO 是非阻塞式的,这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。
NIO 主要有三大核心部分:
- Channel通道
- Buffer缓冲区
- Selector选择器
传统的 BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel和 Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
使用 Java 的 NIO,用非阻塞的 IO 方式处理
优点:这种模式可以用一个线程,处理大量的客户端连接
缺点:代码复杂度较高,不易理解
多路复用的Selector选择器
Selector 是 Java NIO(Non-blocking I/O)框架中的关键组件之一,它提供了一种机制来检测一组 SelectableChannel 对象(如 ServerSocketChannel, SocketChannel, DatagramChannel 和 FileChannel)上的 I/O 事件。Selector 能够高效地监控多个通道的读、写、连接等事件,从而允许一个单线程处理多个网络连接。使用 Selector 可以显著提高服务器的可扩展性和效率,因为它避免了为每个连接创建单独线程的开销,减少了上下文切换,并且只在实际有事件发生时才进行 I/O 操作,这样可以最大化地利用 CPU 和 I/O 资源。
import java.nio.channels.*;
import java.net.*;
public class SelectorExample {
public static void main(String[] args) throws Exception {
// 创建一个 Selector
Selector selector = Selector.open();
// 打开 ServerSocketChannel 并绑定到本地地址
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
// 设置通道为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 注册通道到 Selector,监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 选择就绪的通道
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// 遍历所有就绪的 SelectionKey
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
// 如果是连接请求,接受新的 SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 如果是读就绪,读取数据
// ...
}
iterator.remove();
}
}
}
}
这段代码展示了一个基本的 TCP 服务器,它使用 Selector 来监听连接请求和读事件。根据实际需求,你可以扩展此代码来处理更多类型的事件和更复杂的业务逻辑。
使用 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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class ChatServer {
private static final int PORT = 8080;
private static final int BUFFER_SIZE = 1024;
private static final ByteBuffer buffer = ByteBuffer.allocate(BROADCAST_SIZE);
public static void main(String[] args) {
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Chat Server started on port " + PORT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
acceptNewConnection(serverSocketChannel, selector);
} else if (key.isReadable()) {
readFromClient(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void acceptNewConnection(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("New client connected: " + clientChannel.getRemoteAddress());
}
private static void readFromClient(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
buffer.clear();
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[bytesRead];
buffer.get(data);
String message = new String(data);
System.out.println("Received from client: " + message);
broadcastToAll(message, clientChannel);
} else if (bytesRead < 0) {
key.cancel();
clientChannel.close();
}
}
private static void broadcastToAll(String message, SocketChannel sender) throws IOException {
for (SelectionKey key : selector.keys()) {
Channel targetChannel = key.channel();
if (targetChannel instanceof SocketChannel && targetChannel != sender) {
((SocketChannel) targetChannel).write(ByteBuffer.wrap(message.getBytes()));
}
}
}
}
客户端代码示例:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class ChatClient {
private static final int PORT = 8080;
private static final String SERVER_ADDRESS = "localhost";
private static final int BUFFER_SIZE = 1024;
private static final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
public static void main(String[] args) {
try (SocketChannel socketChannel = SocketChannel.open()) {
socketChannel.connect(new InetSocketAddress(SERVER_ADDRESS, PORT));
socketChannel.configureBlocking(false);
System.out.println("Connected to the chat server.");
while (true) {
System.out.print("Enter your message: ");
String message = System.console().readLine();
//特殊命令:退出
if (message.equalsIgnoreCase("exit")) {
break;
}
buffer.put(message.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}