IO通信之多路复用

简介: 仅个人心得,仅供参考,不足之处望请指出。

什么是多路复用器

 聊多路复用器之前呢,先回归昨天的NIO,NIO的出现解决了BIO阻塞线程、一连接一线线程问题。但是它有缺点吗,答案是肯定的。
NIO的缺点
 我们把问题放大,如果有一万个连接但是只有一个连接是有数据的,但是对于我们的NIO来说,他每次都会遍历所有连接并且去调用内核,我们都是到用户态切换内核态的消耗还是很大的,这里就会涉及到操作系统的知识,当用户态切换到内核态时会有一个80中断,需要经历的过程大致为,保存用户线程的现场信息,根据中断描述符从中断向量表中查找指令,调用指令,恢复现场,如果我们只有一个连接有数据但是会进行9999次无效的80中断是不是会造成不必要的资源消耗。
如何解决这一个问题
 第一种多路复用器selector横空出世,他从根本问题出发,NIO不是80中断消耗大吗,那直接去内核去遍历所有的连接不就好了,这也是selector的做法,大致流程是这样的
image.png
还有一个和selector 逻辑想用的多路复用器poll,他和selector不同之处在于selector是有连接限制的。


下面是重头戏epoll

 在追踪内核之前我们先把epoll的大致流程讲一下,然后我们去追踪内核去证明。
image.png
图片优点笼统,笔者下面一一解释
 首先要说一点,目前我说认知的所有IO模型都需要socket、bind、listen这几个指令。
 首先还是需要生成一个socket然后通过bind命令将socket与目标端口绑定然后去监听。
接下来就是epoll特有的指令了
 第一个epoll_create(size)这个指令会在内存开辟两个空间,一个空间用来存放需要监听的文件描述符以及需要监听对应文件描述符的事件,第二个空间是用来当有事件到来时,将对应的文件描述符以及数据放入这个缓冲区中,当程序调用accept、read时就回去缓冲区中找有没有对应的事件。
 第二个指令epoll_ctl(epfds,op,fds,event),这个指令是将对应的文件描述符以及需要监听的事件交由epoll去管理,下面解释一下参数,第一个参数epoll的文件描述符,第二个参数表示需要啥操作,第三个文件描述符表示需要监听的对象,第四个参数为需要监听的事件。
 第三个指令epoll_wait(epfds,events,maxevent,timeout),这个指令是来监听epoll中所有需要监听的事件有没有返回,如果有返回就将对应的fds与数据放入缓冲区中。参数对应的意思为,epoll的文件描述符、需要监听的事件、最大返回事件个数、超时时间。
 笔者认为有了这些基础我们可以更好的去理解epoll的原理。参数的意思可以使用man int 对应的命令,这个命令来查看。

重点注意

 epoll有一个非常了不起的地方就是他对于收到的事件数据放入缓冲区的操作与我们程序accept/read能达到异步,这点是epoll非常厉害的地方。


下面我们来一一证明

 笔者写了一个简陋的java代码如下

server端

public class NIOServer {
    private static ByteBuffer bb = ByteBuffer.allocate(1024);
    private static ServerSocketChannel ssc = null;
    private static Selector selector = null;
    private static  int port = 8888;
    public static void main(String[] args) throws IOException {
        ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(port));
        selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动。。");
        while (true){
            while (selector.select(500)>0){
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                while (it.hasNext()){
                    SelectionKey selectionKey = it.next();
                    it.remove();
                    if(selectionKey.isAcceptable()){
                        handlekey(selectionKey);
                    }else if(selectionKey.isReadable()){
                        handleRead(selectionKey);
                    }
                }
            }
        }
    }
    public static void  handlekey(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel cssc = (ServerSocketChannel) selectionKey.channel();
        SocketChannel client = cssc.accept();
        client.configureBlocking(false);
        client.register(selector,SelectionKey.OP_READ);
        System.out.println("新的客户端加入"+client.getRemoteAddress());
    }
    public static void handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel cssc = (SocketChannel) selectionKey.channel();
        int read = cssc.read(bb);
        if(read>0){
            System.out.println(new String(bb.array(),0,read));
        }
    }
}

client端

public class NIOClient {
    public static void main(String[] args) {
        try (SocketChannel socketChannel = SocketChannel.open();){
            socketChannel.connect(new InetSocketAddress("localhost", 8888));
            ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
            String str = "你好,服务器我是客户端";
            sendBuffer.put(str.getBytes());
            sendBuffer.flip();
            socketChannel.write(sendBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

JavaNIO默认的是使用epoll,如果发现使用的不是epoll我们可以加虚拟机参数。如果没问题参数可以不加

image.png

 我们通过strace命令来对追踪我们的内核调用。打开我们的out文件
image.png
 我们先来关注这两条指令,第一个socket是对应生成socket的,并返回了一个文件描述符5,接下来看第二个指令,注意参数,第一个参数就是对应的我们socket的文件描述符,第三个参数很重要NONBLOCK表示设置我们的socket为非阻塞的。我们继续向下看。
image.png
这两个指令应该很熟悉,就是bind端口,然后去监听。就不过多解释了。

下面就是我们要重点关注的epoll的指令了。

image.png
 这个指令上面已经解释过了就是用来创建我们的epoll的并分配资源。我们可以使用man命令来证实一下,如下图

image.png

这个就是epoll_create命令的解释,感兴趣可以自己去看一下。我们继续看下一条指令。

image.png

 下一个指令时epoll_ctl;这些参数大家应该可以看个大概。第一个就是我们创建epoll的时候返回的文件描述符8,第二个字面意思就是添加吗,就是往epoll中去添加socket的文件描述符5,最后的参数是需要监听的事件,以及对应的文件描述符。
 下一个指令就是epoll_wait命令,第一个参数就是我们epoll的文件描述符8。而且有没有注意到超时时间,这个是不是和我们java程序中设置的超时时间是一样的,这点我们需要注意,我们先使用man 命令来看一下,指令的介绍。

image.png

image.png

 我们只关注返回值,有兴趣的读者可以自己翻阅一下,返回值这里说,成功时,epoll_wait()返回为请求的I/O准备的文件描述符的数量,如果在请求的超时毫秒内没有文件描述符准备就绪,则返回零。当发生错误时,epoll_wait()返回-1,我们这里的返回值为0表示当前还没有事件。

下面我们启动客户端jar包

image.png

 我们的日志文件中出现了变化,图中画红线的指令,就是表示epoll_wait收到了事件,然后发现是一个连接事件,然后就去创建连接并返回文件描述符9,我们继续向下看。

image.png

 我们看到又调用了epoll_ctl将文件描述符9就是对应的我们的连接交由epoll去管理,然后再调用epoll_wait又获取了事件,这个事件就是我们客户端给服务器端发的数据。我们继续向下看。

image.png

发现后面的wait总是能返回事件,而且都一样,这说明我们程序中有很大的bug,我打断点调试后发现每次都会进到可读的逻辑中,我自己在处理读数据的时候最后 selectionKey.cancel();我不知道合理不合理,但是确实解决了问题。

 自己学习的心得希望能帮到大家,也许这种东西只有在面试的时候才有用,那就拿他来跟面试官说吧。

目录
相关文章
|
2月前
|
存储 监控 Linux
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
【Linux IO多路复用 】 Linux下select函数全解析:驾驭I-O复用的高效之道
64 0
|
4月前
|
存储 Linux 调度
io复用之epoll核心源码剖析
epoll底层实现中有两个关键的数据结构,一个是eventpoll另一个是epitem,其中eventpoll中有两个成员变量分别是rbr和rdlist,前者指向一颗红黑树的根,后者指向双向链表的头。而epitem则是红黑树节点和双向链表节点的综合体,也就是说epitem即可作为树的节点,又可以作为链表的节点,并且epitem中包含着用户注册的事件。当用户调用epoll_create()时,会创建eventpoll对象(包含一个红黑树和一个双链表);
72 0
io复用之epoll核心源码剖析
|
4月前
|
存储 网络协议
TCP服务器 IO多路复用的实现:select、poll、epoll
TCP服务器 IO多路复用的实现:select、poll、epoll
37 0
|
12天前
|
存储 监控 网络协议
IO多路复用
IO多路复用
|
2月前
|
传感器 编解码 C语言
【软件设计师备考 专题 】IO设备、通信设备的性能,以及基本工作原理
【软件设计师备考 专题 】IO设备、通信设备的性能,以及基本工作原理
41 1
|
2月前
|
NoSQL Java Linux
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
【Linux IO多路复用 】 Linux 网络编程 认知负荷与Epoll:高性能I-O多路复用的实现与优化
69 0
|
2月前
|
JavaScript Unix Linux
IO多路复用:提高网络应用性能的利器
IO多路复用:提高网络应用性能的利器
|
4月前
|
网络协议 架构师 Linux
一文说透IO多路复用select/poll/epoll
一文说透IO多路复用select/poll/epoll
172 0
|
4月前
|
网络协议 Linux
2.1.1网络io与io多路复用select/poll/epoll
2.1.1网络io与io多路复用select/poll/epoll
|
1月前
|
存储 缓存 安全
Java 中 IO 流、File文件
Java 中 IO 流、File文件