NET-SNMP学习 Table对象 (表格代码框架iterate)

SNMP Table是对象的有序集合,包含若干行。为什么SNMP中需要用表格呢,原因是有些数据的组织用表格来表达更方便,多个对象的组合才能完整描述一条信息。
表格代码框架概述:
表格框架的配置文件根据表格数据是否存在于Net-SNMP的内核中分为两大类:

  • 一类是表格数据存放于其内部。这类框架代码中将所有的表格信息和数据都注册到系统中,同时生成单个处理句柄,在代理内核完成数据的GET/SET等操作。当对表格中某列有特殊的处理需求时,才自定义额外的处理句柄。这类框架配置文件有,mib2c.table_data.conf, mib2c.create_dataset.conf,mib2c.array-user.conf等,后者还具有根据表格索引排序的功能。

  • 另一类时表格数据存在于其外部。这类代码框架中,表格数据由外部程序定义和维护。这些数据可以存在于代理中,也可以存在于另外的系统中,一般的项目中就属于这种数据模型。表格数据的查询通过迭代器的方式来实现,这些迭代器的功能是实现表格行和列数据的获取。使用迭代器一般要求表格数据定义为链表结构,每个链表节点代表一行数据。框架一般会提供get_first_data和get——next-data方法来实现迭代的功能,这类框架配置文件有mib2c.iterate.conf和mib2c.iterate_access.conf等。
    这些框架代码也有其共同之处,如模块注册等机制。
    我们使用的是代码框架配置文件:mib2c.iterate.conf,该代码框架既可以用于简单表也适用于通用表。

Iterate 系列框架
Iterate 框架中,表格模块的注册与标量模块注册方式是一样的:注册OID的回调函数(XXX_handler)。这些注册由接口init_XXX()和initialize_table_XXX()实现。不过在表格框架的代码中还需要注册查询表格的迭代器,即表格中还需要具有查询表格行的方法。它们由XXX_get_first_data_point()和XXX_get_next_data_point()实现。另外,该代码框架还提供了定义表格的链表结构体(XXX_Table_entry)和操作链表的两个函数XXXTable_createEntry()和XXXTable_remove Entry()。
MIB文件:

TEST-TABLE-MIB DEFINITIONS ::= BEGIN
 
IMPORTS
    MODULE-IDENTITY, OBJECT-TYPE, TimeTicks FROM SNMPv2-SMI
       DisplayString, FROM SNMPv2-TC
    jsHostInfo
        FROM JS-MAIN-MIB ;
    testTable OBJECT IDENTIFIER ::= { jsHostInfo 20 }

    boardSlotTable OBJECT-TYPE
        SYNTAX SEQUENCE OF BoardSlotEntry
        MAX-ACCESS not-accessible
        STATUS current
        DESCRIPTION " Fan status description table "
        ::= { testTable  2 }
 
 
    boardSlotEntry OBJECT-TYPE
        SYNTAX BoardSlotEntry
        MAX-ACCESS not-accessible
        STATUS current
        DESCRIPTION " Boad status description table entry "
        INDEX   { boardSlotID }
        ::= { boardSlotTable 1}
 
 
    BoardSlotEntry::=
       SEQUENCE {
                boardSlotID  INTEGER,
                boardType INTEGER,
                boardSeriesNumber DisplayString, 
               }
 
    boardSlotID  OBJECT-TYPE
        SYNTAX  INTEGER
        MAX-ACCESS read-only
        STATUS current
        DESCRIPTION " Slot ID "
        ::= { boardSlotEntry 1 }
 
    --add enum value
    boardType  OBJECT-TYPE
        SYNTAX  INTEGER
        MAX-ACCESS read-only
        STATUS current
        DESCRIPTION " Slot Number "
        ::= { boardSlotEntry 2 }
        
    boardSeriesNumber  OBJECT-TYPE
        SYNTAX  DisplayString (SIZE (0..128))
        MAX-ACCESS read-only
        STATUS current
        DESCRIPTION " Slot Series Number "
        ::= { boardSlotEntry 3 }
 
    boardWorkMode  OBJECT-TYPE
        SYNTAX  INTEGER
        {
            normal (1),
            abnormal (2),
            offline (3)
        }
        MAX-ACCESS read-only
        STATUS current
        DESCRIPTION " Board Status: normal (1), abnormal (2) and offline (3)"
        ::= { boardSlotEntry 4 }
 
END

使用
mib2c mib2c.iterate.conf TEST-TABLE-MIB::testTable 将MIB文件编译成C模块代码

在这里插入图片描述
选择2,回车

在这里插入图片描述
选择2,回车
在这里插入图片描述
选择1,回车
在这里插入图片描述
选择1 回车
最后生成我们想要的.c .h文件

源码没有修改前的文件:

/*
 * Note: this file originally auto-generated by mib2c using
 *  $
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "boardSlotTable.h"

/** Initializes the boardSlotTable module */
void
init_boardSlotTable(void)
{
    /*
     * here we initialize all the tables we're planning on supporting 
     */
    initialize_table_boardSlotTable();
}

#Determine the first/last column names

/** Initialize the boardSlotTable table by defining its contents and how it's structured */
void
initialize_table_boardSlotTable(void)
{
    const oid       boardSlotTable_oid[] =
        { 1, 3, 6, 1, 4, 1, 88888, 1, 1, 20, 2 };
    const size_t    boardSlotTable_oid_len =
        OID_LENGTH(boardSlotTable_oid);
    netsnmp_handler_registration *reg;
    netsnmp_iterator_info *iinfo;
    netsnmp_table_registration_info *table_info;

    DEBUGMSGTL(("boardSlotTable:init",
                "initializing table boardSlotTable\n"));

    reg =
        netsnmp_create_handler_registration("boardSlotTable",
                                            boardSlotTable_handler,
                                            boardSlotTable_oid,
                                            boardSlotTable_oid_len,
                                            HANDLER_CAN_RONLY);

    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
    netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER,   /* index: boardSlotID */
                                     0);
    table_info->min_column = COLUMN_BOARDSLOTID;
    table_info->max_column = COLUMN_BOARDWORKMODE;

    iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
    iinfo->get_first_data_point = boardSlotTable_get_first_data_point;
    iinfo->get_next_data_point = boardSlotTable_get_next_data_point;
    iinfo->table_reginfo = table_info;

    netsnmp_register_table_iterator(reg, iinfo);
    netsnmp_inject_handler_before(reg,
                                  netsnmp_get_cache_handler
                                  (BOARDSLOTTABLE_TIMEOUT,
                                   boardSlotTable_load,
                                   boardSlotTable_free, boardSlotTable_oid,
                                   boardSlotTable_oid_len),
                                  TABLE_ITERATOR_NAME);

    /*
     * Initialise the contents of the table here 
     */
}

    /*
     * Typical data structure for a row entry 
     */
struct boardSlotTable_entry {
    /*
     * Index values 
     */
    long            boardSlotID;

    /*
     * Column values 
     */
    long            boardSlotID;
    long            boardType;
    char            boardSeriesNumber[NNN];
    size_t          boardSeriesNumber_len;
    long            boardWorkMode;

    /*
     * Illustrate using a simple linked list 
     */
    int             valid;
    struct boardSlotTable_entry *next;
};

struct boardSlotTable_entry *boardSlotTable_head;

/*
 * create a new row in the (unsorted) table 
 */
struct boardSlotTable_entry *
boardSlotTable_createEntry(long boardSlotID,)
{
    struct boardSlotTable_entry *entry;

    entry = SNMP_MALLOC_TYPEDEF(struct boardSlotTable_entry);
    if (!entry)
        return NULL;

    entry->boardSlotID = boardSlotID;
    entry->next = boardSlotTable_head;
    boardSlotTable_head = entry;
    return entry;
}

/*
 * remove a row from the table 
 */
void
boardSlotTable_removeEntry(struct boardSlotTable_entry *entry)
{
    struct boardSlotTable_entry *ptr, *prev;

    if (!entry)
        return;                 /* Nothing to remove */

    for (ptr = boardSlotTable_head, prev = NULL;
         ptr != NULL; prev = ptr, ptr = ptr->next) {
        if (ptr == entry)
            break;
    }
    if (!ptr)
        return;                 /* Can't find it */

    if (prev == NULL)
        boardSlotTable_head = ptr->next;
    else
        prev->next = ptr->next;

    SNMP_FREE(entry);           /* XXX - release any other internal resources */
}

/*
 * Example cache handling - set up linked list from a suitable file 
 */
int
boardSlotTable_load(netsnmp_cache * cache, void *vmagic)
{
    FILE           *fp;
    struct boardSlotTable_entry *this;
    char            buf[STRMAX];

    /*
     * The basic load routine template assumes that the data to
     * be reported is held in a file - with one row of the file
     * for each row of the table.
     * If your data is available via a different API, you
     * should amend this initial block (and the control of the
     * 'while' loop) accordingly.
     * 'XXX' marks where the template is incomplete and
     * code will definitely need to be added. 
     */

    fp = fopen("/data/for/boardSlotTable", "r");
    if (!fp) {
        return -1;
    }
    while (fgets(buf, STRMAX, fp)) {
        this = SNMP_MALLOC_TYPEDEF(struct boardSlotTable_entry);
        /*
         * XXX - Unpick 'buf' to extract the individual field values
         * and then populate the 'this' data structure with them 
         */

        this->next = boardSlotTable_head;
        boardSlotTable_head = this;     /* Iterate helper is fine with unordered lists! */
    }
    fclose(fp);
    return 0;                   /* OK */
}

void
boardSlotTable_free(netsnmp_cache * cache, void *vmagic)
{
    struct boardSlotTable_entry *this, *that;

    for (this = boardSlotTable_head; this; this = that) {
        that = this->next;
        SNMP_FREE(this);        /* XXX - release any other internal resources */
    }
    boardSlotTable_head = NULL;
}

/*
 * Example iterator hook routines - using 'get_next' to do most of the work 
 */
netsnmp_variable_list *
boardSlotTable_get_first_data_point(void **my_loop_context,
                                    void **my_data_context,
                                    netsnmp_variable_list * put_index_data,
                                    netsnmp_iterator_info *mydata)
{
    *my_loop_context = boardSlotTable_head;
    return boardSlotTable_get_next_data_point(my_loop_context,
                                              my_data_context,
                                              put_index_data, mydata);
}

netsnmp_variable_list *
boardSlotTable_get_next_data_point(void **my_loop_context,
                                   void **my_data_context,
                                   netsnmp_variable_list * put_index_data,
                                   netsnmp_iterator_info *mydata)
{
    struct boardSlotTable_entry *entry =
        (struct boardSlotTable_entry *) *my_loop_context;
    netsnmp_variable_list *idx = put_index_data;

    if (entry) {
        snmp_set_var_typed_integer(idx, ASN_INTEGER, entry->boardSlotID);
        idx = idx->next_variable;
        *my_data_context = (void *) entry;
        *my_loop_context = (void *) entry->next;
        return put_index_data;
    } else {
        return NULL;
    }
}


/** handles requests for the boardSlotTable table */
int
boardSlotTable_handler(netsnmp_mib_handler *handler,
                       netsnmp_handler_registration *reginfo,
                       netsnmp_agent_request_info *reqinfo,
                       netsnmp_request_info *requests)
{

    netsnmp_request_info *request;
    netsnmp_table_request_info *table_info;
    struct boardSlotTable_entry *table_entry;

    DEBUGMSGTL(("boardSlotTable:handler", "Processing request (%d)\n",
                reqinfo->mode));

    switch (reqinfo->mode) {
        /*
         * Read-support (also covers GetNext requests)
         */
    case MODE_GET:
        for (request = requests; request; request = request->next) {
            table_entry = (struct boardSlotTable_entry *)
                netsnmp_extract_iterator_context(request);
            table_info = netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case COLUMN_BOARDSLOTID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardSlotID);
                break;
            case COLUMN_BOARDTYPE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardType);
                break;
            case COLUMN_BOARDSERIESNUMBER:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR,
                                         table_entry->boardSeriesNumber,
                                         table_entry->
                                         boardSeriesNumber_len);
                break;
            case COLUMN_BOARDWORKMODE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardWorkMode);
                break;
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            }
        }
        break;

    }
    return SNMP_ERR_NOERROR;
}

修改后的文件:

/*
 * Note: this file originally auto-generated by mib2c using
 *  $
 */

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "boardSlotTable.h"

#define SN_SIZE 64
#define STRMAX  128


/*
* Typical data structure for a row entry 
*/
struct boardSlotTable_entry {
    /*
     * Index values 
     */
    //long            boardSlotID;

    /*
     * Column values 
     */
    long            boardSlotID;
    long            boardType;
    char            boardSeriesNumber[SN_SIZE];
    size_t          boardSeriesNumber_len;
    long            boardWorkMode;

    /*
     * Illustrate using a simple linked list 
     */
    int             valid;
    struct boardSlotTable_entry *next;
};

struct boardSlotTable_entry *boardSlotTable_head;

/*
 * create a new row in the (unsorted) table 
 */
//参数是可变的,自定义
struct boardSlotTable_entry *
boardSlotTable_createEntry(long boardSlotID,long boardtype,char *boardSeriesNumber,size_t boardSeriesNumber_len)
{
    struct boardSlotTable_entry *entry;

    entry = SNMP_MALLOC_TYPEDEF(struct boardSlotTable_entry);
    if (!entry)
        return NULL;
	
    entry->boardSlotID = boardSlotID;
	memcpy(entry->boardSeriesNumber,boardSeriesNumber,strlen(boardSeriesNumber));
	entry->boardSeriesNumber_len = boardSeriesNumber_len;
	entry->boardType = boardtype;	
	entry->next = boardSlotTable_head;
	boardSlotTable_head = entry;
    return entry;
}

/*
 * remove a row from the table 
 */
void
boardSlotTable_removeEntry(struct boardSlotTable_entry *entry)
{
    struct boardSlotTable_entry *ptr, *prev;

    if (!entry)
        return;                 /* Nothing to remove */

    for (ptr = boardSlotTable_head, prev = NULL;
         ptr != NULL; prev = ptr, ptr = ptr->next) {
        if (ptr == entry)
            break;
    }
    if (!ptr)
        return;                 /* Can't find it */

    if (prev == NULL)
        boardSlotTable_head = ptr->next;
    else
        prev->next = ptr->next;

    SNMP_FREE(entry);           /* XXX - release any other internal resources */
}





/** 初始化testTable模块 */
void
init_boardSlotTable(void)
{
    /*
     * here we initialize all the tables we're planning on supporting 
     */
    initialize_table_boardSlotTable();
}

//#Determine the first/last column names

/** Initialize the boardSlotTable table by defining its contents and how it's structured */
/**注册表格信息 */
void
initialize_table_boardSlotTable(void)
{
    const oid       boardSlotTable_oid[] =
        { 1, 3, 6, 1, 4, 1, 88888, 1, 1, 20, 2 };
    const size_t    boardSlotTable_oid_len =
        OID_LENGTH(boardSlotTable_oid);
    netsnmp_handler_registration *reg;
    netsnmp_iterator_info *iinfo;
    netsnmp_table_registration_info *table_info;

    DEBUGMSGTL(("boardSlotTable:init",
                "initializing table boardSlotTable\n"));
	//注册OID的响应句柄
    reg =
        netsnmp_create_handler_registration("boardSlotTable",
                                            boardSlotTable_handler,
                                            boardSlotTable_oid,
                                            boardSlotTable_oid_len,
                                            HANDLER_CAN_RONLY);

    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
	//注册表格的索引信息
    netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER,   /* index: boardSlotID */
                                     0);
	//定义表格第一列和最后一列
    table_info->min_column = COLUMN_BOARDSLOTID;
    table_info->max_column = COLUMN_BOARDWORKMODE;
	//注册迭代器
    iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);
    iinfo->get_first_data_point = boardSlotTable_get_first_data_point;
    iinfo->get_next_data_point = boardSlotTable_get_next_data_point;
    iinfo->table_reginfo = table_info;

    netsnmp_register_table_iterator(reg, iinfo);
	//下面是针对OID实现缓存。当不提供缓存机制时可不实现
	//testTable_load 为钩子函数 testTable_free 为相应资源释放函数
    netsnmp_inject_handler_before(reg,
                                  netsnmp_get_cache_handler
                                  (BOARDSLOTTABLE_TIMEOUT,
                                   boardSlotTable_load,
                                   boardSlotTable_free, boardSlotTable_oid,
                                   boardSlotTable_oid_len),
                                  TABLE_ITERATOR_NAME);

    /*
     * Initialise the contents of the table here 
     */
    boardSlotTable_createEntry(1,0,"TRY-621-0",strlen("TRY-621-0"));
	boardSlotTable_createEntry(2,1,"TRY-621-1",strlen("TRY-621-1"));
	boardSlotTable_createEntry(3,2,"TRY-621-2",strlen("TRY-621-2"));
	boardSlotTable_createEntry(4,3,"TRY-621-3",strlen("TRY-621-3"));
}


/*
 * Example cache handling - set up linked list from a suitable file 
 */
int
boardSlotTable_load(netsnmp_cache * cache, void *vmagic)
{
    FILE           *fp;
    struct boardSlotTable_entry *this;
    char            buf[STRMAX];

    /*
     * The basic load routine template assumes that the data to
     * be reported is held in a file - with one row of the file
     * for each row of the table.
     * If your data is available via a different API, you
     * should amend this initial block (and the control of the
     * 'while' loop) accordingly.
     * 'XXX' marks where the template is incomplete and
     * code will definitely need to be added. 
     */

    fp = fopen("/data/for/boardSlotTable", "r");
    if (!fp) {
        return -1;
    }
    while (fgets(buf, STRMAX, fp)) {
        this = SNMP_MALLOC_TYPEDEF(struct boardSlotTable_entry);
        /*
         * XXX - Unpick 'buf' to extract the individual field values
         * and then populate the 'this' data structure with them 
         */

        this->next = boardSlotTable_head;
        boardSlotTable_head = this;     /* Iterate helper is fine with unordered lists! */
    }
    fclose(fp);
    return 0;                   /* OK */
}

void
boardSlotTable_free(netsnmp_cache * cache, void *vmagic)
{
    struct boardSlotTable_entry *this, *that;

    for (this = boardSlotTable_head; this; this = that) {
        that = this->next;
        SNMP_FREE(this);        /* XXX - release any other internal resources */
    }
    boardSlotTable_head = NULL;
}

/*
 * Example iterator hook routines - using 'get_next' to do most of the work 
 */
//获取第一行:表结构链表
netsnmp_variable_list *
boardSlotTable_get_first_data_point(void **my_loop_context,
                                    void **my_data_context,
                                    netsnmp_variable_list * put_index_data,
                                    netsnmp_iterator_info *mydata)
{
	//只需要赋值表结构链表变量 boardSlotTable_head
    *my_loop_context = boardSlotTable_head;
    return boardSlotTable_get_next_data_point(my_loop_context,
                                              my_data_context,
                                              put_index_data, mydata);
}
//迭代器 获取下一行数据
netsnmp_variable_list *
boardSlotTable_get_next_data_point(void **my_loop_context,
                                   void **my_data_context,
                                   netsnmp_variable_list * put_index_data,
                                   netsnmp_iterator_info *mydata)
{
    struct boardSlotTable_entry *entry =
        (struct boardSlotTable_entry *) *my_loop_context;
    netsnmp_variable_list *idx = put_index_data;

    if (entry) {
        snmp_set_var_typed_integer(idx, ASN_INTEGER, entry->boardSlotID);
        idx = idx->next_variable;
        *my_data_context = (void *) entry;
        *my_loop_context = (void *) entry->next;
        return put_index_data;
    } else {
        return NULL;
    }
}

//处理请求的回调函数都类型:根据请求类型和列号进行查询
/** handles requests for the boardSlotTable table */
int
boardSlotTable_handler(netsnmp_mib_handler *handler,
                       netsnmp_handler_registration *reginfo,
                       netsnmp_agent_request_info *reqinfo,
                       netsnmp_request_info *requests)
{

    netsnmp_request_info *request;
    netsnmp_table_request_info *table_info;
    struct boardSlotTable_entry *table_entry;

    DEBUGMSGTL(("boardSlotTable:handler", "Processing request (%d)\n",
                reqinfo->mode));

    switch (reqinfo->mode) {
        /*
         * Read-support (also covers GetNext requests)
         */
    case MODE_GET:
        for (request = requests; request; request = request->next) {
            table_entry = (struct boardSlotTable_entry *)
                netsnmp_extract_iterator_context(request);
            table_info = netsnmp_extract_table_info(request);

            switch (table_info->colnum) {
            case COLUMN_BOARDSLOTID:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardSlotID);
                break;
            case COLUMN_BOARDTYPE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardType);
                break;
            case COLUMN_BOARDSERIESNUMBER:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_value(request->requestvb, ASN_OCTET_STR,
                                         table_entry->boardSeriesNumber,
                                         table_entry->
                                         boardSeriesNumber_len);
                break;
            case COLUMN_BOARDWORKMODE:
                if (!table_entry) {
                    netsnmp_set_request_error(reqinfo, request,
                                              SNMP_NOSUCHINSTANCE);
                    continue;
                }
                snmp_set_var_typed_integer(request->requestvb, ASN_INTEGER,
                                           table_entry->boardWorkMode);
                break;
            default:
                netsnmp_set_request_error(reqinfo, request,
                                          SNMP_NOSUCHOBJECT);
                break;
            }
        }
        break;

    }
    return SNMP_ERR_NOERROR;
}

运行测试:
./snmpwalk -v2c -c public localhost 1.3.6.1.4.1.88888.1.1.20.2
./snmpget -v2c -c public localhost 1.3.6.1.4.1.88888.1.1.20.2.1.1.1
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值