Netty入门P2

本文深入探讨了传统的IO模型及其在高并发场景下存在的问题,包括线程资源浪费和上下文切换效率低下等。同时,文章详细介绍了NIO模型如何解决这些问题,通过引入Selector机制减少线程使用,提高效率,并采用Buffer方式读写数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IO模型

IO客户端:

/**
 * @program: learnnetty
 * @description: IO客户端
 * @create: 2020-04-29 10:47
 **/
public class IOClient {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    //目标ip以及端口号
                    Socket socket = new Socket("127.0.0.1", 8080);
                    while (true) {
                        //获取输出流并写入数据
                        socket.getOutputStream().write(
                                (new Date() + ": hello world").getBytes()
                        );
                        Thread.sleep(2000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

IO服务端:

/**
 * @program: learnnetty
 * @description: IO服务端
 * @create: 2020-04-29 10:47
 **/
public class IOServer {
    public static void main(String[] args) throws IOException {
        //监听端口
        final ServerSocket serverSocket = new ServerSocket(8080);
        //监听端口线程
        new Thread(new Runnable() {
            public void run() {
                //不断轮询
                while (true) {
                    try {
                        //执行Accept操作
                        final Socket socket = serverSocket.accept();
                        //读取数据线程
                        new Thread(new Runnable() {
                            public void run() {
                                try {
                                    int len;
                                    byte[] data = new byte[1024];
                                    //获取socket的输入流
                                    InputStream inputStream = socket.getInputStream();
                                    //读取数据
                                    while ((len = inputStream.read(data)) != -1){
                                        System.out.println(new String(data, 0, len));
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }).start();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

对于IO模型主要存在以下缺点:

  1. 线程资源受限:线程是操作系统中非常宝贵的资源,同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费;
  2. 线程上下文切换效率低下:单机CPU的核心数固定,线程数过多会导致操作系统频繁的切换线程,导致应用效率下降;
  3. IO编程中,数据的读写是以字节流为单位的;

NIO模型

NIO解决了IO模型中线程资源受限的问题
模型
IO模型中,一个连接接入时,会创建一个线程,线程对应一个while死循环,而死循环的目的是为了不断监听这个连接上是否有数据可读,但是使用死循环的副作用是在其它情况下(无数据可读时)这些while死循环会浪费资源。

在NIO模型中,将n条线程对应n个死循环改为了只有一个死循环,这个死循环则由一个线程控制。在NIO模型中添加了selector,在连接接入时,在NIO中不再建立新的while死循环去监听是否有新的数据可读,而是把这条新接入的连接注册到selector上,通过检查selector就可以批量的检查出是否数据可读的连接,然后再读取数据。

NIO解决了IO模型中线程上下文切换效率低下的问题

由于NIO模型中线程数量大大降低,线程切换的效率因此大大提高。

NIO中不再以字节流为单位读写数据

IO模型读写是面向流的,一次性能从流中读取一个或者多个字节,并且读取完后无法再读取,如需缓存数据需要自己实现。

NIO模型读写是面向缓存(buffer)的,可以读取buffer中任意一个字节数据,不许自己实现缓存。

NIO服务端示例代码:

/**
 * @program: learnnetty
 * @description: NIO服务端
 * @create: 2020-04-29 15:17
 **/
public class NIOServer {
    public static void main(String[] args) throws IOException {
        //服务端选择器
        final Selector serverSelector = Selector.open();
        //客户端选择器
        final Selector clientSelector = Selector.open();
        //处理连接线程
        new Thread(new Runnable() {
            public void run() {
                try {
                    //获取Channel
                    ServerSocketChannel listenerChannel = ServerSocketChannel.open();
                    //绑定端口
                    listenerChannel.socket().bind(new InetSocketAddress(8080));
                    //是否阻塞
                    listenerChannel.configureBlocking(false);
                    //注册
                    listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);

                    while (true){
                        //检查是否有新的连接
                        if (serverSelector.select(1) > 0){
                            Set<SelectionKey> keys = serverSelector.selectedKeys();
                            Iterator<SelectionKey> keyIterator = keys.iterator();
                            while (keyIterator.hasNext()){
                                SelectionKey key = keyIterator.next();
                                //连接是否是可Accept的
                                if (key.isAcceptable()){
                                    try {
                                        //获取Channel
                                        SocketChannel clientChannel = ((ServerSocketChannel)key.channel()).accept();
                                        clientChannel.configureBlocking(false);
                                        //注册
                                        clientChannel.register(clientSelector, SelectionKey.OP_READ);
                                    }finally {
                                        keyIterator.remove();
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //读取数据线程
        new Thread(new Runnable() {
            public void run() {
                try {
                    while (true){
                        //是否连接又可读取的数据
                        if (clientSelector.select(1) > 0){
                            Set<SelectionKey> keys = clientSelector.selectedKeys();
                            Iterator<SelectionKey> keyIterator = keys.iterator();

                            while (keyIterator.hasNext()){
                                SelectionKey key = keyIterator.next();
                                //当前数据是否可读
                                if (key.isReadable()){
                                    try {
                                        //获取Channel
                                        SocketChannel clientChannel = (SocketChannel)key.channel();
                                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                                        //读取数据
                                        clientChannel.read(buffer);
                                        buffer.flip();
                                        System.out.println(Charset.defaultCharset().newDecoder().decode(buffer).toString());
                                    }finally {
                                        keyIterator.remove();
                                        key.interestOps(SelectionKey.OP_READ);
                                    }
                                }
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

NIO模型核心思路:

  • NIO模型通常会有两个线程,每个线程绑定一个轮询器selector。
  • 服务端检测到新的连接后,不再是创建一个新的线程,而是直接将其注册到某个selector上。
  • selector通常在while死循环内,如果某一时刻有新的连接接入/连接内有可读数据,就可以通过Selector.select()方法发现,并进行后续处理。
  • 在数据的读写阶段使用Buffer。

参考自掘金大佬

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值