Java BOI与NIO超详细实例精讲
顽石九变 人气:0Java BIO
阻塞IO,每个客户端链接都需要一个独立的线程处理,客户端链接没关闭时,线程链接处于阻塞状态,直到客户端链接关闭
如果客户端链接没有读取到数据,链接就会一直阻塞住,造成资源浪费
示例代码
开发一个ServerSocket服务端,通过telnet链接发送信息
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class BIOServerTest { public static void main(String[] args) throws Exception { ExecutorService pool = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端启动"); while (true) { System.out.println("等待链接..."); Socket socket = serverSocket.accept(); pool.execute(() -> { handler(socket); }); } } private static void handler(Socket socket) { try { System.out.println("有一个客户端接入:" + Thread.currentThread().getName()); byte[] bytes = new byte[1024]; //通过socket 获取输入流 InputStream inputStream = socket.getInputStream(); //循环读取客户端发送的数据 while (true) { System.out.println("等待读取数据..."); int read = inputStream.read(bytes); if (read != -1) { //输出客户端读取的数据 System.out.println("线程信息:" + Thread.currentThread().getName()); System.out.println(new String(bytes, 0, read)); } else { break; } } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("关闭client链接:" + Thread.currentThread().getName()); try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
通过telnet链接两个客户端,分别发送请求
从控制台打印信息可以看出每一个链接对应的线程都是独立的
等待链接...
有一个客户端接入:pool-1-thread-1
等待读取数据...
线程信息:pool-1-thread-1
aaa
等待读取数据...
线程信息:pool-1-thread-1
bbb
等待读取数据...
等待链接...
有一个客户端接入:pool-1-thread-2
等待读取数据...
线程信息:pool-1-thread-2
ccc
等待读取数据...
线程信息:pool-1-thread-2
ddd
等待读取数据...
Java NIO
非阻塞IO,通过Selector和Channel,实现一个线程维护多个链接
NIO有三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
NOI是面向缓冲区编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加的处理过程的灵活性,使用它可以提供非阻塞式的高伸缩性网络
NIO是通过事件驱动编程的,Selector会根据不同的事件,在各个通道上切换
Channel同时支持读写双向处理
Selector能够监测到多个注册的通到是否有事件发生,这样就可以只用一个线程去管理多个通道,也就是管理多个链接和请求。
只有在链接真正有读写事件发生时,才会进行读写,大大减少了系统开销,并且不必为每个链接创建一个线程
代码解读
- 当客户端链接时,会通过ServerSocketChannel得到SocketChannel
- 将SocketChannel注册到Selector上,一个selector上可以注册多个SocketChannel
- 通过socketChannel.register()方法注册
- 注册后返回一个SelectionKey,会和该Selector关联
- Selector监听select方法,返回有事件发生的通道的个数
- 进一步得到有事件发生的各个SelectionKey,通过SelectionKey反向获取SocketChannel
- 通过得到的channel完成业务处理
1)服务端代码
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; public class NIOServer { public static void main(String[] args) throws Exception { ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(7000); serverSocketChannel.socket().bind(inetSocketAddress); serverSocketChannel.configureBlocking(false); Selector selector = Selector.open(); //注册客户端链接事件到selector serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int select = selector.select(1000L); if (select == 0) { System.out.println("等待1秒,没有事件发生"); continue; } //监听到相关事件发生,读取SelectionKey集合,遍历处理所有事件 Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { SocketChannel socketChannel = serverSocketChannel.accept(); System.out.println("客户端链接成功,生成一个sockeChannel " + socketChannel.hashCode()); //将socketChannel设置为非阻塞 socketChannel.configureBlocking(false); //注册内容读取事件到selector,同时关联一个Buffer socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024)); } if (key.isReadable()) { SocketChannel channel = null; try { channel = (SocketChannel) key.channel(); ByteBuffer byteBuffer = (ByteBuffer) key.attachment(); int read = channel.read(byteBuffer); System.out.println("读取到数据: " + new String(byteBuffer.array(), 0, read)); } catch (Exception e) { if (channel != null) { channel.close(); } e.printStackTrace(); } } //手动从集合中移除当前的SelectionKey,防止重复操作 keyIterator.remove(); } } } }
2)客户端代码
import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; public class NIOClient { public static void main(String[] args) throws Exception { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000); if (!socketChannel.connect(inetSocketAddress)){ while (!socketChannel.finishConnect()){ System.out.println("链接未完成,客户端不会阻塞...."); } } String str = "Hello,你好~~~"; ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes()); socketChannel.write(byteBuffer); System.in.read(); } }
控制台输出
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
等待1秒,没有事件发生
客户端链接成功,生成一个sockeChannel 60559178
读取到数据: Hello,你好~~~
等待1秒,没有事件发生
加载全部内容