Netty(六):NioServerSocketChannel源码解析
我们在Netty学习系列五的最后提出了一些问题还没得到回答,今天来通过学习NioServerSocketChannel的源码来帮我们找到之前问题的答案。
先看一下NioServerSocketChannel的继承结构。
AttributeMap接口及DefaultAttributeMap主要是提供了体检属性和获取属性的能力,便于我们为Channel绑定额外的属性。
AbstractChannel实现了Channel接口,实现了Channel通用的行为和方法,我们在Netty学习系列四中已经介绍过了。
AbstractNioChannel抽象类关联了Channel接口与JDK的NIOChannel,也就是让底层的通信交给Nio来实现。
简单介绍下源码:
public abstract class AbstractNioChannel extends AbstractChannel { private static final InternalLogger logger =
InternalLoggerFactory.getInstance(AbstractNioChannel.class); private static final ClosedChannelException DO_CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractNioChannel.class, "doClose()"); //和Java NIO的Channel绑定
private final SelectableChannel ch;
//为SelectableChannel注册的时间
protected final int readInterestOp;
volatile SelectionKey selectionKey; boolean readPending; private final Runnable clearReadPendingRunnable = new Runnable() {
@Override
public void run() {
clearReadPending0();
}
}; /**
* The future of the current connection attempt. If not null, subsequent
* connection attempts will fail.
*/
private ChannelPromise connectPromise;
private ScheduledFuture<?> connectTimeoutFuture;
private SocketAddress requestedRemoteAddress; //构造函数,参数分别为父Channel,要封装的SelectableChannel和注册的感兴趣的事件
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
//将SelectableChannel设置为非阻塞
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
} throw new ChannelException("Failed to enter non-blocking mode.", e);
}
} //通道是否打开
@Override
public boolean isOpen() {
return ch.isOpen();
} //返回更具体的Unsafe子类
@Override
public NioUnsafe unsafe() {
return (NioUnsafe) super.unsafe();
} //返回内部封装的SelectableChannel
protected SelectableChannel javaChannel() {
return ch;
} //返回EventLoop更具体的子类
@Override
public NioEventLoop eventLoop() {
return (NioEventLoop) super.eventLoop();
} //返回SelectionKey
protected SelectionKey selectionKey() {
assert selectionKey != null;
return selectionKey;
} //已废弃方法
@Deprecated
protected boolean isReadPending() {
return readPending;
} //已废弃方法
@Deprecated
protected void setReadPending(final boolean readPending) {
if (isRegistered()) {
EventLoop eventLoop = eventLoop();
if (eventLoop.inEventLoop()) {
setReadPending0(readPending);
} else {
eventLoop.execute(new Runnable() {
@Override
public void run() {
setReadPending0(readPending);
}
});
}
} else {
// Best effort if we are not registered yet clear readPending.
// NB: We only set the boolean field instead of calling clearReadPending0(), because the SelectionKey is
// not set yet so it would produce an assertion failure.
this.readPending = readPending;
}
} /**
* Set read pending to {@code false}.
*/
protected final void clearReadPending() {
if (isRegistered()) {
EventLoop eventLoop = eventLoop();
if (eventLoop.inEventLoop()) {
clearReadPending0();
} else {
eventLoop.execute(clearReadPendingRunnable);
}
} else {
// Best effort if we are not registered yet clear readPending. This happens during channel initialization.
// NB: We only set the boolean field instead of calling clearReadPending0(), because the SelectionKey is
// not set yet so it would produce an assertion failure.
readPending = false;
}
} private void setReadPending0(boolean readPending) {
this.readPending = readPending;
if (!readPending) {
((AbstractNioUnsafe) unsafe()).removeReadOp();
}
} private void clearReadPending0() {
readPending = false;
((AbstractNioUnsafe) unsafe()).removeReadOp();
} //Unsafe的具体子类,增加了一些和NioChannel相关的特性
public interface NioUnsafe extends Unsafe {
//返回内部的SelectableChannel
SelectableChannel ch(); //连接完成
void finishConnect(); //读方法
void read(); //强制刷新
void forceFlush();
} //NioUnsafe的抽象实现
protected abstract class AbstractNioUnsafe extends AbstractUnsafe implements NioUnsafe { protected final void removeReadOp() {
SelectionKey key = selectionKey();
// Check first if the key is still valid as it may be canceled as part of the deregistration
// from the EventLoop
// See https://github.com/netty/netty/issues/2104
if (!key.isValid()) {
return;
}
int interestOps = key.interestOps();
if ((interestOps & readInterestOp) != 0) {
// only remove readInterestOp if needed
key.interestOps(interestOps & ~readInterestOp);
}
} //返回内部封装的Channel
@Override
public final SelectableChannel ch() {
return javaChannel();
} //connect方法,实际在使用时NioServerSocket是不支持connect的,但是NioSocket会支持
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
} try {
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
} boolean wasActive = isActive();
//调用具体子类的doConnect方法
if (doConnect(remoteAddress, localAddress)) {
//连接成功设置fulfillConnectPromise
fulfillConnectPromise(promise, wasActive);
} else {
//连接未成功
connectPromise = promise;
requestedRemoteAddress = remoteAddress; //根据配置的超时时间,设置超时任务,一旦到达超时时间则抛出连接失败的异常
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
} //添加监听器,如果期间操作成功了,则取消掉超超时任务
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
//运行出现异常,则设置Promise为失败
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
} private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (promise == null) {
// Closed via cancellation and the promise has been notified already.
return;
} // Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
// We still need to ensure we call fireChannelActive() in this case.
boolean active = isActive(); // trySuccess() will return false if a user cancelled the connection attempt.
boolean promiseSet = promise.trySuccess(); //active状态发生改变,现在已经连接成功
if (!wasActive && active) {
//pipeline产生Active事件在通道中流传
pipeline().fireChannelActive();
} // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
if (!promiseSet) {
close(voidPromise());
}
} private void fulfillConnectPromise(ChannelPromise promise, Throwable cause) {
if (promise == null) {
// Closed via cancellation and the promise has been notified already.
return;
} // Use tryFailure() instead of setFailure() to avoid the race against cancel().
promise.tryFailure(cause);
closeIfClosed();
} //连接完成,该方法会在连接成功后,由EventLoop调用
@Override
public final void finishConnect() { assert eventLoop().inEventLoop(); try {
boolean wasActive = isActive();
doFinishConnect();
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
// See https://github.com/netty/netty/issues/1770
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
}
} @Override
protected final void flush0() {
// Flush immediately only when there's no pending flush.
// If there's a pending flush operation, event loop will call forceFlush() later,
// and thus there's no need to call it now.
if (!isFlushPending()) {
super.flush0();
}
} @Override
public final void forceFlush() {
// directly call super.flush0() to force a flush now
super.flush0();
} private boolean isFlushPending() {
SelectionKey selectionKey = selectionKey();
return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;
}
} //判断EventLoop和Channel是否匹配
@Override
protected boolean isCompatible(EventLoop loop) {
return loop instanceof NioEventLoop;
} //注册
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//让内部的javaChannel先注册的interestOps为0
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
} @Override
protected void doDeregister() throws Exception {
eventLoop().cancel(selectionKey());
} //doBeginRead由read方法调用
@Override
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
} readPending = true;
//重新注册感兴趣的事件
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
} /**
* Connect to the remote peer
*/
protected abstract boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception; /**
* Finish the connect
*/
protected abstract void doFinishConnect() throws Exception; //分配直接内存
protected final ByteBuf newDirectBuffer(ByteBuf buf) {
final int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
ReferenceCountUtil.safeRelease(buf);
return Unpooled.EMPTY_BUFFER;
} final ByteBufAllocator alloc = alloc();
if (alloc.isDirectBufferPooled()) {
ByteBuf directBuf = alloc.directBuffer(readableBytes);
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
ReferenceCountUtil.safeRelease(buf);
return directBuf;
} final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
if (directBuf != null) {
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
ReferenceCountUtil.safeRelease(buf);
return directBuf;
} // Allocating and deallocating an unpooled direct buffer is very expensive; give up.
return buf;
} //分配直接内存
protected final ByteBuf newDirectBuffer(ReferenceCounted holder, ByteBuf buf) {
final int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
ReferenceCountUtil.safeRelease(holder);
return Unpooled.EMPTY_BUFFER;
} final ByteBufAllocator alloc = alloc();
if (alloc.isDirectBufferPooled()) {
ByteBuf directBuf = alloc.directBuffer(readableBytes);
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
ReferenceCountUtil.safeRelease(holder);
return directBuf;
} final ByteBuf directBuf = ByteBufUtil.threadLocalDirectBuffer();
if (directBuf != null) {
directBuf.writeBytes(buf, buf.readerIndex(), readableBytes);
ReferenceCountUtil.safeRelease(holder);
return directBuf;
} // Allocating and deallocating an unpooled direct buffer is very expensive; give up.
if (holder != buf) {
// Ensure to call holder.release() to give the holder a chance to release other resources than its content.
buf.retain();
ReferenceCountUtil.safeRelease(holder);
} return buf;
} //关闭方法
@Override
protected void doClose() throws Exception {
ChannelPromise promise = connectPromise;
if (promise != null) {
// Use tryFailure() instead of setFailure() to avoid the race against cancel().
promise.tryFailure(DO_CLOSE_CLOSED_CHANNEL_EXCEPTION);
connectPromise = null;
} ScheduledFuture<?> future = connectTimeoutFuture;
if (future != null) {
future.cancel(false);
connectTimeoutFuture = null;
}
}
}
AbstractNioChannel又有两个子类,分别是AbstractNioMessageChannel和AbstractNioByteChannel。两者的区别是前者的通道中封装处理的是Object,而后者的通道中封装处理的是ByteBuf(或FileRegion)。
对于NioServerSocketChannel而言,需要处理的是NioSocketChannel。因此它集成了AbstractNioMessageChannel。
AbstractNioMessageChannel源码:
public abstract class AbstractNioMessageChannel extends AbstractNioChannel {
boolean inputShutdown; //构造函数
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
//设置父Channel, 内部封装的JDKchannel和注册interestOp
super(parent, ch, readInterestOp);
} //返回Unsafe对象
@Override
protected AbstractNioUnsafe newUnsafe() {
return new NioMessageUnsafe();
} //读
@Override
protected void doBeginRead() throws Exception {
if (inputShutdown) {
return;
}
super.doBeginRead();
} //AbstractNioUnsafe对象的
private final class NioMessageUnsafe extends AbstractNioUnsafe { private final List<Object> readBuf = new ArrayList<Object>(); @Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config); boolean closed = false;
Throwable exception = null; try {
try {
//开始读操作,主要是调用子类的doReadMessages实现,从SelectableChannel中读取数据,并封装到readBuf
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
} allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
} //将读到的readBuf通过pipline,在通道中流通,便于被通道中的Handler处理
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
//清空
readBuf.clear();
//读完成,产生readCompleate事件
allocHandle.readComplete();
pipeline.fireChannelReadComplete(); //如果有异常,则产生异常事件
if (exception != null) {
closed = closeOnReadError(exception); pipeline.fireExceptionCaught(exception);
} //如果被关闭,则调用关闭
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
} //写操作,NioServerSocketChannel不支持写
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
final SelectionKey key = selectionKey();
final int interestOps = key.interestOps(); for (;;) {
Object msg = in.current();
if (msg == null) {
//如果注册了写事件,则移除写事件
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);
}
break;
}
try {
boolean done = false;
for (int i = config().getWriteSpinCount() - 1; i >= 0; i--) {
//具体的写操作交给子类实现(NioServerSocketChannel不支持写操作)
if (doWriteMessage(msg, in)) {
done = true;
break;
}
} if (done) {
in.remove();
} else {
// Did not write all messages.
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
key.interestOps(interestOps | SelectionKey.OP_WRITE);
}
break;
}
} catch (Exception e) {
if (continueOnWriteError()) {
in.remove(e);
} else {
throw e;
}
}
}
} /**
* Returns {@code true} if we should continue the write loop on a write error.
*/
protected boolean continueOnWriteError() {
return false;
} protected boolean closeOnReadError(Throwable cause) {
if (!isActive()) {
// If the channel is not active anymore for whatever reason we should not try to continue reading.
return true;
}
if (cause instanceof PortUnreachableException) {
return false;
}
if (cause instanceof IOException) {
// ServerChannel should not be closed even on IOException because it can often continue
// accepting incoming connections. (e.g. too many open files)
return !(this instanceof ServerChannel);
}
return true;
} //读和写的具体操作交给子类去实现
protected abstract int doReadMessages(List<Object> buf) throws Exception; protected abstract boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception;
}
最后来看NioServerSocketChannel源码:
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel { private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class); //产生NIOServerSocketChannel的方法
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
/**
* Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
* {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
*
* See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
*/
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
} private final ServerSocketChannelConfig config; //默认构造函数, ReflectivaChannelFactory利用反射创建Channel时,即是调用了这个方法
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
} /**
* Create a new instance using the given {@link SelectorProvider}.
*/
public NioServerSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
} //将NIO中的ServerSocketChannel封装成Netty的NioServerSocketChannel
public NioServerSocketChannel(ServerSocketChannel channel) {
//调用父类的构造函数,注意设置了interestOps为OP_ACCEPT
super(null, channel, SelectionKey.OP_ACCEPT);
//创建配置
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
} //返回以太网地址
@Override
public InetSocketAddress localAddress() {
return (InetSocketAddress) super.localAddress();
} //返回元数据信息
@Override
public ChannelMetadata metadata() {
return METADATA;
} //返回配置
@Override
public ServerSocketChannelConfig config() {
return config;
} //Channel是否活跃
@Override
public boolean isActive() {
//通过socket的bound状态来确定是否为active
return javaChannel().socket().isBound();
} //返回远端地址,ServerSocketChannel没有对应的远端地址
@Override
public InetSocketAddress remoteAddress() {
return null;
} //内部封装的JDK自带的Channel
@Override
protected ServerSocketChannel javaChannel() {
return (ServerSocketChannel) super.javaChannel();
} @Override
protected SocketAddress localAddress0() {
return SocketUtils.localSocketAddress(javaChannel().socket());
} //通过调用内部封装的JDK中的NIO channel来绑定地址
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
} //关闭通道
@Override
protected void doClose() throws Exception {
javaChannel().close();
} //读消息
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
//其实就是调用ServerSocketChannel的accept方法监听accept事件,返回SocketChannel
SocketChannel ch = SocketUtils.accept(javaChannel()); try {
//将JDK NIO中的channel封装成Netty的NioSocketChannel对象,添加进buf中,使其在Pipeline中传递
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;//返回数量
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t); try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
} return 0;
} //NIOServerSocketChannel不支持的部分操作 返回null 或者 UnsuppotedOperationException异常
@Override
protected boolean doConnect(
SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
throw new UnsupportedOperationException();
} @Override
protected void doFinishConnect() throws Exception {
throw new UnsupportedOperationException();
} @Override
protected SocketAddress remoteAddress0() {
return null;
} @Override
protected void doDisconnect() throws Exception {
throw new UnsupportedOperationException();
} @Override
protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
throw new UnsupportedOperationException();
} @Override
protected final Object filterOutboundMessage(Object msg) throws Exception {
throw new UnsupportedOperationException();
} /********************************************************************/ private final class NioServerSocketChannelConfig extends DefaultServerSocketChannelConfig {
private NioServerSocketChannelConfig(NioServerSocketChannel channel, ServerSocket javaSocket) {
super(channel, javaSocket);
} @Override
protected void autoReadCleared() {
clearReadPending();
}
} // Override just to to be able to call directly via unit tests.
@Override
protected boolean closeOnReadError(Throwable cause) {
return super.closeOnReadError(cause);
}
}
分析完源码,再来看看上一篇文章中提出的问题:
为什么一开始register中注册的interestOps值为0,而非OP_ACCEPT?又是何时会注册OP_ACCEPT呢?
首先我们通过分析NioServerSocketChannel的源码可以看到:
channelFactory会通过发射创建NioServerSocketChannel对象。而发射调用的构造函数中设置了readInterestOps的值为OP_ACCEPT。而在AbstractNioChannel的doBeginRead方法中又会将readInterestOps注册到channel。
根据方法名我们可以猜测在开始读之前,selectableChannel的interestOps会从0被改为OP_ACCEPT。
为了证实这点,我们需要弄清楚开始时register interestOps为0的时机和调用doBeginRead的时机。
首先注册interestOps为0是在AbstractNioChannel的doRegister方法中。我们知道这个方法发生在channel的注册阶段。
再看doBeginRead的函数调用:
之前已经介绍过了注册或者绑定成功后,会调用pipeline.fireChannelActive事件。此时的DefaultChannelPipeline除了传递channelActive事件之外,还会调用readIfAutoRead()。
这个方法会根据Config配置的AutoRead属性来决定是否调用read方法。
而这个属性默认是自动读的。于是就可以调用read方法,并最终为channel注册OP_ACCEPT事件。
Netty(六):NioServerSocketChannel源码解析的更多相关文章
- Netty系列之源码解析(一)
本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 当前:Netty 源码解析(一)开始 Netty 源码解析(二): Netty 的 Channel ...
- Netty 核心组件 EventLoop 源码解析
前言 在前文 Netty 启动过程源码分析 (本文超长慎读)(基于4.1.23) 中,我们分析了整个服务器端的启动过程.在那篇文章中,我们重点关注了启动过程,而在启动过程中对核心组件并没有进行详细介绍 ...
- netty(六) buffer 源码分析
问题 : netty的 ByteBuff 和传统的ByteBuff的区别是什么? HeapByteBuf 和 DirectByteBuf 的区别 ? HeapByteBuf : 使用堆内存,缺点 ,s ...
- 十六.jQuery源码解析之Sizzle设计思路.htm
为了便于后面的叙述,需要了解一些相关术语和约定. 并列选择器表达式:"div,p,a"====>div,p,a是并列的. 块表达式:"div>p"中 ...
- Netty 源码解析(六): Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第六篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一 ):开始 Netty ...
- Netty 源码解析(三): Netty 的 Future 和 Promise
今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...
- Netty 源码解析(九): connect 过程和 bind 过程分析
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(八): 回到 Channel 的 register 操作
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...
- Netty 源码解析(四): Netty 的 ChannelPipeline
今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
随机推荐
- Shell:homework
1.判断/etc/inittab文件是否大于100行,如果大于,则显示”/etc/inittab is a big file.”否则显示”/etc/inittab is a small file.”# ...
- c#声明数组
声明二维数组时候,出现报错: string[][] dataTable; dataTable = new string[rows][cols]; 无效的秩说明符: 应为","或&q ...
- Java第二十九天,文件及目录的管理,File类
一.基础知识点 1.路径分隔符 (1)什么是路径分隔符? 这个多被应用在环境变量设置当中,例如当我设置Path环境变量时,多个环境变量的路径要用 ':'(Windows系统用封号分隔)或 ':'(Li ...
- MySQL学习之路2-数据库and数据表的基本操作
数据库基本操作 查看.选择数据库: show databases; use <databasename>; 创建数据库:create database <dbname> cha ...
- String 对象-->split() 方法
1.定义和用法 split() 方法用于把一个字符串分割成字符串数组. 语法: string.split(separator,limit) 参数: separator:可选.字符串或正则表达式,从该参 ...
- Apache Hudi 设计与架构最强解读
感谢 Apache Hudi contributor:王祥虎 翻译&供稿. 欢迎关注微信公众号:ApacheHudi 本文将介绍Apache Hudi的基本概念.设计以及总体基础架构. 1.简 ...
- shell脚本编程(ubantu)
项目 内容 这个作业属于那个课程 这里是链接 作业要求在哪里 这里是链接 学号-姓名 17041506-张政 作业学习目标 了解shell脚本的概念及使用:掌握shell脚本语言的基本语法:学习简单的 ...
- synchronized的锁是针对多线程的
synchronized的锁是针对多线程的,从线程的角度去思考才能真正弄明白. Java的synchronized使用方法总结 1. 把synchronized当作函数修饰符时 这也就是同步方法,那这 ...
- 【python实现卷积神经网络】定义训练和测试过程
代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...
- 跨域cookies 共享
这是由于,本地调试.涉及到cookies的问题 想要跨域使用的问题 vue 中的mian.js中放入下面代码 import axios from 'axios' axios.defaults.with ...