1. Java NIO是非阻塞IO,有些人可能会把NIO理解成 new io,其实不是的,nio和阻塞IO本质上操作的socket文件描述符是相同的,SocketChannel和socket本质也是一样的,都是要读取socket数据。NIO和阻塞IO有什么区别呢。阻塞IO的特点是当socket没有数据的时候,read会线程阻塞,直到有可读的数据,才获取线程的执行权,没可读的数据线程就一直卡在read的方法上。NIO是我不调read方法,直到操作系统告诉我那个socket是有数据的可读的,我再去read,这样我是不是就不会卡死在read上。
NIO和阻塞IO的区别:
阻塞IO:当读取一个socket的时候,没有数据可读,线程卡死,直到有数据线程才恢复运行。
当server要监听多个socket描述符的时候server就废了,一个卡住就相当于线程死了,
除非用上多线程,不过线程终究是有限的。
NIO:我不会先read,而是我先获取哪些socket是可以读的,一个都没有我就等待,
直到有可读的socket,我遍历可读的socket集合,挨个处理。我不会卡在任何一个socket read上面
linux上面实现nio使用的,epoll的三个函数实现的。关于Linux的C实现可以看我上一篇的文章。
2. java nio server
流程和我上一篇的 epoll http server几乎是一样的,懂得底层原理,再看这些和玩一样。
@Slf4j
public class NioServer {
public static void main(String[] args) throws Exception{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("http://localhost:8080");
while (true){
// select 就会阻塞,直到有新的连接或者已连接的文件描述符触发事件,返回的set集合是这个socket有注册的事件发生
int select = selector.select();
log.info("socket size: "+select);
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
try{
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()){
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
SocketChannel channel = serverSocketChannel1.accept();
log.info("new socket: "+channel.getRemoteAddress().toString());
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()){
SocketChannel channel = null;
try{
channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(8192);
channel.read(byteBuffer);
byteBuffer.flip();
byte[] array = new byte[byteBuffer.limit()];
byteBuffer.get(array);
log.info("data:" + new String(array, StandardCharsets.UTF_8));
byteBuffer.clear();
//写数据
byteBuffer.put("hello".getBytes(StandardCharsets.UTF_8));
byteBuffer.flip();
channel.write(byteBuffer);
byteBuffer.clear();
}catch (Exception e){
// 可能会抛出IOException,这个时候关闭这个socket
log.error(e.getMessage(), e);
assert channel != null;
channel.close();
}
}
}catch (Exception e){
log.error(e.getMessage(), e);
System.exit(-1);
}finally {
iterator.remove();
}
}
}
}
}
3. socket client
public class NioClient {
public static void main(String[] args) throws Exception{
SocketChannel socket = SocketChannel.open(new InetSocketAddress("localhost", 8080));
DefaultEventLoop eventExecutors = new DefaultEventLoop();
eventExecutors.submit(() -> {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
try {
socket.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes, StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
socket.close();
System.exit(-1);
}finally {
buffer.clear();
}
}
});
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
buffer.put(s.getBytes(StandardCharsets.UTF_8));
buffer.flip();
socket.write(buffer);
buffer.clear();
}
}
}
4. 测试

