原理和实战完美诠释NIO的强大之处

栏目: 服务器 · 发布时间: 5年前

内容简介:平时工作中,很大部分时间都投入了业务。我们对于一些框架、设计思想等都没有太去的关注,第一个深入一个技术底层是比较枯燥与孤独的;第二个就是没有人带领去用一个有趣或者通俗易懂的教导;但如果真的是明白了那些大牛们的思维方式,我们都会异口同声的称赞他们,真的就是牛逼啊。学习底层的一些原理知识,我建议有2种方式:1、多看源代码,在源代码中与之前接触到的理论相结合,最后会恍然大悟;

平时工作中,很大部分时间都投入了业务。我们对于一些框架、设计思想等都没有太去的关注,第一个深入一个技术底层是比较枯燥与孤独的;第二个就是没有人带领去用一个有趣或者通俗易懂的教导;但如果真的是明白了那些大牛们的思维方式,我们都会异口同声的称赞他们,真的就是牛逼啊。

学习底层的一些原理知识,我建议有2种方式:

1、多看源代码,在源代码中与之前接触到的理论相结合,最后会恍然大悟;

2、多跟大神们交流,在没接触之前,你不会觉得自己有多菜;

之前写过的Demo:

案例1: https://github.com/chengcheng222e/io-learn.git

案例2: https://github.com/chengcheng222e/vernon-socket

案例3: https://github.com/chengcheng222e/vernon-netty.git

1、Socket通信编程

1.1 只能接受一次连接的代码块

package com.cyblogs.io.learn.bio;

import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
import java.util.Scanner;

public class BIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket=new ServerSocket(8080);
        System.out.println("BIOServer has started,listening on port:" + serverSocket.getLocalSocketAddress());

        Socket clientSocket = serverSocket.accept(); //等待被接受
        System.out.println("Conection from " + clientSocket.getRemoteSocketAddress());

        Scanner input=new Scanner(clientSocket.getInputStream());
        String request=input.nextLine();
        System.out.println(request);

        String response="From BIOServer response: " + request + "\n";
        clientSocket.getOutputStream().write(response.getBytes());
    }
}

我们启动服务,然后通过 telnet 的方式来摸你 client 来发起通讯。

启动服务

原理和实战完美诠释NIO的强大之处

连接服务

原理和实战完美诠释NIO的强大之处

服务端返回

原理和实战完美诠释NIO的强大之处

这里就会发现,如果我们不去发起一个连接,那么服务端就就会一直停留在这里。

Scanner input=new Scanner(clientSocket.getInputStream());

1.2 接受多次连接的代码块

但这个程序有问题,就是接受到一个请求后,代码就接走完了。主进程 main 函数会立马跳出。我们改造一下,希望得到的效果是,我们可以通过多个 client 去连接服务端。

package com.cyblogs.io.learn.bio;

import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
import java.util.Scanner;

public class BIOServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("BIOServer has started,listening on port:" + serverSocket.getLocalSocketAddress());

        while (true) {
            Socket clientSocket = null;
            try {
                clientSocket = serverSocket.accept(); //等待被接受
                System.out.println("Conection from " + clientSocket.getRemoteSocketAddress());

                Scanner input = new Scanner(clientSocket.getInputStream());
                while (true) {
                    if (input.hasNext()) {
                        String request = input.nextLine();
                        if ("quit".equals(request)) {
                            break;
                        }
                        System.out.println(request);
                        String response = "From BIOServer response " + request + "\n";
                        clientSocket.getOutputStream().write(response.getBytes());
                    }
                }
            } finally {
                if (clientSocket != null) clientSocket.close();
            }
        }
    }
}

这类就很能明显的看出来。当多个客户端连接之后,之后之前的客户端退出之后,后面的的客户端才能关于服务端进行交互。

原理和实战完美诠释NIO的强大之处

上述的例子就是一个简单的例子来描述一个IO的阻塞,而且非常的浪费资源。原因为是只有一个线程,而且还是阻塞的。

2、如何提高性能?

原理和实战完美诠释NIO的强大之处

如上图所示,之前说是有一个线程在做核心业务的处理而且是阻塞的。那如果说每次来一个请求,我都单独分出一个线程来做。但是为了考虑到每次来都分配一个,到时候CPU会处理不过来,我就利用一个线程池来管理一个固定大小的线程池。如果超过了我的处理能力,我就让它排队。感觉上这个思维比之前的就好很多,那具体我们再看看改造后的代码。

原理和实战完美诠释NIO的强大之处

package com.cyblogs.io.learn.bio;

import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;

public class BIOServer {

    public static void main(String[] args) throws IOException {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("BIOServer has started,listening on port:" + serverSocket.getLocalSocketAddress());

        RequestHandler requestHandler = new RequestHandler();// 为了让多线程去切换
        while (true) {
            Socket clientSocket = null;
            clientSocket = serverSocket.accept(); //等待被接受
            System.out.println("Conection from " + clientSocket.getRemoteSocketAddress());
            // 多线程处理
            executor.submit(new ClientHandler(clientSocket, requestHandler));
        }
    }
}
package com.cyblogs.io.learn.bio;

public class RequestHandler {

    public String handler(String request) {
        return "From BIOServer response " + request + "\n";
    }
}
package com.cyblogs.io.learn.bio;

import java.io.IOException;  
import java.net.Socket;  
import java.util.Scanner;

public class ClientHandler implements Runnable {

    private Socket clientSocket;
    private RequestHandler requestHandler;

    public ClientHandler(Socket clientSocket, RequestHandler requestHandler) {
        this.clientSocket = clientSocket;
        this.requestHandler = requestHandler;
    }


    public void run() {
        try {
            Scanner input = new Scanner(clientSocket.getInputStream());
            while (true) {
                if (input.hasNext()) {
                    String request = input.nextLine();
                    if ("quit".equals(request)) {
                        break;
                    }
                    System.out.println(request);
                    String response = requestHandler.handler(request);
                    clientSocket.getOutputStream().write(response.getBytes());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);

        }
    }
}

为了摸你这个场景,我打开4个客户端。

原理和实战完美诠释NIO的强大之处

第4个客户端会处理阻塞中,那么真正的堵塞在哪儿呢?

String request = input.nextLine();

所以,之前的场景,不管如何都属于 BIO 的范畴, BIO 最终还是存在瓶颈。为了提高性能,就出现了 NIO

问题:之前的设计存在哪些设计缺陷?(留一个问题)

3、了解NIO的设计

之前的问题都是在于一直在等待用的输入,所以每个线程还是一直非常被浪费。所以,解决问题的关键在于能不能不要一直等用户的输入,而是在真正在输入的时候再创建出一个线程来处理。处理完毕后资源又会得到释放。

package com.cyblogs.io.learn.nio;

import com.cyblogs.io.learn.bio.RequestHandler;

import java.io.IOException;  
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;  
import java.util.Set;

public class NIOServer {  
    // Channel[Server Client] Selector Buffer
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8081));
        System.out.println("BIOServer has started, listening on port: " + serverSocketChannel.getLocalAddress());

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        RequestHandler requestHandler = new RequestHandler();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
            // 整个过程,单线程只有这类是阻塞的
            int select = selector.select();
            if (select == 0) {
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable()) {
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = serverChannel.accept();
                    System.out.println("Connection from " + clientChannel.getRemoteAddress());
                    clientChannel.configureBlocking(false);
                    clientChannel.register(selector, SelectionKey.OP_READ);
                }

                if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    channel.read(buffer);
                    String request = new String(buffer.array()).trim();
                    buffer.clear();
                    System.out.println(String.format("From %s : %s", channel.getRemoteAddress(), request));
                    String response = requestHandler.handler(request);
                    channel.write(ByteBuffer.wrap(response.getBytes()));
                }
                iterator.remove();
            }

        }
    }
}

但是,对于NIO最好修饰的一个框架就是Netty。可以参考一下上面vernon-netty的一个小demo。这边文章主要是初步了解一个BIO到NIO过渡的手写过程。后面我们再聊聊Netty是如何封装NIO的。

https://netty.io/

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTML & CSS设计与构建网站

HTML & CSS设计与构建网站

[美] Jon Duckett / 刘涛、陈学敏 / 清华大学出版社 / 2013-1 / 59.80元

欢迎您选择一种更高效的学习HTML和CSS的方式。不管您设计和建立新网站,还是想更好地控制现有网站,都可以在《HTML & CSS 设计与构建网站》一书的指导下创建出用户友好、令用户赏心悦目的Web内容。我们知道,编码是一项令人望而生畏的工作,而本书却采用有别于许多传统编程书籍的新颖编排方式,将使您收到事半功倍的学习效果。 每一页都在短小精悍的示例代码的引导下,简明直观、直截了当地阐述一个新......一起来看看 《HTML & CSS设计与构建网站》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具