1 深入理解Netty, 前置知识从java nio 到 hotspot 到 linux

本章本来三部分,

第一部分是对 linux epoll机制的一些简述

第二部分是 java nio 到  hotspot  的一些简述

第三部 是一些简要总结

第一部分:

select/poll机制:

        select/poll 是 epoll之前提出的一种多路复用机制,它大概的原理是在一个线程内,将用户层的fd集合拷贝进内核,进行遍历,然后返回 可读,可写的fd集合。

         由前一段可以看出select/poll的问题是,他每次遍历都需要将fd从用户态复制到内核态。如果连接数一多,对性能造成很大的影响。
        

  

epoll机制:

         epoll 是基于事件通知机制 与 多路复用,在内核级别实现的机制。

        它的原理是, 当一个客户端向服务发起一个请求是,服务端会创建一个socket 或者说叫 fd,来跟该这个请求做一一对应。

1   首先 epoll 是将 该对应的 fd,放进一颗红黑树里

2    对该fd 注册事件,假设这里是可读事件

3    当这个客户端连接对服务器发送数据时,服务器网卡会发起一个中断,调用该 socket 相应的回调,将其放进 一个 链表里

        

        在linux 上提供了一组 api ,分别是 epoll_create,epoll_ctl,epoll_wait 供我们使用。它们对应的作用分表是

1  epoll_create 用于初始化,对应的结构体

2  epoll_ctl 对fd做相应的操作,可以将fd 加入到红黑树,可以给fd注册相应的时间

3  epoll_wait 如果链表上有相应的fd,返回链表上的fd

为什么要用红黑树:

        二叉树实现简单,但是查询性能不稳定,常因为动态更新导致性能退化问题。 AVL树是一种高度平衡二叉树,查找效率非常高,但是因为要维护这种高度平衡性,所以在每次删除和新增都可能需要付昂贵的代价。

        而二叉树,是从二三四叉树演变来的,具体可以看看<算法>这本书,所以每次调整时,只需要调整有限的节点,所以在维护平衡的成本上,要比AVL树要低得多。所以,红黑树的插入,删除,查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支持这种工业级应用,我们更倾向于这种性能稳定的平衡二叉树

第二部分:

java级别的NIO:

java 的 nio 的底层使用的是 linux 的 epoll 机制。我们先从一个简单的demo开始。

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 {
    public static void main(String[] args) throws Exception {

        // new
        ServerSocketChannel serverSocketChannel
                            = ServerSocketChannel.open();


        Selector selector = Selector.open();

        serverSocketChannel.socket().bind(new InetSocketAddress(6666));

        serverSocketChannel.configureBlocking(false);

        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while(true){
            if(selector.select(1000) == 0 ){
                System.out.println("-================");
                continue;
            }


            Set<SelectionKey>  selectionKeys = selector.selectedKeys();

            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();


            while(keyIterator.hasNext()){

                SelectionKey key = keyIterator.next();
                if(key.isAcceptable()){
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("");

                    socketChannel.configureBlocking(false);

                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if(key.isReadable()){
                    SocketChannel channel = (SocketChannel) key.channel();

                    ByteBuffer buffer = (ByteBuffer) key.attachment();

                    channel.read(buffer);

                    System.out.println("");

                }

                keyIterator.remove();

            }


        }


    }
}


java nio 的核心组件有三个,分别是 channel ,buffer,selector。

channel:

 channel,如果对于网络IO来说,你们可以简单理解为 socket,它封装了socket,对它进行读写的操作

buffer:

 数据的载体或者容器

selector:

 selector 底层就是封装  epoll_create,epoll_ctl,epoll_wait 这几个 API。

其中大致的逻辑是,socket先将自己注册到 selector,然后当可操作的时候,就将该socket返回,然后就可以做相应的操作

现在我们来分析一下 Selector的关键源代码:

        其中 native 函数 在 openjdk\jdk\src\solaris\native\sun\nio\ch\EPollArrayWrapper.c 文件中,这里我删掉native 函数部分代码,方便阅读

        我们主要分析 Selector.open(),erverSocketChannel.register,selector.select(),这三部分代码,因为分别包含了 epoll 关键的API

1  Selector.open():

Selector.open()-----> openSelector() ----> new EpollArrayWrapper() ---- > this.epollCreate()

其中 epollCreate()是native 函数

epollCreate()对应代码

Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
{
    int epfd = epoll_create(256);
    return epfd;
}

2  erverSocketChannel.register:

将对应的事件加入到集合,然后在select的时候批量 epoll_ctl

epoll_ctl()对应代码

Java_sun_nio_ch_EPollArrayWrapper_epollCtl(JNIEnv *env, jobject this, jint epfd,
                                           jint opcode, jint fd, jint events)
{
    struct epoll_event event;
    int res;
    event.events = events;
    event.data.fd = fd;
    RESTARTABLE(epoll_ctl(epfd, (int)opcode, (int)fd, &event), res);

}

3 selector.select:

最终会调用 EPollSelectorImpl.doSelect() ---- > 再调用 EPollArrayWrapper.poll()--->epollCtl()和epollWait()

其中 epollWait() 是native 函数

epollWait()对应代码

Java_sun_nio_ch_EPollArrayWrapper_epollWait(JNIEnv *env, jobject this,
                                            jlong address, jint numfds,
                                            jlong timeout, jint epfd)
{
    struct epoll_event *events = jlong_to_ptr(address);
    int res;
    if (timeout <= 0) {           /* Indefinite or no wait */
        RESTARTABLE(epoll_wait(epfd, events, numfds, timeout), res);
    } else {                      /* Bounded wait; bounded restarts */
        res = iepoll(epfd, events, numfds, timeout);
    }

    return res;
}

第三部分:

由此,我们可以总结出,上面 java nio server的demo,可以简化为 以下伪代码:

fun(){
    
    int serverFd = new Socket();
  
    int epfd = epoll_create;
 
     // 监控主fd
    epoll_ctl(epfd,EPOLL_CTL_ADD,serverFd,是否有新连接);
    


     while(1){
        // 获取就绪事件
       int count = epoll_wait(epfd)
        
       for(i=0;i<count;i++){
          if(是新连接事件){
               // 添加客户端fd
               epoll_ctl(epfd,EPOLL_CTL_ADD,clientFd,是否可读可写);
          }
          if(是可读事件){

          } 
       }
      
     }

}

先使用 epoll_create 初始化内核结构,然后使用 epoll_ctl 监听 服务器fd 的 是否有连接事件,

如果有可连接事件,则继续将客户端fd继续监听起来,给对应的客户端fd注册 可读,可写事件

其实,可以看出,java nio,其实是比较底层的,如果要在实际项目中使用,还需要考虑很多东西,比如线程模型,tcp 拆包,粘包等等。所以出现了很多框架来解决这方面的问题,如netty,jetty等等。

参考:

极客时间  算法与数据结构

<算法>

尚硅谷netty视频

还有其他一些博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值