nginx内存池源码解析

目录
  • 内存池概述
  • 一、nginx数据结构
  • 二、nginx向OS申请空间ngx_create_pool
  • 三、nginx向内存池申请空间
  • 四、大块内存的分配与释放
  • 五、关于小块内存不释放
  • 六、销毁和清空内存池
  • 七、编译测试内存池接口功能

内存池概述

内存池是在真正使用内存之前,预先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存。

内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的碎片,提示程序性能,减少程序员在编写代码中对内存的关注等

目前一些常见的内存池实现方案有STL中的内存分配区,boost中的object_pool,nginx中的ngx_pool_t,google的开源项目TCMalloc等。

为了自身使用的方便,Nginx封装了很多有用的数据结构,比如ngx_str_t ,ngx_array_t, ngx_pool_t 等等,对于内存池,nginx设计的十分精炼,值得我们学习,本文重点给大家介绍nginx内存池源码,并用一个实际的代码例子作了进一步的讲解。

一、nginx数据结构

// SGI STL小块和大块内存的分界点:128B
// nginx(给HTTP服务器所有的模块分配内存)小块和大块内存的分界点:4096B
#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize - 1) 

// 内存池默认大小
#define NGX_DEFAULT_POOL_SIZE    (16 * 1024)

// 内存池字节对齐,SGI STL对其是8B
#define NGX_POOL_ALIGNMENT       16
#define NGX_MIN_POOL_SIZE        ngx_align((sizeof(ngx_pool_t) + 2 * sizeof(ngx_pool_large_t)), \
		                         NGX_POOL_ALIGNMENT)

// 将开辟的内存调整到16的整数倍
#define ngx_align(d, a)          (((d) + (a - 1)) & ~(a - 1))
typedef struct ngx_pool_s ngx_pool_t;

typedef struct {
    u_char               *last;   // 指向可用内存的起始地址
    u_char               *end;    // 指向可用内存的末尾地址
    ngx_pool_t           *next;   // 指向下一个内存块
    ngx_uint_t            failed; // 当前内存块分配空间失败的次数
} ngx_pool_data_t;

// 内存池块的类型
struct ngx_pool_s {
    ngx_pool_data_t       d;          // 内存池块头信息
    size_t                max;
    ngx_pool_t           *current;    // 指向可用于分配空间的内存块(failed < 4)的起始地址
    ngx_chain_t          *chain;      // 连接所有的内存池块
    ngx_pool_large_t     *large;	  // 大块内存的入口指针
    ngx_pool_cleanup_t   *cleanup;    // 内存池块的清理操作,用户可设置回调函数,在内存池块释放之前执行清理操作
    ngx_log_t            *log;        // 日志
};

二、nginx向OS申请空间ngx_create_pool

// 根据size进行内存开辟
ngx_pool_t * ngx_create_pool(size_t size, ngx_log_t *log){
    ngx_pool_t  *p;
	// 根据系统平台定义的宏以及用户执行的size,调用不同平台的API开辟内存池
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }

    p->d.last = (u_char *) p + sizeof(ngx_pool_t);  // 指向可用内存的起始地址
    p->d.end = (u_char *) p + size;                 // 指向可用内存的末尾地址
    p->d.next = NULL;                               // 指向下一个内存块,当前刚申请内存块,所以置空
    p->d.failed = 0;                                // 内存块是否开辟成功

    size = size - sizeof(ngx_pool_t);              // 能使用的空间 = 总空间 - 头信息
    // 指定的大小若大于一个页面就用一个页面,否则用指定的大小
    // max = min(size, 4096),max指的是除开头信息以外的内存块的大小
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;         // 指向可用于分配空间的内存块的起始地址
    p->chain = NULL;
    p->large = NULL;        // 小块内存直接在内存块开辟,大块内存在large指向的内存开辟
    p->cleanup = NULL;
    p->log = log;

    return p;
}

三、nginx向内存池申请空间

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
    	// 当前分配的空间小于max,小块内存的分配
        return ngx_palloc_small(pool, size, 1);   // 考虑内存对齐
    }
#endif

    return ngx_palloc_large(pool, size);
}

void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 0);  // 不考虑内存对齐
    }
#endif

    return ngx_palloc_large(pool, size);
}

void* ngx_pcalloc(ngx_pool_t *pool, size_t size){
    void *p;
    p = ngx_palloc(pool, size); // 考虑内存对齐
    if (p) {
        ngx_memzero(p, size);   // 可以初始化内存为0
    }

    return p;
}

ngx_palloc_small 分配效率高,只做了指针的偏移

static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;
	// 从第一个内存块的current指针指向的内存池进行分配
    p = pool->current;

    do {
        m = p->d.last;  // m指向可分配内存的起始地址

        if (align) {
        	// 把m调整为NGX_ALIGNMENT整数倍
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }
		// 内存池分配内存的核心代码
        if ((size_t) (p->d.end - m) >= size) {
        	// 若可分配空间 >= 申请的空间
        	// 偏移d.last指针,记录空闲空间的首地址
            p->d.last = m + size;
            return m;
        }
        // 当前内存块的空闲空间不够分配,若有下一个内存块则转向下一个内存块
        // 若没有,p会被置空,退出while
        p = p->d.next;
    } while (p);

    return ngx_palloc_block(pool, size);
}

当前内存池的块足够分配:

当前内存池的块不够分配:

  1. 开辟新的内存块,修改新内存块头信息的last、end、next、failed
  2. 前面所有内存块的failed++
  3. 连接新的内存块以及前面的内存块
static void * ngx_palloc_block(ngx_pool_t *pool, size_t size){
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;
	// 开辟与上一个内存块大小相同的内存块
    psize = (size_t) (pool->d.end - (u_char *) pool);

	// 将psize对齐为NGX_POOL_ALIGNMENT的整数倍后,向OS申请空间
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    new = (ngx_pool_t *) m;    // 指向新开辟内存块的起始地址

    new->d.end = m + psize;    // 指向新开辟内存块的末尾地址
    new->d.next = NULL;		   // 下一块内存的地址为NULL
    new->d.failed = 0;		   // 当前内存块分配空间失败的次数

	// 指向头信息的尾部,而max,current、chain等只在第一个内存块有
    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;                // last指向当前块空闲空间的起始地址

	// 由于每次都是从pool->current开始分配空间
	// 若执行到这里,除了new这个内存块分配成功,其他的内存块全部分配失败
    for (p = pool->current; p->d.next != NULL; p = p->d.next) {
    	// 对所有的内存块的failed都++,直到该内存块分配失败的次数大于4了
    	// 就表示该内存块的剩余空间很小了,不能再分配空间了
    	// 就修改current指针,下次从current开始分配空间,再次分配的时候可以不用遍历前面的内存块
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    p->d.next = new;   // 连接可分配空间的首个内存块 和 新开辟的内存块

    return m;
}

四、大块内存的分配与释放

typedef struct ngx_pool_large_s  ngx_pool_large_t;

struct ngx_pool_large_s {
    ngx_pool_large_t     *next;   // 下一个大块内存的起始地址
    void                 *alloc;  // 大块内存的起始地址
};

static void * ngx_palloc_large(ngx_pool_t *pool, size_t size){
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

	// 调用的就是malloc
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;
	// for循环遍历存储大块内存信息的链表
    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
        	// 当大块内存被ngx_pfree时,alloc为NULL
        	// 遍历链表,若大块内存的首地址为空,则把当前malloc的内存地址写入alloc
            large->alloc = p;
            return p;
        }
		// 遍历4次后,若还没有找到被释放过的大块内存对应的信息
		// 为了提高效率,直接在小块内存中申请空间保存大块内存的信息
        if (n++ > 3) {
            break;
        }
    }
	// 通过指针偏移在小块内存池上分配存放大块内存*next和*alloc的空间
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
    	// 如果在小块内存上分配存储*next和*alloc空间失败,则无法记录大块内存
    	// 释放大块内存p
        ngx_free(p);
        return NULL;
    }

    large->alloc = p;			   // alloc指向大块内存的首地址
    large->next = pool->large;	   // 这两句采用头插法,将新内存块的记录信息存放于以large为头结点的链表中
    pool->large = large;

    return p;
}

大块内存的释放

// 释放p指向的大块内存
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p){
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
    	// 遍历存储大块内存信息的链表,找到p对应的大块内存
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            // 释放大块内存,但不释放存储信息的内存空间
            ngx_free(l->alloc);  // free
            l->alloc = NULL;     // alloc置空

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

五、关于小块内存不释放

就用了last和end两个指着标识空闲的空间,是无法将已经使用的空间合理归还到内存池的,只是会重置内存池。同时还存储了指向大内存块large和清理函数cleanup的头信息

考虑到nginx的效率,小块内存分配高效,同时也不回收内存

void ngx_reset_pool(ngx_pool_t *pool){
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

	// 由于需要重置小块内存,而大块内存的控制信息在小块内存中保存
	// 所以需要先释放大块内存,在重置小块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

	// 遍历小块内存的链表,重置last、failed、current、chain、large等管理信息
    for (p = pool; p; p = p->d.next) {
    	// 由于只有第一个内存块有除了ngx_pool_data_t以外的管理信息,别的内存块只有ngx_pool_data_t的信息
    	// 不会出错,但是会浪费空间
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

	// current指向可用于分配内存的内存块
    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

nginx本质是http服务器,通常处理的是短链接,间接性提供服务,需要的内存不大,所以不回收内存,重置即可。

客户端发起一个requests请求后,nginx服务器收到请求会返回response响应,若在keep-alive时间内没有收到客户的再次请求,nginx服务器会主动断开连接,此时会reset内存池。下一次客户端请求再到来时,可以复用内存池。

如果是处理长链接,只要客户端还在线,服务器的资源就无法释放,直到系统资源耗尽。长链接一般使用SGI STL内存池的方式进行内存的开辟和释放,而这种方式分配和回收空间的效率就比nginx低

六、销毁和清空内存池

假设如下情况:

// 假设内存对齐为4B
typedef struct{
	char* p;
	char data[508];
}stData;

ngx_pool_t *pool = ngx_create_pool(512, log);  // 创建一个总空间为512B的nginx内存块
stData* data_ptr = ngx_alloc(512);            // 因为可用的实际内存大小为:512-sizeof(ngx_pool_t),所以属于大内存开辟
data_ptr->p = malloc(10);                   // p指向外界堆内存,类似于C++对象中对用占用了外部资源

当回收大块内存的时候,调用ngx_free,就会导致内存泄漏

以上内存泄漏的问题,可以通过回调函数进行内存释放(通过函数指针实现)

typedef void (*ngx_pool_cleanup_pt)(void *data);

typedef struct ngx_pool_cleanup_s  ngx_pool_cleanup_t;

// 以下结构体由ngx_pool_s.cleanup指向,也是存放在内存池的小块内存
struct ngx_pool_cleanup_s {
    ngx_pool_cleanup_pt   handler;
    void                 *data;     // 指向需要释放的资源
    ngx_pool_cleanup_t   *next;     // 释放资源的函数都放在一个链表,用next指向这个链表
};

nginx提供的函数接口:

// p表示内存池的入口地址,size表示p->cleanup->data指针的大小
// p->cleanup指向含有清理函数信息的结构体
// ngx_pool_cleanup_add返回 含有清理函数信息的结构体 的指针
ngx_pool_cleanup_t* ngx_pool_cleanup_add(ngx_pool_t *p, size_t size){
    ngx_pool_cleanup_t  *c;

	// 开辟清理函数的结构体,实际上也是存放在内存池的小块内存
    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
    	// 为c->data申请size的空间
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }
    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    // 采用头插法,将当前结构体串在pool->cleanup后
    c->next = p->cleanup;
    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

使用方式:

void release(void* p){
	free(p);
}

ngx_pool_cleanup_t* clean_ptr = ngx_clean_cleanup_add(pool, sizeof(char*));
clean_ptr->handler = &release;   // 用户设置销毁内存池前需要调用的函数
clean_ptr->data = data_ptr->p;   // 用户设置销毁内存池前需要释放的内存的地址

ngx_destroy_pool(pool);          // 用户销毁内存池

七、编译测试内存池接口功能

void ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

	// 遍历cleanup链表(存放的时释放前需要调用的函数),可释放外部占用的资源
    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            c->handler(c->data);
        }
    }

	// 释放大块内存
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

	// 释放小块内存池
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

执行configure生成Makefile文件(若报错则表示需要apt安装软件)

Makefile如下:

执行make命令使用Makefile编译源码,在相应目录下生成 .o文件

#include <ngx_config.h>
#include <nginx.h>
#include <ngx_core.h>
#include <ngx_palloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,
            const char *fmt, ...){

}

typedef struct Data stData;
struct Data{
    char *ptr;
    FILE *pfile;
};

void func1(char *p){
    printf("free ptr mem!\n");
    free(p);
}

void func2(FILE *pf){
    printf("close file!\n");
    fclose(pf);
}

void main(){
	// max = 512 - sizeof(ngx_pool_t)
	// 创建总空间为512字节的nginx内存块
    ngx_pool_t *pool = ngx_create_pool(512, NULL);
    if(pool == NULL){
        printf("ngx_create_pool fail...");
        return;
    }

	// 从小块内存池分配的
    void *p1 = ngx_palloc(pool, 128);
    if(p1 == NULL){
        printf("ngx_palloc 128 bytes fail...");
        return;
    }

	// 从大块内存池分配的
    stData *p2 = ngx_palloc(pool, 512);
    if(p2 == NULL){
        printf("ngx_palloc 512 bytes fail...");
        return;
    }

    // 占用外部堆内存
    p2->ptr = malloc(12);
    strcpy(p2->ptr, "hello world");
    // 文件描述符
    p2->pfile = fopen("data.txt", "w");

    ngx_pool_cleanup_t *c1 = ngx_pool_cleanup_add(pool, sizeof(char*));
    c1->handler = func1;   // 设置回调函数
    c1->data = p2->ptr;    // 设置资源地址

    ngx_pool_cleanup_t *c2 = ngx_pool_cleanup_add(pool, sizeof(FILE*));
    c2->handler = func2;
    c2->data = p2->pfile;

	// 1.调用所有的预置的清理函数 2.释放大块内存 3.释放小块内存池所有内存
    ngx_destroy_pool(pool); 

    return;
}

由于ngx_pool_cleanup_add中用头插法将创建的清理块链入pool->cleanup,所以ngx_destroy_pool的时候先清理文件后清理堆内存。

相关测试代码推送到:https://github.com/BugMaker-shen/nginx_sgistl_pool

到此这篇关于nginx内存池源码解析的文章就介绍到这了,更多相关nginx内存池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Nginx中基本的内存池初始化配置

    ngx_cycle 的初始化 整个初始化过程中,最重要的就是全局变量 nginx_cycle 的初始化,很多变量都是在这个过程中初始化的 nginx_cycle 又是通过两个局部变量 init_cycle 和 cycle 实现初始化的 事实上,日志初始化也可以算是对 nginx_cyle 的初始化,因为在代码中接下来马上要发生的就是一个赋值 ngx_memzero(&init_cycle, sizeof(ngx_cycle_t)); init_cycle.log = log; ngx_cycle

  • Nginx源码安装的方法步骤

    一.下载Nginx源文件 进入nginx官网下载nginx的稳定版本,我下载的是1.10.0. 下载:wget http://nginx.org/download/nginx-1.10.0.tar.gz 解压:tar -zxvf nginx-1.10.0.tar.gz 二.检查安装依赖项 执行下面的命令安装nginx的依赖库: yum -y install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel 三.配置Nginx安装选

  • nginx内存池源码解析

    目录 内存池概述 一.nginx数据结构 二.nginx向OS申请空间ngx_create_pool 三.nginx向内存池申请空间 四.大块内存的分配与释放 五.关于小块内存不释放 六.销毁和清空内存池 七.编译测试内存池接口功能 内存池概述 内存池是在真正使用内存之前,预先申请分配一定数量的.大小相等(一般情况下)的内存块留作备用.当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够用时,再继续申请新的内存. 内存池的好处有减少向系统申请和释放内存的时间开销,解决内存频繁分配产生的

  • nginx线程池源码分析

    周末看了nginx线程池部分的代码,顺手照抄了一遍,写成了自己的版本.实现上某些地方还是有差异的,不过基本结构全部摘抄. 在这里分享一下.如果你看懂了我的版本,也就证明你看懂了nginx的线程池. 本文只列出了关键数据结构和API,重在理解nginx线程池设计思路.完整代码在最后的链接里. 1.任务节点 typedef void (*CB_FUN)(void *); //任务结构体 typedef struct task { void *argv; //任务函数的参数(任务执行结束前,要保证参数

  • Android开发中线程池源码解析

    线程池(英语:thread pool):一种线程使用模式.线程过多会带来调度开销,进而影响缓存局部性和整体性能.而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务.这避免了在处理短时间任务时创建与销毁线程的代价.线程池不仅能够保证内核的充分利用,还能防止过分调度.可用线程数量应该取决于可用的并发处理器.处理器内核.内存.网络sockets等的数量. 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销.----摘自维基百科 我们在Android或者Java开发中

  • 深度源码解析Java 线程池的实现原理

    java 系统的运行归根到底是程序的运行,程序的运行归根到底是代码的执行,代码的执行归根到底是虚拟机的执行,虚拟机的执行其实就是操作系统的线程在执行,并且会占用一定的系统资源,如CPU.内存.磁盘.网络等等.所以,如何高效的使用这些资源就是程序员在平时写代码时候的一个努力的方向.本文要说的线程池就是一种对 CPU 利用的优化手段. 线程池,百度百科是这么解释的: 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的

  • Java 线程池ThreadPoolExecutor源码解析

    目录 引导语 1.整体架构图 1.1.类结构 1.2.类注释 1.3.ThreadPoolExecutor重要属性 2.线程池的任务提交 3.线程执行完任务之后都在干啥 4.总结 引导语 线程池我们在工作中经常会用到.在请求量大时,使用线程池,可以充分利用机器资源,增加请求的处理速度,本章节我们就和大家一起来学习线程池. 本章的顺序,先说源码,弄懂原理,接着看一看面试题,最后看看实际工作中是如何运用线程池的. 1.整体架构图 我们画了线程池的整体图,如下: 本小节主要就按照这个图来进行 Thre

  • 内存泄漏检测工具LeakCanary源码解析

    目录 前言 使用 源码解析 LeakCanary自动初始化 如何关闭自动初始化 LeakCanary初始化做了什么 ActivityWatcher FragmentAndViewModelWatcher RootViewWatcher ServiceWatcher Leakcanary对象泄漏检查 总结 前言 LeakCanary是一个简单方便的内存泄漏检测工具,它是由大名鼎鼎的Square公司出品并开源的出来的.目前大部分APP在开发阶段都会接入此工具用来检测内存泄漏问题.它让我们开发者可以在

  • Android图片加载利器之Picasso源码解析

    看到了这里,相信大家对Picasso的使用已经比较熟悉了,本篇博客中将从基本的用法着手,逐步的深入了解其设计原理. Picasso的代码量在众多的开源框架中算得上非常少的一个了,一共只有35个class文件,但是麻雀虽小,五脏俱全.好了下面跟随我的脚步,出发了. 基本用法 Picasso.with(this).load(imageUrl).into(imageView); with(this)方法 public static Picasso with(Context context) { if

  • JetCache 缓存框架的使用及源码解析(推荐)

    目录 一.简介 为什么使用缓存? 使用场景 使用规范 二.如何使用 引入maven依赖 添加配置 配置说明 注解说明 @EnableCreateCacheAnnotation @EnableMethodCache @CacheInvalidate @CacheUpdate @CacheRefresh @CachePenetrationProtect @CreateCache 三.源码解析 项目的各个子模块 常用注解与变量 缓存API Cache接口 AbstractCache抽象类 Abstra

  • 源码解析ios开发SDWebImage方法

    目录 引言 源码解析 字典操作 看一下调用下载函数前的实例化过程 快速查找缓存的方法回调 开始进入查找函数 总结一下函数调用 1.先调用 2.设置图片 引言 在着手写第二篇的时候,发现这个SDWebimage确实吧NSOperation用的太美了.确实可能帮你理解NSOperation和NSOperationQueue,当然还有Block的队列.还有一个GCD. 各位看官在看的时候可以着重的看看他的operatinQueue的队列.看看是怎么添加到队列的以及是怎么移除队列.在后面的文章就会提到他

  • Netty实战源码解析NIO编程

    目录 1 前言 2 Netty是什么? 3 Java I/O模型简介 3.1 BIO代码实现 4 Java NIO 4.1 基本介绍 4.2 三大核心组件的关系 4.3 Buffer缓冲区 4.4 Channel通道 4.5 Selector选择器 4.5.1 Selector的创建 4.5.2 注册Channel到Selector 4.5.3 SelectionKey 4.5.4 从Selector中选择Channel 4.5.5 停止选择的方法 4.5.6 NIO客户端.服务端 5 Java

随机推荐