Eine vorläufige Studie zum NIO-Quellcode

Überprüfen Sie zunächst den Prozess zum Erstellen von Server und Selektor

 * 相当于开启一个server端,并绑定为一个端口,用于客户端向此发起请求
 * 其中这个server端,是非阻塞的,selector帮助server端监听事件,并当事件到来时处理事件
public NIOServerDemo(int port) {
    
    

    try {
    
    
        this.port = port;
        selector = Selector.open();
        //创建 可选择通道并配置为非阻塞式
        //通过open方法创建 服务器套接字 通道
        ServerSocketChannel server = ServerSocketChannel.open();
		server.configureBlocking(false);
		
        server.register(selector, SelectionKey.OP_ACCEPT);  //注册感兴趣的事件
        server.bind(new InetSocketAddress(this.port)); //绑定指定端口
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

 
Beginnen wir mit der offenen Methode von Selector und schauen wir uns den Quellcode der Klasse java.nio.channels.Selector an. Sehen Sie, was open getan hat:

1. Selector.open();

Öffnen, um Selektor zu erstellen

selector = Selector.open();


public static Selector open() throws IOException {
    
    
    return SelectorProvider.provider().openSelector();
}

 
SelectorProvider.provider() stellt den Standardselektor bereit

public static SelectorProvider provider() {
    
    
    synchronized (lock) {
    
    
    //保证了整个Server程序中只有一个KQueueSelectorProvider对象
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
    
    
                public SelectorProvider run() {
    
    
                        if (loadProviderFromProperty())
                            return provider;
                        if (loadProviderAsService())
                            return provider;
   //会根据操作系统来返回不同的实现类,mac平台返回KQueueSelectorProvider
                        provider = sun.nio.ch.DefaultSelectorProvider.create();
                        return provider;
                    }
                });
    }
}

DefaultSelectorProvider.create erstellt standardmäßig KQueueSelectorProvider (in der Mac-Umgebung).

public static SelectorProvider create() {
    
    
    return new KQueueSelectorProvider();
}

Sehen Sie, was new KQueueSelectorProvider(); bewirkt

KQueueSelectorImpl(SelectorProvider var1) {
    
    
    super(var1);
    //关键方法
    long var2 = IOUtil.makePipe(false);
    //移位处理。
    this.fd0 = (int)(var2 >>> 32);
    this.fd1 = (int)var2;

    try {
    
    
        this.kqueueWrapper = new KQueueArrayWrapper();
        this.kqueueWrapper.initInterrupt(this.fd0, this.fd1);
        this.fdMap = new HashMap();
        this.totalChannels = 1;
    } catch (Throwable var8) {
    
    
        try {
    
    
            FileDispatcherImpl.closeIntFD(this.fd0);
        } catch (IOException var7) {
    
    
            var8.addSuppressed(var7);
        }

        try {
    
    
            FileDispatcherImpl.closeIntFD(this.fd1);
        } catch (IOException var6) {
    
    
            var8.addSuppressed(var6);
        }

        throw var8;
    }
}

IOUtil.makePipe(true)ist eine native Methode. Es gibt zwei Dateideskriptoren zurück: Das hohe Bit speichert den Dateideskriptor am Leseende des Kanals und die niedrigen 32 Bit speichern den Dateideskriptor am Schreibende.

 long var2 = IOUtil.makePipe(false);
 this.fd0 = (int)(var2 >>> 32);
 this.fd1 = (int)var2;

 
Platzieren Sie den FD am Schreibende und den FD am Leseende der zurückgegebenen Pipe in this.kqueueWrapper (Sie werden später herausfinden, dass dies dazu dient, das wakeup() des Selectors zu implementieren).

this.kqueueWrapper = new KQueueArrayWrapper();
this.kqueueWrapper.initInterrupt(this.fd0, this.fd1);

 

2. ServerSocketChannel.open()

Implementierung von ServerSocketChannel:

public static ServerSocketChannel open() throws IOException {
    
    
    return SelectorProvider.provider().openServerSocketChannel();
}


public ServerSocketChannel openServerSocketChannel() throws IOException {
    
    
    return new ServerSocketChannelImpl(this);
}


ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
    
    
    super(var1);
    this.fd = Net.serverSocket(true);
    this.fdVal = IOUtil.fdVal(this.fd);
    this.state = 0;
}

super(var1); var1 stellt SelectorProvider dar und der Standard-SelectorProvider ist KQueueSelectorProvider. ing

 

三. serverChannel.register()

Dann serverChannel.register(selector,SelectionKey.OP_ACCEPT);binden Sie den Selector und den Channel zusammen, d. h. binden den FD, der beim Erstellen eines neuen ServerSocketChannel erstellt wurde, an den Selector .

 
Bisher wurde der Server gestartet und die folgenden Objekte erstellt.

  1. KQueueSelector: Es handelt sich um ein Singleton-Objekt, das tatsächlich die API des Betriebssystems aufruft.
  2. KQueueSelectorImpl enthält den folgenden Inhalt.
    a. IOUtil.makePipe: Tatsächlich werden zwei FDs erstellt, eines für das Leseende und eines für das Schreibende.
    b. initInterrupt(this.fd0, this.fd1): Speichern Sie den im Selector registrierten FD, einschließlich des FD auf der Schreibseite der Pipe und des vom ServerSocketChannel verwendeten FD.

 

4. selector.select()

selector.select() ruft hauptsächlich doSelect von KQueueSelectorImpl auf

protected int doSelect(long var1) throws IOException {
    
    
    boolean var3 = false;
    if (this.closed) {
    
    
        throw new ClosedSelectorException();
    } else {
    
    
        this.processDeregisterQueue();

        int var7;
        try {
    
    
            this.begin();
            var7 = this.kqueueWrapper.poll(var1);
        } finally {
    
    
            this.end();
        }

        this.processDeregisterQueue();
        return this.updateSelectedKeys(var7);
    }
}

this.kqueueWrapper.poll() ist der Kern, der die in kqueueWrapper gespeicherte FD abfragt; die spezifische Implementierung besteht darin, die native Methode kevent0() aufzurufen.

 var7 = this.kqueueWrapper.poll(var1);
 
 
 int poll(long var1) {
    
    
    this.updateRegistrations();
    int var3 = this.kevent0(this.kq, this.keventArrayAddress, 128, var1);
    return var3;
}

private native int kevent0(int var1, long var2, int var4, long var5);

Schauen Sie sich übrigens die Logik von findNative an

// Invoked in the VM class linking code.
static long findNative(ClassLoader loader, String name) {
    
    
    Vector<NativeLibrary> libs =
        loader != null ? loader.nativeLibraries : systemNativeLibraries;
    synchronized (libs) {
    
    
        int size = libs.size();
        for (int i = 0; i < size; i++) {
    
    
            NativeLibrary lib = libs.elementAt(i);
            long entry = lib.find(name);
            if (entry != 0)
                return entry;
        }
    }
    return 0;
}

kevent0.() überwacht, ob der FD in kqueueWrapper Daten ein- oder ausgibt , was zu einer E/A-Blockierung führt, bis ein Datenlese- und -schreibereignis auftritt.

Da beispielsweise die FD von ServerSocketChannel auch in kqueueWrapper gespeichert ist, wird kevent0() zurückgegeben, solange ClientSocket ein Datenelement an ServerSocket sendet. Und da kqueueWrapper auch die FD des Schreibendes der Pipe in kqueueWrapper gespeichert hat,
usw Solange das Schreibende der Pipe an FD gerichtet ist, werden Daten gesendet, was auch dazu führt, dass poll0() zurückkehrt.
 
Wenn keine dieser beiden Situationen auftritt, blockiert kevent0() immer, d. h. selector.select( ) blockiert immer;
wenn eine dieser Situationen eintritt, kehrt selector.select() zurück. Verwenden Sie daher while(true) in run() von OperationServer, um sicherzustellen, dass poll() nach dem Selector weiterhin überwacht wird empfängt und verarbeitet die Daten.

 
 
KQueueSelectorImpl.wakeup()

public Selector wakeup() {
    
    
    synchronized(this.interruptLock) {
    
    
        if (!this.interruptTriggered) {
    
    
            this.kqueueWrapper.interrupt();
            this.interruptTriggered = true;
        }

        return this;
    }
}

wakeup() sendet ein Byte 1 über das Schreibende der Pipe send(scoutFd, &byte, 1, 0), um poll() aufzuwecken. Sie können also bei Bedarf (beim ing) selector.wakeup() aufrufen, um den aufzuwecken Selektor .

 
 

Referenz:
„Netty4-Kernprinzipien und handgeschriebenes RPC-Framework“

Supongo que te gusta

Origin blog.csdn.net/hiliang521/article/details/131217363
Recomendado
Clasificación