什么是新连接接入?以及新连接接入前,Netty处于什么状态

netty的服务端NioServerSocketChannel初始化,注册在BossGroup中的一条NioEventLoop中,并且给NioServerSocketChannel中维护的jdk原生的ServerSocketChannel绑定好了端口后, EventLoop启动,开始轮询工作...

这时候 EventLoop 它在轮询什么? 其实它在轮询监听当初NioServerSocketChannel经过二次注册感兴趣的事件时, 告诉 Selector,让Selector关注自己身上可能会出现 OP_ACCEPT 事件, 这合情合理,因为对于Netty的主从Reactor线程模型中, BossGroup中的channel只关心OP_ACCEPT 也就是用户的请求建立连接事件

netty的新连接接入要做哪些工作?

看上图,netty的新连接接入,对应这个线程模型中我圈出来的部分, 主要步骤如下

  • 服务端Selector轮询到客户端请求建立连接
  • 处理请求
    • 从服务端维护的JDK 原生ServerSocketChannel中accept()客户端的channel
    • 使用new的方法 将客户端的Channel封装成 NioSocketChannel
      • 层层往上调用super(),初始化channel的组件
      • 创建channel的配置类对象 config
    • 向下传播channelRead事件
      • 给客户端的channel设置相关参数
      • 将客户端的channel注册在 workerGroup 中的轮询算法选出的 EventLoop
      • 将jdk原生的SocketChanel注册进 EventLoop中的选择器中
      • 传播channelregist事件
      • 传播channelActive事件
        • 给客户端的channel二次注册netty可以处理的感兴趣的事件

这是我总结的新连接接入的流程,从上面分析的开始检查新链接,终止的标志是,把客户端的NioSocketChannel二次注册在EventLoop上,成为Netty可以处理的chanel为止

入口:NioEventLoop处理IO事件

当服务端的事件循环检测到有io事件时,使用它的processSelectedKeys();处理,源码如下:

private void processSelectedKeys() {
// todo selectedKeys 就是经过优化后的keys(底层是数组) , 默认不为null
if (selectedKeys != null) {
processSelectedKeysOptimized();
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}

当有了新IO请求进来, jdk原生的Selector将SelectionKey放入存放感兴趣的key的集合中,而这个集合现在就是netty通过反射的方式强制替换为以数组为数据结构的selectedKeys, 数组不为空, 跟进processSelectedKeysOptimized();,源码如下: 解析写在源码下面:

private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
// null out entry in the array to allow to have it GC'ed once the Channel close
// todo 数组输出空项, 从而允许在channel 关闭时对其进行垃圾回收
// See https://github.com/netty/netty/issues/2363
// todo 数组中当前循环对应的keys质空, 这种感兴趣的事件只处理一次就行
selectedKeys.keys[i] = null; // todo 获取出 attachment,默认情况下就是注册进Selector时,传入的第三个参数 this===> NioServerSocketChannel
// todo 一个Selector中可能被绑定上了成千上万个Channel, 通过K+attachment 的手段, 精确的取出发生指定事件的channel, 进而获取channel中的unsafe类进行下一步处理
final Object a = k.attachment();
// todo if (a instanceof AbstractNioChannel) {
// todo 进入这个方法, 传进入 感兴趣的key + NioSocketChannel
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
} if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
selectedKeys.reset(i + 1); selectAgain();
i = -1;
}
}
}

处理感兴趣的事件, 想想,需要什么他才能进一步处理呢? 需要下面两点:

  • 这个感兴趣的事件是啥?

    • 在这了就是上面的 k
  • 哪个channel出现的Selector感兴趣的事件?
    • 在这里是通过 attachment拿到的 a ,其实不就是服务端的NioServerSocketChannel ?

另外它把NioServerSocketChannel向上强转成了AbstractNioChannel这是为什么呢?

答:

第一点:

在我写的上一篇Chanel的架构体系中,我们知道,Netty的NioXXXChannel其实是netty的,基于原生的jdk的chanel的封装,而在他的整个继承体系中,这个AbstractNioChannel就负责维护jdk原生的channel, 知道了这有啥用? 当然有用,我们要去给客户端channel接生了,原生服务端channel.accept()==客户端channel

第二点:

针对数据的读写都是unsafe中,回想是哪个类中定义了读取channel中IO数据的抽象模板函数呢? AbstractNioChannel, 是它新增的内部接口,从而进客户端和服务对针对chanel的不同特化read进行不同的实现

好, 有了这两个条件,继续跟进processSelectedKey(k, (AbstractNioChannel) a);看它是如何处理, 源码如下:

  • 获取到服务端的unsafe对象(数据读写)
  • 根据k的readOps,进行计算决定执行unsafe.read();
// todo 服务端启动后,方法被用用处理新链接,  可以模拟 telnet localhost 8899 新链接的介入
// todo 处理selectedkey
// todo netty底层对数据的读写都是 unsafe完成的
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
// todo 这个unsafe 也是可channel 也是和Channel进行唯一绑定的对象
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) { // todo 确保Key的合法
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
return;
}
if (eventLoop != this || eventLoop == null) { // todo 确保多线程下的安全性
return;
}
unsafe.close(unsafe.voidPromise());
return;
}
// todo NioServerSocketChannel和selectKey都合法的话, 就进入下面的 处理阶段
try {
// todo 获取SelectedKey 的 关心的选项
int readyOps = k.readyOps();
// todo 在read() write()之前我们需要调用 finishConnect() 方法, 否则 NIO JDK抛出异常
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps( ); unsafe.finishConnect();
} // Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// todo 同样是检查 readOps是否为零, 来检查是否出现了 jdk 空轮询的bug
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}

下面我们进入 unsafe.read();, 直接跳进去,直接进入到了AbstractNioChannel的抽象内部类,因为上面说了,做了向上强制类型转换,我们源码如下:

    /**
* Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
*/
public interface NioUnsafe extends Unsafe {
/**
* Return underlying {@link SelectableChannel}
*/
SelectableChannel ch(); /**
* Finish connect
*/
void finishConnect(); void forceFlush();
}

具体的实现是谁? 因为我们是服务端的channel, 所以实现类是:NioMessageUnsafe, 进入查看他的源码: 下面这段代码真的是挺长的, 它的解析我写在他的下面:

@Override
public void read() {
// todo 同样是断言, 当前的线程必须是在 EventLoop 里面的线程才有资格执行
assert eventLoop().inEventLoop( );
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
// todo 用于查看服务端接受的速率, 说白了就是控制服务端是否接着read 客户端的IO事件
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config); boolean closed = false;
Throwable exception = null;
try {
try {
do {
// todo 进入
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
//todo 对读到的连接,进行简单的计数
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
} int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
// todo 处理新的连接的逻辑来到这, 意思是让pipeline中发生事件传播,
// todo pipeline是谁的呢? 现在是NioMessageUnsafe 所以是服务端的,
// todo 事件是如何传播的呢? head-->>ServerBootStraptAcceptor-->>tail 依次传播,
// todo 传播的什么事件? ChannelRead, 也就是说,会去调用 ServerBootStraptAcceptor的ChannelRead方法,跟进去
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
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();
}
}
}
}

read()三部曲:

针对这段代码,我们值关心下面几部分, 这三部分结束, 整个新链接的建立就完成了,

下面三部曲的 大前提都是,当前我们是在AbstractNioMessageChannel

  • doReadMessages(readBuf)
  • allocHandle.incMessagesRead(localRead);
  • pipeline.fireChannelRead(readBuf.get(i));

第一步:

如何创建出jdk原生的 客户端channel,对它做了什么?

第一步doReadMessages(readBuf) 这是AbstractNioMessageChannel的抽象方法,从chanel读取内容我们需要一个维护特化chanenl引用的对象,谁呢? 它的子类NioServerSocketChannel, 源码如下: 解析依然写在代码下面

//todo doReadMessage  其实就是 doChannel
// todo 处理新连接, 现在是在 NioServReaderSocketChannel里面
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
// todo java Nio底层在这里 创建jdk底层的 原生channel
SocketChannel ch = SocketUtils.accept(javaChannel()); try {
if (ch != null) {
// todo 把java原生的channel, 封装成 Netty自定义的封装的channel , 这里的buf是list集合对象,由上一层传递过来的
// todo this -- NioServerSocketChannel
// todo ch -- SocketChnnel
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;
}

这是个跨越性的操作, 上面的代码主要进行如下面几步工作:

  • 从原生的jdk ServerSocketChannel中 accept出 jdk原生的 SocketChanel
  • 将jdk原生的 Socket封装成Netty对它的封装类型 NioChannel

为啥,服务端的channel需要反射创建,而客户的的channel直接new?

我的理解是,netty不仅可以做 NIO编程模型的服务器, 传统的阻塞式IO,或者其他类型的服务器他也可以做, 我们传递进入的服务端Chanel的类型决定了他可以成为的服务器的类型, netty的设计者是不知道,用户想用netty做些什么的,于是设计成通过反射创建

但是,一旦服务端的channel类型确定了,对应的客户端的channel也一定知道了,直接new 就好了

NioSocketChannel的创建过程

我们跟进new NioSocketChannel(this, ch) ,继续阅读, 其中的 this,是服务端的NioServerSocketChannel , ch 是 jdk原生的 SocketChannel, 方法调用链 的源码如下:

public NioSocketChannel(Channel parent, SocketChannel socket) {
// todo 向上传递
super(parent, socket);
// todo 主要是设置 禁用了 NoDelay算法
config = new NioSocketChannelConfig(this, socket.socket());
}

跟进去, 看, 他把SelectionKey.OP_READ,传递给了他的父类, 稍后 会用这个参数进行 cannel的二次注册,使得NioSocketChannel可以被netty处理它发生的感兴趣的事件, 我们发现,和服务端的chanel明显不同的是, 服务端的NioChannel关注用户的accept,而这里的客户端的channel关注的是read事件,它标志着,服务端的Selector会关心它当中传递进客户端发送的数据,告诉Selector应该读

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}

继续跟进,到AbstractNioChannel, 他做了如下工作:

  • super(parent) 把NioServerSocketChannel设置为NioSokcetChannel的父parent
  • 自己维护原生的JDK SocketChannel
  • 保存感性趣的选项
  • 设置为非阻塞

源码如下:

*/ // todo 无论是服务端的channel 还是客户端的channel都会使用这个方法进行初始化
// // TODO: 2019/6/23 null ServerSocketChannel accept
// todo 如果是在创建NioSocketChannel parent==NioServerSocketChannel ch == SocketChanel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);// todo 继续向上跟,创建基本的组件
// todo 如果是创建NioSocketChannel 这就是在保存原生的jdkchannel
// todo 如果是创建NioServerSocketChannel 这就是在保存ServerSocketChannel
this.ch = ch;
// todo 设置上感兴趣的事件
this.readInterestOp = readInterestOp;
try {
// todo 作为服务端, ServerSocketChannel 设置为非阻塞的
// todo 作为客户端 SocketChannel 设置为非阻塞的
ch.configureBlocking(false);

第二步:

现在NioSocketChannel已经创建完成了,代码的调用栈重新返回上面的NioMessageUnsafe.read()方法,我们接着往下看

//todo 对读到的连接,进行简单的计数
allocHandle.incMessagesRead(localRead);

第三步 pipeline.fireChannelRead(readBuf.get(i));

往下传播channelRead(), 在管道中传递事件 channel, 对于服务端来说, 现在他的pipeline是怎么个状态呢?

Header --> ServerBootStraptAcceptor --> tail

channel的pipeline组件是基于双向链表实现,其中head和tail是默认的链表头和尾, 中间的ServerBootStraptAcceptor是什么呢? 其实他是在创建服务端的NioServerSocketChannel时,是在channel注册完毕之后,通过回调,将ServerBootStrap的init()函数,给channel添加channelInitializer时添加进去的; ServerBootStraptAcceptor本质上就是handler, 回顾第一个图, 他就是图中的Acceptor

ok,现在我们去直接去ServerBootStraptAcceptor中,他是ServerBootStrap的内部类,我们看它的channelRead()方法,源码如下:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
// todo 给这个来连接的通道添加 childHandler,是我在Server中添加的childHandler, 实际上是那个MyChannelInitializer , 最终目的是添加handler
child.pipeline().addLast(childHandler);
// todo 给新来的Channel设置 options 选项
setChannelOptions(child, childOptions, logger);
// todo 给新来的Channel设置 attr属性
for (Entry<AttributeKey<?>, Object> e : childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
} try {
//todo 这里这!! 把新的channel注册进 childGroup
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {

我们可以看到,如下工作:

  • 初始化属性
  • 把客户端的channel的注册进childGroup中的EventLoop

在这里补一张channelGroup的继承图

我们看这个childGroup.regist()方法, 我们知道childGroup是workerGroup,在本类中,它的类型是EventLoopGroup,这是个接口类型的变量, 我们用点进去查看源码自然跳转进接口中,但是我们需要找他的实现类, 那么,是谁重写的它的方法呢?

大家去看看上面的图,它的直接实现类只有一个MultiThreadEventGroup, 其实大家想想看,现在的任务是将原生的客户端channel,注册进WorkerGroup中的EventLoop,那第一步是啥呢? 不得先从这个 事件循环组中拿出一个事件循环吗? 好,进去看MultiThreadEventGroup是如何实现的, 源码如下:

@Override
public ChannelFuture register(Channel channel) {
// todo next() -- 就在上面-> 根据轮询算法获取一个事件的执行器 EventExecutor
// todo, 而每一个EventLoop对应一个EventExecutor 这里之所以是个组, 是因为, 我的机器内核决定我的 事件循环组有八个线程,
// todo ?? ????
// todo 但是一会的责任并没有一直循环, 难道有效的bossGroup只有一条 // todo 再进去就是SingleThreadEventLoop对此方法的实现了
return next().register(channel);
}

是的,确实在获取事件循环,我们进行跟进 next().register(channel), 现在是 eventloop.regist() ,当我们进入方法时,再次来到EventLoopGroup对这个方法的实现, ok,大家重新去看上面的图,一个eventloop.regist(),现在不再是 循环组.regist 而是 事件循环.regist, 而在图上,我们可以很轻松的看到 , 对EventLoopGroup接口的实现就是 SingleThreadEventLoop, 好,接着进去看它的实现, 源码如下:

// todo register来到这里
@Override
public ChannelFuture register(Channel channel) {
// todo ChannelPromise == channel+Executor 跟进去
// todo 再次调用 register, 就在下面
return register(new DefaultChannelPromise(channel, this));
}

调用本类的register(new DefaultChannelPromise(channel, this) ,接着进去,源码如下: 同样解析写在源码下面

@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
// todo 重点来了
// todo channel() 获取通道对象
// todo unsafe() 获取仅供内部使用的unsafe对象 它定义在Channel接口中, 具体的对象是 Channel的子类, AbstractNioChannel
// todo unsafe对象进行下一步注册 register
* promise.channel().unsafe().register(this, promise);
return promise;
}
  • promise.channel() 取出的是客户端的 NioSocketChanenl
  • promise.channel().unsafe() 是 AbstractUnsafe

来到regist()的实现类

方法调用链:

  • 本类方法register

    • 本类方法register0()

      • 本类抽象方法doRegister()
      • pipeline.fireChannelRegistered();传播channel注册事件
      • pipeline.fireChannelActive(); 传播channel Active事件
        • 二次注册事件

其中,上面的doRegister()是真正的将jdk原生的channel注册进原生的selector

pipeline.fireChannelRegistered();是在 header --> ServerBootStraptAccptor --> 用户自己添加的handler --> tail 中,挨个传递 ChannelRegistered, 就是从头开始调用它们的函数, 我们着重看下面的第三个

pipeline.fireChannelActive();其实是比较绕的,涉及到了pipeline中事件的传递,但是它的作用很大,通过传播channelActive挨个回调他们的状态,netty成功的给这条客户端的新连接注册上了netty能处理的感兴趣的事件

整体源码太长了我不一一贴出来了, 直接看关于pipeline.fireChannelActive();的源码,如下:

if (isActive()) {
if (firstRegistration) {
// todo 在pipeline中传播ChannelActive的行为,跟进去
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
// todo 可以接受客户端的数据了
beginRead();
}

第一个判断, if (isActive())针对两个channel,存在两种情况

  • 如果是服务端的channel, 只有在channel绑定完端口后,才会处于active的状态
  • 如果是客户端的channel, 注册到selector+处于连接状态, 他就是active状态

满足条件,进入第一个分支判断,同样满足第一次注册的条件,开始传播事件

回想一下,现在程序进行到什么状态? 看上图的subReactor每一个蓝色的箭头都是一个客户端的channel, 问题是netty还处理不了这些channel上的会发生的感兴趣的事件,因为第一步我们只是把jdk原生的chanel和原生的selector之间进行了关联, 而netty对他们的封装类还没有关联,于是下一步就通过传播active的行为去二次注册关联感兴趣的事件

关于pipeline中的事件传递太多内容了,在下篇博客中写,连载

现在直接给结果,

传递到header的read()源码如下

  @Override
public void read(ChannelHandlerContext ctx) {
// todo 如果是服务端: NioMessageUnsafe
// todo 如果是客户端: NioSocketChannelUnsafe
unsafe.beginRead();
}

接着跟进

@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
//todo 如果是服务端: 这里的SelectionKey就是我们在把NioServerSocketChannel 注册进BoosGroup中的eventLoop时,返回的selectionKey , 就在上面
//todo 如果是客户端: 这里的SelectionKey就是我们在把NioSocketChannel 注册进BoosGroup中的eventLoop时,返回的selectionKey , 就在上面
final SelectionKey selectionKey = this.selectionKey;
// todo 这SelectionKey 就是我们把 NioServerSocketChannel中的ServerSocketChannel注册进BossGroup时, 附加的第三个参数 0
if (!selectionKey.isValid()) {
return;
} readPending = true;
// todo 获取这个Selection 的感兴趣的事件,实际就是当时注册时的第二个参数 0
final int interestOps = selectionKey.interestOps();
// todo 如果是服务端, readInterestOp是创建服务端channel时设置的 op_accept
// todo 如果是客户端的新连接,readInterestOp是创建客户端channel时设置的 op_read
if ((interestOps & readInterestOp) == 0) {
// todo interestOps | readInterestOp两者进行或运算,原来是0事件 , 现在又增加了一个事件, accept事件或者是read
// todo 进而 从新注册到SelectionKey上面去。。。 0P_Accept 或者 OP_Read
selectionKey.interestOps(interestOps | readInterestOp);
}
}

ok, 到这里netty的新链接接入就完成了....

连载下一篇, pipeline中的事件传播

Netty-新连接接入源码解读的更多相关文章

  1. Netty 学习(八):新连接接入源码说明

    Netty 学习(八):新连接接入源码说明 作者: Grey 原文地址: 博客园:Netty 学习(八):新连接接入源码说明 CSDN:Netty 学习(八):新连接接入源码说明 新连接的接入分为3个 ...

  2. Netty是如何处理新连接接入事件的?

    更多技术分享可关注我 前言 前面的分析从Netty服务端启动过程入手,一路走到了Netty的心脏——NioEventLoop,又总结了Netty的异步API和设计原理,现在回到Netty服务端本身,看 ...

  3. HttpClient 4.3连接池参数配置及源码解读

    目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB-&g ...

  4. HttpClient4.3 连接池参数配置及源码解读

    目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB-&g ...

  5. Netty服务端NioEventLoop启动及新连接接入处理

    一 Netty服务端NioEventLoop的启动 Netty服务端创建.初始化完成后,再向Selector上注册时,会将服务端Channel与NioEventLoop绑定,绑定之后,一方面会将服务端 ...

  6. Netty异步Future源码解读

    本文地址: https://juejin.im/post/5df771ee6fb9a0161d743069 说在前面 本文的 Netty源码使用的是 4.1.31.Final 版本,不同版本会有一些差 ...

  7. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

  8. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  9. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

随机推荐

  1. 指定Qt程序运行的style,比如fusion(以前没见过QStyleFactory)

    转载请注明文章:指定Qt程序运行的style,比如fusion 出处:多客博图 代码很简单,如下: #include <QtWidgets/QApplication>   #include ...

  2. 判断当前进程是否以管理员权限运行(AllocateAndInitializeSid后,用CheckTokenMembership与AdministratorsGroup进行比较,和Delphi的那个例子还有点不一样)

    在Win7下(Vista以上的...)有时某些操作就是会让人郁闷 开启了UAC的话,得以管理员权限运行才不会出现Access is denied... 但是,程序又不是非得什么时候都用那破管理员权限的 ...

  3. CEGUI 0.7.7 VS2010+SP3 编译过程

    1 在官方网站http://www.cegui.org.uk/ 下载最新的CEGUI 源代码 版本是0.7.7 2 下载编译需要用到的依赖文件包 将解压后的文件夹 Dependencies 和CEGU ...

  4. Google C++测试框架系列入门篇:第二章 开始一个新项目

    上一篇:Google C++测试框架系列入门篇:第一章 介绍:为什么使用GTest? 原始链接:Setting up a New Test Project 词汇表 版本号:v_0.1 开始一个新项目 ...

  5. java中静态类与普通类之间区别

    所谓静态,指以static关键字修饰的,包括类,方法,块,字段. 非静态,指没有用static 修饰的. 一.静态类的特点 1.全局唯一,任何一次的修改都是全局性的影响 2.只加载一次,优先于非静态 ...

  6. 用JavaScript刷LeetCode的正确姿势

    虽然很多人都觉得前端算法弱,但其实 JavaScript 也可以刷题啊!最近两个月断断续续刷完了 leetcode 前 200 的 middle + hard ,总结了一些刷题常用的模板代码.走过路过 ...

  7. .NET平台简介

    前言: 看到一个名词:搜商(SQ),还挺有趣.讲的是在互联网时代,怎么能够快速找到自己所需信息或资源,成为一种能力,并将其提升到类似智商.情商的概念.在以后工作过程中,尽量提高自己获取.辨别.处理信息 ...

  8. spring boot 2.x 系列 —— actuator 服务监控与管理

    文章目录 一.概念综述 1.1 端点 1.2 启用端点 1.3 暴露端点 1.4 健康检查信息 二.项目说明 1.1 项目结构说明 1.2 主要依赖 1.3 项目配置 1.4 查看监控状态 三.自定义 ...

  9. Java NIO 学习笔记(七)----NIO/IO 的对比和总结

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  10. Java web环境的搭建

    学习java web 首先得要能够在自己的电脑上配置环境,经查阅资料后了解,需要装java EEEclipse ,即企业版的,还有就是tomcat服务器. 第一步,从网上下载,eclipse 企业版的 ...