目录
整体代码
代码详解
注意事项
非阻塞模式:
Selector的正确使用:
适当的缓冲区管理:
事件处理的错误处理:
性能优化:
进程退出和资源释放:
当使用Java NIO(New I/O)创建一个服务端时,你需要使用ServerSocketChannel
和Selector
两个关键类。
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 NIOServer {
private static final int BUFFER_SIZE = 1024;
private static final int TIMEOUT = 3000;
public static void main(String[] args) {
// 创建一个Selector实例
try (Selector selector = Selector.open();
// 创建一个ServerSocketChannel实例
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
// 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
// 注册到Selector,监听ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 阻塞等待就绪的事件
if (selector.select(TIMEOUT) == 0) {
System.out.print(".");
continue;
}
// 获取所有已就绪的事件的SelectionKey集合
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理事件
handleKey(key);
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleKey(SelectionKey key) throws IOException {
if (key.isAcceptable()) {
// 处理ACCEPT事件
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
} else if (key.isReadable()) {
// 处理READ事件
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int bytesRead = socketChannel.read(buffer);
if (bytesRead == -1) {
// 客户端关闭连接
socketChannel.close();
} else if (bytesRead > 0) {
// 处理接收到的数据
buffer.flip();
byte[] data = new byte[bytesRead];
buffer.get(data);
String message = new String(data);
System.out.println("Received message: " + message);
buffer.clear();
}
}
}
}
导入必要的类:
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;
设置常量:
这里定义了一个缓冲区大小和一个超时时间,可以根据需要进行调整。
private static final int BUFFER_SIZE = 1024;
private static final int TIMEOUT = 3000;
创建Selector和ServerSocketChannel实例:
try (Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
使用try-with-resources
语句创建了Selector
和ServerSocketChannel
的实例。这样可以确保在使用完后自动关闭它们。
配置ServerSocketChannel:
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
将ServerSocketChannel
配置为非阻塞模式,并绑定到指定的端口(这里使用的是8080)
注册到Selector:
将ServerSocketChannel
注册到Selector
上,以便监听ACCEPT
事件。
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
进入事件循环:
这是服务端的主要事件循环。在每次循环中,使用selector.select(TIMEOUT)
阻塞等待就绪的事件。如果没有任何事件就绪,将继续下一次循环。
while (true) {
if (selector.select(TIMEOUT) == 0) {
System.out.print(".");
continue;
}
处理就绪的事件:
获取就绪的事件的SelectionKey
集合,并遍历处理每个就绪的事件。
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
handleKey(key);
iterator.remove();
}
处理ACCEPT事件:
如果就绪的事件是ACCEPT
事件,表示有新的客户端连接请求。通过ServerSocketChannel.accept()
方法接受连接,并将SocketChannel
注册到Selector
上,以便监听READ
事件。
if (key.isAcceptable()) {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
}
处理READ事件:
如果就绪的事件是READ
事件,表示有数据可读取。使用SocketChannel.read()
方法读取数据,并进行相应的处理。在示例中,将读取的数据存储到ByteBuffer
中,并打印接收到的消息。
SocketChannel
的read
方法在处理READ
事件时返回的整数值有以下含义:
read
方法立即返回0。在处理read
事件时,需要根据返回值进行适当的处理。以下是对这些返回值的典型处理方式:
ByteBuffer
中获取数据并进行相应的处理。可以将读取到的数据转换为字符串、处理数据包等操作。READ
事件,或进行其他操作。else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
int bytesRead = socketChannel.read(buffer);
if (bytesRead == -1) {
socketChannel.close();
} else if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[bytesRead];
buffer.get(data);
String message = new String(data);
System.out.println("Received message: " + message);
buffer.clear();
}
}
在实现Java NIO服务端时,以下是一些需要注意的关键事项,并附带一些示例说明:
configureBlocking(false)
将ServerSocketChannel
和SocketChannel
配置为非阻塞模式。serverSocketChannel.configureBlocking(false);
socketChannel.configureBlocking(false);
OP_ACCEPT
、OP_READ
等。serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
socketChannel.register(selector, SelectionKey.OP_READ);
if (key.isAcceptable()) {
// 处理ACCEPT事件
} else if (key.isReadable()) {
// 处理READ事件
}
ByteBuffer
缓冲区的大小,避免过小或过大的缓冲区。ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
// 读取数据
} catch (IOException e) {
// 处理读取异常
}
并发访问的线程安全性:
synchronized (selector) {
// 同步访问共享资源
}
DirectByteBuffer
代替HeapByteBuffer
,以减少数据复制和内存拷贝。ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
serverSocketChannel.close();
socketChannel.close();
selector.close();