前言
这段时间自己在看一些Java中BIO和NIO之类的东西,看了很多博客,发现各种关于NIO的概念说的天花乱坠头头是道,可以说是非常的完整,但是整个看下来之后,自己对NIO还是一知半解的状态,所以这篇文章不会提到很多的概念,而是站在一个实践的角度,写一些我自己关于NIO的见解,站在实践过后的高度下再回去看概念,应该对概念会有一个更好的理解。
实现一个简易单线程服务器
要讲明白BIO和NIO,首先我们应该自己实现一个简易的服务器,不用太复杂,单线程即可。
为什么使用单线程作为演示
因为在单线程环境下可以很好地对比出BIO和NIO的一个区别,当然我也会演示在实际环境中BIO的所谓一个请求对应一个线程的状况。
服务端
public class Server { public static void main(String[] args) { byte[] buffer = new byte[1024]; try { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("服务器已启动并监听8080端口"); while (true) { System.out.println(); System.out.println("服务器正在等待连接..."); Socket socket = serverSocket.accept(); System.out.println("服务器已接收到连接请求..."); System.out.println(); System.out.println("服务器正在等待数据..."); socket.getInputStream().read(buffer); System.out.println("服务器已经接收到数据"); System.out.println(); String content = new String(buffer); System.out.println("接收到的数据:" + content); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
客户端
public class Consumer { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1",8080); socket.getOutputStream().write("向服务器发数据".getBytes()); socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
代码解析
我们首先创建了一个服务端类,在类中实现实例化了一个SocketServer并绑定了8080端口。之后调用accept方法来接收连接请求,并且调用read方法来接收客户端发送的数据。最后将接收到的数据打印。
完成了服务端的设计后,我们来实现一个客户端,首先实例化Socket对象,并且绑定ip为127.0.0.1(本机),端口号为8080,调用write方法向服务器发送数据。
运行结果
当我们启动服务器,但客户端还没有向服务器发起连接时,控制台结果如下:
当客户端启动并向服务器发送数据后,控制台结果如下:
结论
从上面的运行结果,首先我们至少可以看到,在服务器启动后,客户端还没有连接服务器时,服务器由于调用了accept方法,将一直阻塞,直到有客户端请求连接服务器。
对客户端功能进行扩展
在上文中,我们实现的客户端的逻辑主要是,建立Socket --> 连接服务器 --> 发送数据,我们的数据是在连接服务器之后就立即发送的,现在我们来对客户端进行一次扩展,当我们连接服务器后,不立即发送数据,而是等待控制台手动输入数据后,再发送给服务端。(服务端代码保持不变)
代码
public class Consumer { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1",8080); String message = null; Scanner sc = new Scanner(System.in); message = sc.next(); socket.getOutputStream().write(message.getBytes()); socket.close(); sc.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试
当服务端启动,客户端连接服务端,但没有发送数据时,控制台结果如下:
当服务端启动,客户端连接服务端,并且发送数据时,控制台结果如下:
结论
从上文的运行结果中我们可以看到,服务器端在启动后,首先需要等待客户端的连接请求(第一次阻塞),如果没有客户端连接,服务端将一直阻塞等待,然后当客户端连接后,服务器会等待客户端发送数据(第二次阻塞),如果客户端没有发送数据,那么服务端将会一直阻塞等待客户端发送数据。
服务端从启动到收到客户端数据的这个过程,将会有两次阻塞的过程。这就是BIO的非常重要的一个特点,BIO会产生两次阻塞,第一次在等待连接时阻塞,第二次在等待数据时阻塞。
当服务端启动,客户端还没有请求连接服务器时,控制台结果如下: