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

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

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

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

查看所有标签

猜你喜欢:

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

C专家编程

C专家编程

[美] Peter Vander Linde / 徐波 / 人民邮电出版社 / 2002-12 / 40.00元

《C专家编程》展示了最优秀的C程序员所使用的编码技巧,并专门开辟了一章对C++的基础知识进行了介绍。 书中对C的历史、语言特性、声明、数组、指针、链接、运行时、内存,以及如何进一步学习C++等问题作了细致的讲解和深入的分析。全书撷取几十几个实例进行讲解,对C程序员具有非常高的实用价值。 这本《C专家编程》可以帮助有一定经验的C程序员成为C编程方面的专家,对于具备相当的C语言基础的程序员......一起来看看 《C专家编程》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试