26、Nio(Selector(选择器))处理accept)

Nio(Selector(选择器))处理accept)

Selector(允许单个线程管理多个channel)其实是分情况的在没有事件发生的时候是个阻塞线程停止,(线程休息一下)。

这一节就是讲的,一个服务器端的serverSocketChannel(用来模拟服务器端)先注册到selector,然后会将其中的ssckey复制到selector的seletorkeys这个集合(其中selector是一个东西,后面的selector.selectorKeys是一个东西)。后面才是客户端建立连接的通道,也方法selectorKeys这个集合,最后他会遍历这个集合打印出来。。。(我们事件的流程都是先注册,再复制,再轮询复制的东西(我们channel注册给selector,selector会把一个key和channel关联起来的。(这个key包含了channel关注的一切问题。(三个参数嘛))))

  1. selector是前面阻塞后面非阻塞,也就是说没有事件的时候是阻塞,有事件的时候就变为非阻塞,(这个selector是在前面的非阻塞模式加强的,非阻塞就是线程一直处于运行态(阻塞方法不会让线程进入阻塞态),selector让线程最开始就是阻塞态(从阻塞态开始))
  2. 让我们来理解一下流程,selector先让线程直接从阻塞模式开始运行,若是有事件来的话(事件的作用就是来唤醒这个阻塞态的(阻塞态变为就绪态)),然后就是变为就绪态再等着cpu调度来进入运行态了(这里注意我们前面说的这个selector是阻塞态的升级(让线程不那么累),就是到他进入运行态就会是前面的非阻塞模式了(阻塞方法不能让他进入阻塞状态))。后面若是没有事件来唤醒这个selector造成的阻塞态那么它就一直是阻塞态了,线程就是休息的状态咯,不用在非阻塞的模式下一直运行咯,seletor的select(底层是以wait()方法)

第一步:我么先创建选择器selector.open()

第二步:将channel注册到selector(这个channel还必须是非阻塞的,即channel.configureBlocking(false))注册用的channel.register(selector,对什么感兴趣,)

Connect, 即连接事件(TCP 连接), 对应于SelectionKey.OP_CONNECT

Accept, 即确认事件, 对应于SelectionKey.OP_ACCEPT

Read, 即读事件, 对应于SelectionKey.OP_READ, 表示 buffer 可读.

Write, 即写事件, 对应于SelectionKey.OP_WRITE, 表示 buffer 可写.

第三步:selector.select方法(没有时间发生就线程阻塞,有时间发生就线程恢复运行态(先变为就绪态))

第四步:!!!得到一个selector.selectorKeys的集合(和前面注册到selector的通道(还关注的事件)是两个东西)其中这个是将上面的selector中的复制到这上面来处理,我们先拿到事件集合(是一个set集合)。若是想要删除的话我们需要用迭代用迭代器(Iterator)来遍历(我们想要再集合遍历的时候还想要删除要用迭代器遍历(若是我们用增强for遍历时还想要删除就会报错))

第五步:是我我们根据我们的遍历(selector.selectorKeys)去建立连接

测试:

  1. (首先我来说一下我们这个流程,最开始我们直接运行这个server也就是服务器端,他只会打印我们上面的那个ssckey(是我们跟serverSectorchannel(通道)建立的key(hash))这里是前面的服务器与selector注册的通道)他们没有true死循环因为其中的没有客户端的连接事件触发,被seletor.select阻塞了。这里就是一个简单的打印上面的ser注册到serverSocketchannel这个通道的key啊,傻逼

然后我们启动一个客户端,这个就会触发上面的兴趣事件咯,所以就会到一个set集合我们再遍历这个集合再去打印,最后是我们去进行这个连接里咯(根据前面的客户端连接建立连接),这里打印了相同的东西,是前面我们启动服务器端,已经将severSocketchannel注册到seletor啊,后面我们启动客户端,他往下走进行遍历。然后会将其中的ssckey复制到selector的seletorkeys这个集合(其中selector是一个东西,后面的selector.selectorKeys是一个东西)。后面才是客户端建立连接的通道,也方法selectorKeys这个集合,最后他会遍历这个集合打印出来。。。

  1. 我们再启动一个客户端,其中前面的连接ssckey是一样的。以为都是感兴趣的那个客户端的建立连接后的,只有最后的channel是不同的。每个客户端事件虽然被监听到了,但是和客户端建立的通道不一样嘛。(selector就是一个管理员,管理通道的。我们前面用的是serverSocketChannel通道来,模拟客户端嘛(这个我们注册就去就会一直存在咯))

总结:

说一下这个程序的理解,其中我们的ServerSoketChannel 是一个通道,他的作用就是可以监听那些tcp连接,这个accept就是连接建立。没有连接进来就是阻塞态咯。

这里我们演示只是一个让客户端来连接我们的服务器。这个服务器的意思其实就是一个通道而已,serverSocketChannel通道。Seletor的作用就是将通道注册到seletor。管理多个channel。这里我们还是理一下(以前),上面的是通道就是一个服务器,下面的这个accept是一个连接通道,再下面的read才是数据的读写。

  1. 现在我们的步骤是先创建一个seletor,
  2. 再将我们的服务器(用serversocketchannel通道)注册到seletor(.register去监听事件发生(是什么事件,在那个channel),连接事件再这里被监听到了,所以是这个连接事件只要一出来,就被上面监听到了)。注册后会给我们一个key
  3. 指明我们这个监听器(管理员)感兴趣的是什么事件。(去监听这个专属的事件发生)这样我们就将我们的通道注册到我们seletor了。
  4. 下面就是我们去看看这个事件什么时候发生(这里是连接事件)。我们如何知道我们事件是否发生呢,我们用的就是seletor的select方法。(解决我们前面的非阻塞问题的(线程一直运行))没有事件就阻塞是阻塞态,有事件就线程恢复运行态。
  5. 假如我们有客户端连接了,那么就会经过上面的select方法,向下运行,但是是我们向下运行我们要怎么处理这个事件呢。我们要先拿到这个事件集合(上面的事件集合(set)).selectedKeys。上一步的select方法弄到的啊(最开始我们所做的就是打开一个客户端(也就是将那个serverSocketchannel注册到了我们slector,并没有事件被监听到(发现)),所以他就会被阻塞到上面的select方法,
  6. 这个时候我们去打开一个客户端,那么这个连接事件就被我们监听到了,所以线程开始马上向下运行了(由阻塞态变为了运行态))

!!!重点,我们将serverSocketchannel注册到了我们selector,那么就是说selector成为这个管理员了,他全权代表这个服务器了,也就是说他这个服务器也不为过的。所以seletor监听到我们事件他就会将这个事件放到这个selector的事件集合里(也就是selector.selectorKeys())

  1. 这里才是重点。傻逼为什么会打印相同的!!!我们只有监听到这个事件并放到了这个事件集合后我们才能去遍历进行连接.Accept(也就是前面的仅仅只是连接请求而已),当我们仅仅只运行这个服务器端,我们所打印的仅仅是个服务器端的地址。后面我们的客户端请求过来,是selector里面先前已经有那个服务器端通道的key了。所以后面我们遍历会再给他打印出来(肯定是相同的)

而后面我们的客户端accept建立的建立通道就是不一样的咯(和我们的serverSocketchannel 不一样)

猜你喜欢

转载自blog.csdn.net/logtcm4/article/details/127804054