为什么要使用NIO
jdk1.4之前的BIO是一种同步阻塞的通信模型,使用线程池虽然能达到提高性能,但是在一些高QPS请求下,仍然会存在线程阻塞的风险,NIO可以减少线程的使用的数量,核心技术点使用Linux的poll/epoll函数的调用,在BIO中处理连接请求时,需要单独的线程处理时,NIO只需要一个线程处理连接请求。NIO是JDK1.4引入的,NIO弥补了BIO的不足。
创建NIO服务器端代码
public class Server {
public static void main(String[] args) throws IOException {
// NIO是面向管道的
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",10001));
serverSocketChannel.configureBlocking(false);
// 调用底层Selector
Selector selector = Selector.open();
// 将ServerSocketChannel注册到Selector上,并对请求连接的时间感兴趣,ServerSocketChannel只负责接受请求,所以一个线程就够了
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// select是阻塞的,是一个逆向的操作,当socket的文件描述符准备就绪时,通知应用层程序
while (true){
// 此方法是阻塞方法
selectionKeys.select();
// 有事件发生,获取全部发生的事件集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 删除事件,防止重复执行事件
iterator.remove();
if (iterator.hasNext()){
SelectionKey next = iterator.next();
// 如果当前事件是一个请求连接的事件,正式由于nio提供了判断事件类型的机制,体现了nio是非阻塞
if (next.isAcceptable()){
ServerSocketChannel serverSocket = (ServerSocketChannel) next.channel();
// 获得请求连接请求的socket
ServerSocket socket = serverSocket.socket();
// 接受请求,并获取管道
SocketChannel acceptSocket = socket.accept().getChannel();
// 设置为非阻塞,必须设置,否则会报错
acceptSocket.configureBlocking(false);
// 将当前的socketChannel注册到selector中,并关注读事件(这里的读是相对于从socket缓冲区中读取事件)
acceptSocket.register(selector,SelectionKey.OP_READ);
System.out.println("客户端连接成功!");
} else if (next.isReadable()){
SocketChannel socketChannel = (SocketChannel) next.channel();
// nio是面向buffer,bio是面向stream
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
int read = socketChannel.read(byteBuffer);
if (read > 0){
byteBuffer.flip();
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String string = new String(bytes, "UTF-8");
System.out.println("服务求收到消息:" + string);
}
}
}
}
}
}
创建NIO客户端
public class Client {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress("127.0.0.1",10001));
System.in.read();
}
}
运行结果
Connected to the target VM, address: '127.0.0.1:50885', transport: 'socket'
客户端连接成功!