netty之io模型

Posted better_hui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了netty之io模型相关的知识,希望对你有一定的参考价值。

目录

前言

一、BIO

二、NIO

三、IO多路复用

代码

select / poll

epoll


前言

所有的IO的前戏都是这三部曲

1、new socket()

2、bind 端口

3、监听 端口

一、BIO

package netty.bio;
​
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
​
/**
 * @author hee.wong
 * @version 1.0.0
 * BIO  阻塞IO模型
 * serverSocket.accept  inputStream.read 都是阻塞的 , 未获取到数据是不返回的。
 * BIO为了能够支持多个连接 , 只能是以多线程的方式 ,  下面有多线程的代码
 * 特点 : 一个链接一个线程
 * 优点 : 简单、可以接受多个链接
 * 缺点 : 线程内存损耗、cpu调度损耗
 */
public class Bioserver {
    public static void main(String[] args) throws Exception{
        //1、打开一个socket
        ServerSocket server = new ServerSocket();
        //2、绑定一个端口 bind
        server.bind(new InetSocketAddress(8081));
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("监听中");
            //3、监听并接收
            Socket socket = server.accept();
            System.out.println(socket.getPort()+"-----请求进来了");
            handleInThread(socket);
        }
    }
    private static void handleInThread(final Socket socket) throws Exception{
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    System.out.println("开始读取数据");
                    InputStream inputStream = socket.getInputStream();
                    byte[] data = new byte[1024];
                    while (true){
                        int read = inputStream.read(data);
                        if(read>0){
                            System.out.println("读取客户端数据"+new String(data,0,read));
                        }else{
                            break;
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
​
    }
}

二、NIO

BIO的缺点是非常明显的,大量的请求链接会耗费服务器的大量线程资源,因此无法支持海量的链接请求。NIO应运而生,nio的accept和 recv 都可以设置为非阻塞 , 即没有事件时,直接返回,基于这一点,我们就可以实现在一个线程内,接收处理N个链接。

package netty.nio;
​
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
​
/**
 * @author hee.wong
 * @version 1.0.0
 * NIO 针对java而言就是nio下的api接口 ,针对linux而言就是非阻塞IO , 
 * 非阻塞IO的允许调用方发起一个内核调用,然后轮询是否有自己感兴趣的事件发生
 * 特点:单线程可以处理多个链接请求
 * 优点:规避了BIO多线程的问题
 * 缺点:C10K的问题,我们假设有10000个链接 , 但是只有1个连接有数据可以读取,但是我们每次都要循环尝试读取这10000
 *       个链接,那么9999个请求是无效的,浪费时间和系统资源的。
 *  NIO的缺点简单来讲就是  循环调用 
 */
public class NioServer {
​
    public static void main(String[]args) throws Exception{
        //打开一个socket
        ServerSocketChannel channel = ServerSocketChannel.open();
        //绑定一个端口
        channel.bind(new InetSocketAddress(8082));
        //设置为非阻塞
        channel.configureBlocking(false);
        List<SocketChannel> sockets = new ArrayList<>();
        while (true){
            TimeUnit.SECONDS.sleep(1);
            //接受请求 , 请注意这里是非阻塞的,非阻塞的
            SocketChannel socket = channel.accept();
            if(socket==null){
                System.out.println("没有链接");
            }else{
                System.out.println(socket.getRemoteAddress()+"-----链接来了");
                //设置这个链接为非阻塞 , 意思是没有数据时直接返回
                socket.configureBlocking(false);
                sockets.add(socket);
            }
            for (SocketChannel socketChannel : sockets) {
                ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                int read = socketChannel.read(buffer);
                if(read < 0){
                      continue;
                }else{
                    System.out.println("开始读取数据");
                    buffer.flip();
                    byte[] data = new byte[read];
                    buffer.get(data);
                    System.out.println(new String(data));
                    buffer.clear();
                }
            }
        }
    }
}

三、IO多路复用

非阻塞的IO模型 , 有循环系统调用的问题 , 而每一次系统调用都会发生软中断 , 涉及到系统执行现场的保存,是很损耗性能的。我们是否可用将这循环调用 , 优化为一次批量调用,答案是肯定的 , 这就是IO多路复用,简单来讲就是我们发起一次系统调用,询问内核有哪些链接有可读的事件。

代码

package netty.nio2;
​
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
​
/**
 * @author hee.wong
 * @version 1.0.0
 * IO多路复用 , 是针对NIO循环系统调用的缺点而来的,这里有了复用器selector的概念,
 * 我们只需要发起一次系统调用,把所有的fds 传递给内核,让内核循环遍历 ,然后把有事件的fd, 告诉我就可以了
 * select poll弊端:
 *      1、重复传递fd 
 *      2、每次select / poll , 都需要遍历全脸的fd数组
 *      
 * 对此epoll有了优化:
 *      1、应用程序不用传递fds给我了 , 我在内核帮你记录着
 *      2、另外我还开辟了另外一块空间 , 专门保存有事件的fd , 
 *  至于如何从复用的fd空间 , 将有事件的fd 移动到另外一块空间 , 是系统内核另起线程 帮我们搞定的, 我们可以漠不关心
 */
public class Nio2Server {
    public static void main(String[] args) throws Exception{
        //打开一个通道channel
        ServerSocketChannel server = ServerSocketChannel.open();
        //设置为非阻塞
        server.configureBlocking(false);
        //绑定端口
        server.bind(new InetSocketAddress(8083));
        //打开一个selector
        //epoll模型下 , oepn ->   epoll_create = fd3 , 返回一个文件描述符
        Selector selector = Selector.open();
        //注册 将我们打开的socket 注册到多路复用器上 , 由复用器负责监听事件, 注意这里关心的事件是连接时间
        // select 、 poll : jvm 会开辟一个数组 , 存放监听的文件描述 fd4
        // epoll : epoll_ctl(fd3 , ADD , fd4 ,ACCEPT) , 在selector对应的文件描述符空间内,添加一个fd4 , 关心的是链接请求
        server.register(selector , SelectionKey.OP_ACCEPT);
        // select / poll , 调用的是内核 select(fd4) poll(fd4)
        // epoll 是调用的  epoll_wait() , 这一步是阻塞的 , 等待关心的事件发生。
        System.out.println("多路复用的服务器起来了");
        while (true){
            while (selector.select(500) > 0){
                //
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                if(selectionKeys.size()>0){
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()){
                        SelectionKey selectionKey = iterator.next();
                        int interestOps = selectionKey.interestOps();
                        switch (interestOps){
                            case SelectionKey.OP_ACCEPT:
                                //获取这个链接
                                SocketChannel client = server.accept();
                                //设置为非阻塞
                                client.configureBlocking(false);
                                //还是在复用器上注册 , 关心的时间是read
                                client.register(selector , SelectionKey.OP_READ);
                                System.out.println("链接来了===="+client.getRemoteAddress());
                                break;
                            case SelectionKey.OP_READ:
                                SocketChannel clientRead = (SocketChannel) selectionKey.channel();
                                ByteBuffer buf = ByteBuffer.allocate(1024);
                                int len = 0 ;
                                while ((len = clientRead.read(buf))>0){
                                    buf.flip();
                                    System.out.println(new String(buf.array(),0 , len) );
                                    buf.clear();
                                }
                                break;
                            default:
                                continue;
                        }
                        iterator.remove();
                    }
                }
            }
        }
    }
}
​

select / poll

epoll

以上是关于netty之io模型的主要内容,如果未能解决你的问题,请参考以下文章

聊聊Netty那些事儿之从内核角度看IO模型

聊聊Netty那些事儿之从内核角度看IO模型

聊聊Netty那些事儿之从内核角度看IO模型

了解Netty之基本理论篇--IO模型

Netty核心之线程模型

Netty精粹之基于EventLoop机制的高效线程模型