什么是BIO
BIO(Blocked Input Output)是一种同步阻塞IO。早期Java网络通信通过Socket(套接字)进行通信,这是一种阻塞式的通信。
通过BIO实现网络通信,需要一对套接字:
- 运行于服务端的ServerSocket
- 运行于客户端的Socket
Socket通信方式如下图所示:
 
Java使用Socket进行网络通信过程:
服务端:
1.创建ServerSocket对象,绑定地址(ip)和端口(port):serverSocket.bind(new InetSocketAddress(host,port))
2.通过accept方法监听客户端请求:accept方法调用后会阻塞住,直到客户端请求出现
3.连接建立,通过输入输出流进行通信:read方法调用会阻塞,直到数据可以读取
4.连接关闭,资源释放
客户端:
1.创建Socket对象,设置连接服务器的地址(ip)及端口(port):socket.connect(new InetSocketAddress(host,port))
2.连接建立,通过输入输出流进行通信:read方法调用会阻塞,直到数据可以读取
3.连接关闭,释放资源
Socket网络通信实例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 
 | public class Server {
 
 public static void main(String[] args) {
 
 try{
 ServerSocket serverSocket = new ServerSocket(8888);
 
 while(true){
 Socket socket = serverSocket.accept();
 
 
 InputStream inputStream = socket.getInputStream();
 DataInputStream dataInputStream = new DataInputStream(inputStream);
 String str = dataInputStream.readUTF();
 System.out.println("server receive from client:" + str);
 
 
 OutputStream outputStream = socket.getOutputStream();
 DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
 dataOutputStream.writeUTF("hello client, I am Server!");
 
 
 socket.close();
 }
 }
 catch (IOException e){
 e.printStackTrace();
 }
 
 }
 }
 
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | public class Client {
 
 public static void main(String[] args) {
 
 Socket socket;
 try{
 socket = new Socket("127.0.0.1", 8888);
 
 
 OutputStream outputStream = socket.getOutputStream();
 DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
 dataOutputStream.writeUTF("hello server, I am client!");
 
 
 InputStream inputStream = socket.getInputStream();
 DataInputStream dataInputStream = new DataInputStream(inputStream);
 String str = dataInputStream.readUTF();
 System.out.println("client receive from server:" + str);
 
 
 socket.close();
 }
 catch (IOException e){
 e.printStackTrace();
 }
 
 }
 
 }
 
 | 
Server端控制台会输出:
| 1
 | server receive from client:hello server, I am client!
 | 
Client端控制台会输出:
| 1
 | client receive from server:hello client, I am Server!
 | 
为什么不推荐使用BIO
资源耗费严重:一个线程只能处理一个客户端连接,如果要管理多个客户端的话,必须为每个客户端连接创建一个线程。
| 12
 3
 4
 5
 6
 
 | while(true){Socket socket = serverSocket.accept();
 new Thread(() - > {
 
 }).start();
 }
 
 | 
线程池可以一定程度上缓解此问题,但无法根本改变:
| 12
 3
 4
 5
 6
 
 | while(true){Socket socket = serverSocket.accept();
 threadPool.execute(() -> {
 
 });
 }
 
 | 
无论如何优化,它底层还是同步阻塞的IO,无法从根本上解决问题。
同步和异步(消息通信机制)
- 同步:发出一个调用时,在没有得到结果之前,该调用就不返回
- 异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果
阻塞和非阻塞(等待调用结果时的状态)
- 阻塞:调用结果返回之前,线程一直挂起
- 非阻塞:调用没有返回结果之前,线程不会阻塞
再看NIO
NIO(Non-blocking IO)是一种同步非阻塞IO,在java1.4时引入,对应java.nio包。
NIO提供了与传统BIO中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同套接字通道的实现,两种通道都支持阻塞和非阻塞两种模式:
- 阻塞模式:基本不会使用。与传统网络编程一样,简单但性能和可靠性不好。
- 非阻塞模式:对高负荷、高并发应用非常好,但是编程麻烦。这也是Netty出现的重要原因。
NIO核心组件解读
NIO包含一下几个核心组件:
- Channel
- Buffer
- Selector
- Selector Key
它们之间的关系如下:
 
1.NIO通过Channel(通道)和Buffer(缓存区)来传输数据,数据总是从缓冲区写入通道,或从通道读取到缓冲区。在NIO中,所以数据都是通过Buffer处理的,Channel对应于JDK底层的Socket。
2.NIO通过Selector(选择器)来监视多个通道对象,如数据到达、连接打开等,单线程可以监视多个通道
3.将Channel注册到Selector时,会返回一个Selector Key,可以根据它获取那些IO事件已经就绪,也可以通过它获取对应的Channel进行操作。
Selector是NIO实现的关键,它使用了事件通知相关的API来选择已经就绪的通道。
简单来说,流程如下:
- 将Channel注册到Selector中
- 调用Selector的select方法,这个方法会阻塞,直到有就绪的Channel出现
- 注册到Selector的就绪态Channel会被轮询出来:新的连接、读就绪、写就绪
- 通过Selector Key获取就绪Channel的集和,进行后续的IO操作
使用NIO进行通信
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 
 | public class NIOServer {
 public static void main(String[] args) throws IOException {
 
 
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
 
 serverSocketChannel.configureBlocking(false);
 
 serverSocketChannel.socket().bind(new InetSocketAddress(8888));
 
 
 Selector selector = Selector.open();
 
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
 
 
 while(true){
 
 int select = selector.select();
 
 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
 
 while(iterator.hasNext()){
 SelectionKey key = iterator.next();
 if(key.isAcceptable()) {
 createChannel(key);
 }
 else if(key.isReadable()){
 doRead(key);
 }
 else if(key.isWritable()){
 doWrite(key);
 }
 
 iterator.remove();
 }
 }
 }
 
 private static void doWrite(SelectionKey key) throws IOException {
 
 
 SocketChannel socketChannel = (SocketChannel)key.channel();
 
 
 ByteBuffer byteBuffer = ByteBuffer.wrap("send to Client".getBytes());
 socketChannel.write(byteBuffer);
 
 
 key.interestOps(SelectionKey.OP_READ);
 }
 
 private static void doRead(SelectionKey key) throws IOException {
 
 
 SocketChannel socketChannel = (SocketChannel)key.channel();
 
 
 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
 
 try{
 
 int read = socketChannel.read(byteBuffer);
 byteBuffer.flip();
 System.out.println("Server receive:" + new String(byteBuffer.array(), 0, read));
 
 key.interestOps(SelectionKey.OP_WRITE);
 }
 catch (IOException e){
 key.cancel();
 }
 }
 
 
 private static void createChannel(SelectionKey key) throws IOException {
 
 
 ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
 
 
 SocketChannel socketChannel = serverSocketChannel.accept();
 System.out.println("Accept connection from " + socketChannel);
 
 
 socketChannel.configureBlocking(false);
 
 
 ByteBuffer byteBuffer = ByteBuffer.wrap(("Welcome:" + socketChannel.getRemoteAddress()
 + " assigned to" + Thread.currentThread().getName()).getBytes());
 socketChannel.write(byteBuffer);
 
 
 
 
 socketChannel.register(key.selector(),SelectionKey.OP_READ );
 }
 }
 
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | public class NIOClient {
 public static void main(String[] args) throws IOException {
 
 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(8888));
 
 socketChannel.configureBlocking(false);
 
 ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
 
 byteBuffer.put("Hello I am Client".getBytes());
 byteBuffer.flip();
 socketChannel.write(byteBuffer);
 
 byteBuffer.clear();
 socketChannel.read(byteBuffer);
 byteBuffer.flip();
 System.out.println(new String(byteBuffer.array()));
 }
 }
 
 | 
NIO为什么更好
- 使用比较少的线程就可以管理多个客户端连接,提高了并发量且减少了资源消耗
- 没有IO操作的时候,线程可以去执行其他任务,非阻塞。
使用NIO编写代码太难了
NIO非常难用,而且存在许多bug,开发和维护的成本比较大。一般情况下会使用Netty这个成熟的框架。
重要角色Netty
还在学习中…..