小言_互联网的博客

关于Java你不知道的那些事之Java NIO使用案例-NIO深入学习(Java程序员的必备技能)

405人阅读  评论(0)

Java NIO使用案例-NIO深入学习

前言

本文是基于上一篇文章《关于Java你不知道的那些事之Java NIO(Java程序员的必备技能)》的基础上继续深入学习JavaNIO有关知识,如果没有看过上篇文章的同学可以先去看上篇文章,回来在看本篇文章。

BIO

概念

在提到NIO之前,我们说先看看BIO,也就是Blocking IO,阻塞IO,我们首先实现一个最基本的网络通信。

	/**
	 * QQ客户端
	 *
	 * @author: 轻狂书生FS
	 * @create: 2020-09-28-11:09
	 */
	public class QQClient {
   
	    public static void main(String[] args) throws IOException {
   
	        Socket socket = new Socket("127.0.0.1", 8080);
	        socket.getOutputStream().write("我来发送".getBytes());
	    }
	}
/*---------------------------------------------------------*/
	/**
	 * QQ服务端
	 *
	 * @author: 轻狂书生FS
	 * @create: 2020-09-28-11:09
	 */
	public class QQServer {
   
	    static byte[] bytes = new byte[1024];
	
	    public static void main(String[] args) throws IOException {
   
	        while(true) {
   
	            ServerSocket serverSocket = new ServerSocket();
	
	            // 绑定IP地址
	            serverSocket.bind(new InetSocketAddress(8080));
	
	            System.out.println("服务器等待连接....");
	
	            // 阻塞
	            Socket socket = serverSocket.accept();
	
	            System.out.println("服务器连接....");
	
	            // 阻塞
	            System.out.println("等待发送客户端数据");
	            socket.getInputStream().read(bytes);
	
	            System.out.println("数据接收成功:" + new String(bytes));
	        }
	    }
	}

首先运行Server端,然后在运行Client端

服务器等待连接....
服务器连接....
等待发送客户端数据
数据接收成功:我来发送 

通过运行结果我们能发现,服务端线程会进行两次阻塞,首先第一次就是在等待连接的时候会阻塞,然后是等待数据的时候又会阻塞。

因此在服务器端,不活跃的线程,比较多,所以我们考虑单线程

BIO怎么改成非阻塞

我们从上面的阻塞开始说起

    while(true) {
   
		ServerSocket serverSocket = new ServerSocket();
	    // 绑定IP地址
	    serverSocket.bind(new InetSocketAddress(8080));
	
	    // 阻塞
	    Socket socket = serverSocket.accept();
	
	    // 阻塞
	    socket.getInputStream().read(bytes);

	}

假设我们现在访问淘宝网页,就相当于我们本机与淘宝的服务器建立了连接,然后淘宝服务器就会等待我的请求,但是假设我只是打开了网页,什么事情都不做,如果我的线程被一直阻塞的话,那就不能为服务进行接收了,这样就会卡在这里,但是我也不能为了这个不活跃的用户单独开启线程,因为他非常消耗我们的CPU资源,这样是会,非阻塞IO的用处就来了

在提到NIO之前,我们在将刚刚的QQServer改成非阻塞版本的伪代码,也就是我们通过一个关键字,设置他不阻塞,但是因为BIO里面没有这个方法,因此他就会一直阻塞着,所以把它称为阻塞的

	// 设置非阻塞
	serverSocket.setConfig();

伪代码如下:

	/**
	 * 单线程版服务器,NIO的伪代码
	 *
	 * @author: 轻狂书生FS
	 * @create: 2020-09-28-12:04
	 */
	public class OneThreadServer {
   
	    public static void main(String[] args) throws IOException {
   
	        ServerSocket serverSocket = new ServerSocket();
	        serverSocket.bind(new InetSocketAddress(8080));
	
	        // 套接字列表,用于存储
	        List<Socket> socketList = new ArrayList<>();
	        // 缓冲区
	        byte[] bytes = new byte[1024];
	
	        // 设置非阻塞
	//        serverSocket.setConfig();
	
	        while(true) {
   
	            // 获取连接
	            Socket socket = serverSocket.accept();
	
	            // 如果没人连接
	            if(socket == null) {
   
	                System.out.println("没人连接");
	
	                // 遍历循环socketList,套接字list
	                for(Socket item : socketList) {
   
	                    int read = socket.getInputStream().read(bytes);
	                    // 表示有人发送东西
	                    if(read != 0) {
   
	                        // 打印出内容
	                        System.out.println(new String(bytes));
	                    }
	                }
	            } else {
   
	                // 如果有人连接,把套接字放入到列表中
	                socketList.add(socket);
	
	                // 设置非阻塞
	//                serverSocket.setConfig();
	                // 遍历循环socketList,套接字list
	
	                // 遍历循环socketList,套接字list
	                for(Socket item : socketList) {
   
	                    int read = socket.getInputStream().read(bytes);
	                    // 表示有人发送东西
	                    if(read != 0) {
   
	                        // 打印出内容
	                        System.out.println(new String(bytes));
	                    }
	                }
	
	            }
	        }
	    }
	}

上述也提到了,因为BIO里面不提供不阻塞的方法,因此无法将其改成非阻塞的

NIO

但是在NIO里面,就提供了让其不阻塞的方法

在之前我们需要创建通信,BIO的方法如下所示:

	ServerSocket serverSocket = new ServerSocket();
	serverSocket.bind(new InetSocketAddress(8080));

在NIO里面提出了通道的概念,其实代码和上面类似,只不过上面是创建了一个Socker连接,而下面是创建了一个通道

	ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
	serverSocketChannel.bind(new InetSocketAddress(8080));

我们创建的通道,默认是阻塞的,但是我们可以通过下面的方式,将其设置成非阻塞的

	// 设置非阻塞
	serverSocketChannel.configureBlocking(false);

下面我们进入while(true)的方法里面,因为原来是通过Socket获取到一个连接

	// 获取连接
	Socket socket = serverSocket.accept();

但是我们都知道,上述的连接是阻塞的,也就是说如果没有连接过来,它会一直阻塞的,因此Java提出了一个新的类,SockerChannel,它里面 提供了非阻塞的方法

	// 设置非阻塞
	serverSocketChannel.configureBlocking(false);

完整代码:

	/**
	 * NIO版QQ服务器
	 *
	 * @author: 轻狂书生FS
	 * @create: 2020-09-28-12:16
	 */
	public class QQServerByNIO {
   
	    public static void main(String[] args) throws IOException {
   
	        // 创建一个通道
	        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
	        serverSocketChannel.bind(new InetSocketAddress(8080));
	
	        // 定义list用于存储SocketChannel,也就是非阻塞的连接
	        List<SocketChannel> socketChannelList = new ArrayList<>();
	        byte [] bytes = new byte[1024];
	        // 缓冲区
	        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
	
	        // 设置非阻塞
	        serverSocketChannel.configureBlocking(false);
	
	        while(true) {
   
	            SocketChannel socketChannel = serverSocketChannel.accept();
	
	            // 但无人连接的时候
	            if(socketChannel == null) {
   
	
	                // 睡眠一秒
	                try {
   
	                    TimeUnit.SECONDS.sleep(1);
	                } catch (InterruptedException e) {
   
	                    e.printStackTrace();
	                }
	                System.out.println("无人连接");
	                for(SocketChannel item: socketChannelList) {
   
	                    int len = item.read(byteBuffer);
	                    if(len > 0) {
   
	                        // 切换成读模式
	                        byteBuffer.flip();
	                        // 打印出结果
	                        System.out.println("读取到的数据" + new String(byteBuffer.array(), 0, len));
	                    }
	                    byteBuffer.clear();
	                }
	
	            } else {
   
	                // 但有人连接的时候
	
	                // 设置成非阻塞
	                socketChannel.configureBlocking(false);
	
	                // 将该通道存入到List中
	                socketChannelList.add(socketChannel);
	
	                for(SocketChannel item: socketChannelList) {
   
	                    int len = item.read(byteBuffer);
	                    if(len > 0) {
   
	                        // 切换成读模式
	                        byteBuffer.flip();
	                        // 打印出结果
	                        System.out.println("读取到的数据" + new String(byteBuffer.array(), 0, len));
	                    }
	                    byteBuffer.clear();
	                }
	            }
	        }
	    }
	}

总结

欢迎点赞+关注,点点关注不迷路,谢谢。


转载:https://blog.csdn.net/LookForDream_/article/details/108996678
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场