看了三遍,终于理解了,下面把我遇到的问题发一下;
很多小伙伴都说第七章跨度大,我也是,刚开始我也是一脸蒙蔽,老师突然就讲解 clink 几个核心类了,突然有点不知所措,所以在听第一遍的时候,都有点懵,so do i ,但看了第二遍之后,就有些理解了,下面一起分析一下;
老师这里的优化,只是对 服务端中的 ClientHandler 中的数据监听进行了优化,先抓住这个点,假如让你去优化 拿到客户端的数据,你怎么去优化,怎么去解耦;带着这个问题入手,会方便很多。
理解之后,觉得老师对解耦的处理非常棒;先看流程图:
然后看 imple,即 SocketChannelAdapter 和 IoSelectorProvider;
SocketChannelAdapter :
数据的读写肯定得拿到 SocektChannel ,拿到之后配置非阻塞模式,然后它的 OP_READ 和 OP_WRITE 是通过继承 sender 和 receiver 接口,把具体的实现交给 IoSelectorProvider。如:
@Override
public boolean receiverAsync(IoArgs.IoArgsEventListener listener) throws IOException {
if (isClosed.get()){
throw new IOException("Current channel is closed!");
}
receiverEventListener = listener;
//读注册给 ioprovider,让它完成具体的实现
return ioProvider.registerInput(channel,inputCallback);
}
IoSelectorProvider:
这里才是 需要关注的点,在 SocketChannelAdapter 已经把注册的事件专递给 IoSelectorProvider 了,所以,也看 register 部分:
@Override
public boolean registerInput(SocketChannel channel, HandleInputCallback callback) {
return registerSelection(channel,readSelector,
SelectionKey.OP_READ,inRegInput,inputCallbackMap,callback) != null;
}
这里的注册,其实就是把当前的channel 的 OP_READ 事件,去检测是否已经注册,没有就先注册到 selector 中,并把当前的 selectionKey 和 Runnable 放到 map 中。如下具体实现
/**
* 注册 selection
* @param channel
* @param selector
* @param registerOps
* @param locker ,多线程,需要等待注册完成,才能去遍历和拿到数据,所以用原子锁
* @param map
* @param runnable
* @return
*/
private static SelectionKey registerSelection(SocketChannel channel,Selector selector,
int registerOps,AtomicBoolean locker,
HashMap<SelectionKey,Runnable> map,
Runnable runnable){
synchronized (locker){
//设置锁定状态
locker.set(true);
try {
//唤醒 selector,让selector不处于 select() 状态
selector.wakeup();
//如果 channel 已经有注册过东西
SelectionKey key;
if (channel.isRegistered()){
key = channel.keyFor(selector);
//如果已经该 key 已经被注册过了
if (key != null){
//把key重新加入
key.interestOps(key.interestOps() | registerOps);
}
}else{
//如果还没有被注册过
key = channel.register(selector, registerOps);
//并把当前的 key 和 runnable 填充到map
map.put(key,runnable);
}
return key;
} catch (Exception e) {
// e.printStackTrace();
}finally {
//解除锁定,表示注册完成
locker.set(false);
}
}
return null;
}
最后,再去理解 startRead 里面的方法,就比较简单了,就是把 selector 中拿到的 selectionkey 与 map 中对比,若存在,则用线程池去启动即可。
注意! IoConntext 的 setUp 方法,被写成静态类且在main函数中启动,所以IoSelectorProvider只被初始化一次,而SocketChannelAdapter 是每次有新客户端过来时,就会重新注册一个。所以老师在 IoSelectorProvider 中才对线程等待同步这块做了优化;
那换成是你,你怎么抽离和优化这块部分呢?
理论+实践,系统且深入掌握Socket核心技术,从容应对各种Socket应用场景的不二之选
了解课程