netbench_server.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

/*内部宏声明*/
#define HOSTNAMESIZE             255
#define PORTBUFSIZE              10
#define HOST_NAME                "127.0.0.1"
#define TEST_PORT                "12865"
//#define LISTEN_PORT                       "5000"
#define INVALID_SOCKET            -1
#define SOCKET_ERROR              -1
#define DO_TCP_STREAM             22
#define TCP_STREAM_RESPONSE       21


/*内部全局变量声明*/
char rem_host_name[HOSTNAMESIZE];
char local_host_name[HOSTNAMESIZE];
char rem_port[PORTBUFSIZE];
char local_port[PORTBUFSIZE];
char listen_port[PORTBUFSIZE];
int rem_addr_family;
int local_addr_family;
char *server_name;
FILE *where = stdout;
int server_control;
struct timeval    time_begin, time_stop;
float lib_elapsed;

union   netbench_request_struct  netbench_request;
union   netbench_response_struct netbench_response;

/*define send and recv size vairables*/
static int ls_size_req,
           lr_size_req,
           ls_size,
           lr_size,
           rs_size_req,
           rr_size_req,
           rs_size,
           rr_size;
int recv_size;
int recv_width;

 

/*内部结构体声明*/
/*struct addrinfo*/
struct addrinfo {
    int ai_flags;
    int ai_family;
    int ai_socktype;
    int ai_protocol;
    socklen_t ai_addrlen;
    struct sockaddr *ai_addr;
};
struct tcp_stream_request_struct {
  int send_buf_size;
  int recv_buf_size; /* how big does the client want it - the */
   /* receive socket buffer that is */
  int receive_size;   /* how many bytes do we want to receive at one */
   /* time? */
  int recv_alignment; /* what is the alignment of the receive */
   /* buffer? */
  int recv_offset;    /* and at what offset from that alignment? */
  int no_delay;       /* do we disable the nagle algorithm for send */
   /* coalescing? */
  int measure_cpu; /* does the client want server cpu utilization */
   /* measured? */
  float cpu_rate; /* do we know how fast the cpu is already? */
  int test_length; /* how long is the test?  */
  int so_rcvavoid;    /* do we want the remote to avoid copies on */
   /* receives? */
  int so_sndavoid;    /* do we want the remote to avoid send copies? */
  int   dirty_count;    /* how many integers in the receive buffer */
   /* should be made dirty before calling recv? */ 
  int   clean_count;    /* how many integers should be read from the */
   /* recv buffer before calling recv? */
  int   port;           /* the port to which the recv side should bind
      to allow netperf to run through those evil
      firewall things */
  int   ipfamily;       /* the address family of ipaddress */
};

struct tcp_stream_response_struct {
  int recv_buf_size; /* how big does the client want it */
  int receive_size;
  int no_delay;
  int measure_cpu; /* does the client want server cpu */
  int test_length; /* how long is the test?  */
  int send_buf_size;
  int data_port_number; /* connect to me here */
  float cpu_rate;  /* could we measure */
  int so_rcvavoid; /* could the remote avoid receive copies? */
  int so_sndavoid; /* could the remote avoid send copies? */
};

struct tcp_stream_results_struct {
  double         bytes_received;
  unsigned int  recv_calls; 
  float          elapsed_time; /* how long the test ran */
  float          cpu_util; /* -1 if not measured */
  float          serv_dem; /* -1 if not measured */
  int            cpu_method;    /* how was cpu util measured? */
  int            num_cpus;      /* how many CPUs had the remote? */
};

union netbench_request_struct {
  struct {
    int     request_type;
    int     dummy;
    int     test_specific_data[MAXSPECDATA];
  } content;
  double dummy;
};

union netbench_response_struct {
  struct {
    int response_type;
    int serv_errno;
    int test_specific_data[MAXSPECDATA];
  } content;
  double dummy;
};

struct ring_elt {
  struct ring_elt *next;  /* next element in the ring */
  char *buffer_base;      /* in case we have to free it at somepoint */
  char *buffer_ptr;       /* the aligned and offset pointer */
};


/*start*/
int main(int argc, char **argv)
{
   server_name = (char *)malloc(strlen(argv[0]) + 1);
   if(server_name == NULL){
   fprintf(where,"%s malloc (%d) failed!/n",server_name, strlen(argv[0]) + 1);
 fflush(where);
 return (1);
   }
  strcpy(server_name, argv[0]);
  set_defaults_opt();
  set_user_opt(argc, argv);
  /*set up server*/
  set_up_server(local_host_name, listen_port, local_addr_family);
  return (0);
 
}
/*******************************************************
函数功能:设置全局变量的默认参数
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*********************************************************/
void set_defaults_opt()
{
  strncpy(local_host_name, "0.0.0.0", sizeof(local_host_name));
  local_addr_family = AF_INET;
  strncpy(listen_port, TEST_PORT, sizeof(listen_port));

  /*set */
  recv_size = 0;
  recv_width = 0;
}
/*********************************************************
函数功能:设置全局变量的用户传入的参数
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
***********************************************************/
void set_user_opt(int argc, char ** argv)
{
   int c ;
   argc--;
   argv++;
   while((c= getopt(argc, argv)) != EOF)
   {
     switch (c) {
  case '?':
   usage();
   exit(0);
  case 'p':
  case 'P':
   argc--;
   argv++;
         strncpy(local_port,*argv[],PORTBUFSIZE);
   break;
  default:
   argc--;
   argv++;
   break;
  }
   }
}

/*******************************************************
函数功能:扫描用户输入的参数
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*********************************************************/
int  getopt(int argc, char **argv)
{
 char *PtrArg = *argv[];
 if('-' == *PtrArg)
 {
  return (*(++PtrArg));
 }
 else
 {
  return 0;
 }

}
/*******************************************************
函数功能:打印使用方法
输入参数:无
输出参数:无
作者:
时间:2009/07/31
描述:新生成函数
*********************************************************/
void usage()
{
   fprintf(where,"./%s -P port_num/n",server_name);
   fprintf(where,"each parameter as follows:/n");
   fprintf(where,">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>/n");
   fprintf(where,"P:local host port number/n");
   fprintf(where,">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>/n");
   fflush(where);
}
/*******************************************************
函数功能:
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*********************************************************/
void set_up_server(char hostname[], char port[], int af)
{
 struct addrinfo     local_res;
 struct sockaddr_in  peeraddr;
 int error;
 //int server_control;
 int on = 1;
 #ifdef DEBUG_INFO
 fprintf(where, "set_up_server called with host '%s' port '%s af_family '%d'/n", hostname, port, af);
 fflush(where);
 #endif
 memset(&local_res, 0, sizeof(struct addrinfo));
 local_res.ai_family = af;
 local_res.ai_socktype = SOCK_STREAM;
 local_res.ai_protocol = IPPROTO_TCP;
 local_res.ai_flags = 0;
 error = get_addr_info(hostname, port, &local_res);
 if(error < 0){
  fprintf(where, "get_addr_info error/n");
 fflush(where);
  }
 server_control = socket(local_res->ai_family, SOCK_STREAM, 0);
 
 if(server_control == INVALID_SOCKET){
  fprintf(where, "server_control create failed!/n");
 fflush(where);
 exit(-1);
  }

 if(setsockopt(server_control,
             SOL_SOCKET,
             SO_REUSEADDR,
             (char *)&on,
             sizeof(on)) == SOCKET_ERROR){

    fprintf(where, "setsockopt error/n");
      fflush(where);
   exit(-1);
  }

 if((bind (server_control,
         local_res->ai_addr,
         local_res->ai_addrlen) != SOCKET_ERROR) &&
         (listen (server_control, 5) !=SOCKET_ERROR)) {
         #ifdef DEBUG_INFO
     fprintf(where, "starting netserver at hostname %s port %s and family %d/n",
     hostname, port, af);
     fflush(where);
     #endif
  }
 else
  {
   fprintf(where,"set_up_server failed a bind or listen call/n");
  fflush(where);
  exit(-1);
  }

 for(;;)
  {
   if((server_sock = accept(server_control,
                         (struct sockaddr *)&peeraddr,
                         sizeof(peeraddr))) == INVALID_SOCKET)
    {
     fprintf(where, "set_up_server: accept failed error/n");
   fflush(where);
   exit(-1);
    }

  signal(SIGCHLD, SIG_IGN);

  switch(fork())
   {
     case -1:
    exit(-1);
    case 0:
   close(server_control);
   process_request();
   exit(0);
    default:
     close(server_sock);
   break;   
   }
  }
 return;
}
/***************************************************************
函数功能:
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*****************************************************************/ 
int get_addr_info(char *HostName,
                    char *HostPort,
                    struct addrinfo *info)
{
  u16 port;
  struct in_addr      temp_addr;
  struct sockaddr_in*  temp_sock = NULL;

  /*get port number and ip address*/
  if(HostPort != NULL)
   {
     if(is_integer(HostPort))
      {
        port = htons(atoi(HostPort));
      }
   else{
    port = htons(0);
    }
   }
  else
   {
     port = htons(0);
   }
  if(HostName != NULL)
   {
     if(is_address(HostName))
      {
          temp_addr.s_addr = inet_addr(nodename);
      }
   else
    {
        temp_addr.s_addr = htonl(INADDR_ANY);
    }
   
   }
  else
   {
     temp_addr.s_addr = htonl(INADDR_ANY);
   }

   /*fill the info->ai_addr and info->ai_addrlen*/
   info->ai_addrlen = sizeof(struct sockaddr_in);
   info->ai_addr = (struct sockaddr*)malloc(sizeof(sockaddr_in));
   if(info->ai_addr == NULL){
     fprintf(where,"ai_addr malloc failed/n");
  fflush(where);
  free(info->ai_addr);
  return SOCKET_ERROR;
   }
    temp_sock = (struct sockaddr_in*)info->ai_addr;
    temp_sock->sin_family = AF_NORMAL;
 temp_sock->sin_port = port;
 memcpy(&temp_sock->sin_addr, &temp_addr, sizeof(struct in_addr));
 return (0);
  
   
}


/***************************************************************
函数功能:处理请求类型,进入相应的处理函数
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*****************************************************************/
void process_request()
{
  while(1){
   recv_request();
 switch(netbench_request.content.request_type) {
  case DO_TCP_STREAM
   recv_tcp_stream();
  break;
  default:
   exit(0);
  }
   }
}
/***************************************************************
函数功能:接收请求信息
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*****************************************************************/
void recv_request()
{
 int   tot_bytes_recvd = 0;
 int   bytes_recvd = 0;
 int   bytes_left;
 char  *buf = (char *)&netbench_request;
 int   buflen = sizeof(netbench_request);
 bytes_left = buflen;
 
 while((tot_bytes_recvd != buflen) && (bytes_recvd = recv(server_sock, buf, bytes_left, 0)) > 0){
  tot_bytes_recvd += bytes_recvd;
 buf             += bytes_recvd;
 bytes_left      -= bytes_recvd;
  }

 #ifdef DEBUG_INFO
 fprintf(where,"recv_request: received %d bytes of request./n",tot_bytes_recvd);
 fflush(where);
 #endif DEBUG_INFO

 if(bytes_recvd <= 0){
 fprintf(where,"recv_request: error on recv/n");
 fflush(where);
 exit(1);
  }
 
 if(tot_bytes_recvd < buflen){
 fprintf(where, "recv_request: partial request received of %d bytes/n",tot_bytes_recvd);
 fflush(where);
 exit(1);
  }
}
/***************************************************************
函数功能:接收TCP 块数据
输入参数:无
输出参数:无
作者:
时间:2009/08/7
描述:新生成函数
*****************************************************************/
void recv_tcp_stream()
{
  struct addrinfo local_data_res;
  struct sockaddr_in server_addr, peer_addr;
  int addrlen;
  char local_data_name[HOSTNAMESIZE];
  char port[PORTBUFSIZE];
  int error;
  unsigned int receive_calls;
  float elapsed_time;
  double   bytes_received;
  int lis_sock,data_sock;
  int recv_len;
  struct ring_elt *recv_ring;
 
  #ifdef DIRTY
  int   *message_int_ptr;
  int   dirty_count;
  int   clean_count;
  int   i;
  #endif
 
  struct  tcp_stream_request_struct    *tcp_stream_request;
  struct  tcp_stream_response_struct   *tcp_stream_response;
  struct  tcp_stream_results_struct    *tcp_stream_results;


  tcp_stream_request  =
   (struct tcp_stream_request_struct *)netbench_request.content.test_specific_data;
  tcp_stream_response  =
   (struct tcp_stream_response_struct *)netbench_response.content.test_specific_data;
  tcp_stream_results   =
   (struct tcp_stream_results_struct *)netbench_response.content.test_specific_data;

  memset(tcp_stream_request, 0, sizeof(struct  tcp_stream_request_struct));
  memset(tcp_stream_response, 0, sizeof(struct tcp_stream_response_struct));
  memset(tcp_stream_results, 0, sizeof(struct  tcp_stream_results_struct));

  #ifdef DEBUG_INFO
  fprintf(where, "netbench_server:recv_tcp_stream :entering.../n");
  fflush(where);
  #endif

  netbench_response.content.response_type = TCP_STREAM_RESPONSE;
  #ifdef DEBUG_INFO
  fprintf(where, "recv_tcp_stream: request type is %d",netbench_response.content.response_type);
  fflush(where);
  #endif
  ls_size_req = tcp_stream_request->send_buf_size;
  lr_size_req = tcp_stream_request->recv_buf_size;

  set_hostanme_port(local_data_name, port, tcp_stream_request->port);
  memset(&local_data_res, 0, sizeof(struct addrinfo));
  local_data_res.ai_family = tcp_stream_request->ipfamily;
  local_data_res.ai_socktype = SOCK_STREAM;
  local_data_res.ai_protocol = IPPROTO_TCP;
  local_data_res.ai_flags = 0;
  error = get_addr_info(local_data_name, port, &local_data_res);
  if(error < 0){
  fprintf(where, "get_addr_info error/n");
 fflush(where);
  }
  lis_sock = create_data_sock(&local_data_res);

  if(lis_sock == INVALID_SOCKET) {

   fprintf(where,"create listen socket error!/n");
    fflush(where);
 exit(-1);
   }
  if(tcp_stream_request->receive_size == 0) {
   if(lr_size > 0) {
  recv_size = lr_size;
    }
 else {
  recv_size = 4096;
  }
   }
  else {
    recv_size = tcp_stream_request->receive_size;
   }
  #ifdef DEBUG_INFO
  fprintf(where, "set recv buff size is %d/n",recv_size);
  fflush(where);
  #endif
  if (recv_width == 0){
   recv_width = (lr_size/recv_size) + 1;
 if(recv_width == 1) recv_width++;
   }

  recv_ring = alloc_ring_buff(recv_width,
                             recv_size,
                             tcp_stream_request->recv_alignment,
                             tcp_stream_request->recv_offset);
  #ifdef DEBUG_INFO
  fprintf(where,"recv_tcp_stream:receive alignment and offset set.../n");
  fflush(where);
  #endif

  if(listen(lis_sock, 5) == SOCKET_ERROR) {
   fprintf(where,"recv_tcp_stream: listen error/n");
 fflush(where);
 close(lis_sock);
 exit(-1);
   }
  addrlen = sizeof(server_addr);
  if(getsockname(lis_sock,
                (struct sockaddr *)&server_addr,
                &addrlen) == SOCKET_ERROR) {
                fprintf(where,"recv_tcp_stream: getsockname error!/n");
     fflush(where);
     close(lis_sock);
     exit(-1);
   }

  tcp_stream_response->data_port_number = (int) ntohs(server_addr.sin_port);
  netbench_response.content.serv_errno  = 0;

  tcp_stream_response->send_buf_size = ls_size;
  tcp_stream_response->recv_buf_size = lr_size;
  tcp_stream_response->receive_size  = recv_size;
  tcp_stream_response->no_delay      = 0;
  tcp_stream_response->so_rcvavoid   = 0;
  tcp_stream_response->so_sndavoid   = 0;

  send_response();

  addrlen = sizeof(peer_addr);

  if((data_sock = accept(lis_sock,
                        (struct sockaddr *)&peer_addr,
                        &addrlen)) == INVALID_SOCKET) {
        fprintf(where,"recv_tcp_stream:accept error!/n");
   fflush(where);
   close(lis_sock);
   exit(-1);
 }
  cpu_start();

  #ifdef DIRTY
  srand((int)getpid());
  dirty_count = tcp_stream_request->dirty_count;
  clean_count = tcp_stream_request->clean_count;
  message_int_ptr = (int *)recv_ring->buffer_ptr;
  for(i = 0; i < dirty_count; i++) {
   *message_int_ptr = rand();
 message_int_ptr++;
   }
  for(i = 0; i < clean_count; i++) {

   dirty_count = *message_int_ptr;
    message_int_ptr++;
   }
  #endif

   bytes_received = 0;
   receive_calls   = 0;

   while((recv_len = recv(data_sock, recv_ring->buffer_ptr, recv_size, 0)) != 0) {
    if(recv_len < 0) {
  fprintf(where, "recv_tcp_stream recv error!/n");
  fflush(where);
  exit(-1);
     }
 bytes_received += recv_len;
 receive_calls++;

 recv_ring = recv_ring->next;
 
 #ifdef DIRTY
    message_int_ptr = (int *)(recv_ring->buffer_ptr);
    for (i = 0; i < dirty_count; i++) {
      *message_int_ptr = rand();
      message_int_ptr++;
    }
    for (i = 0; i < clean_count; i++) {
      dirty_count = *message_int_ptr;
      message_int_ptr++;
    }
    #endif
    }

 if(shutdown(data_sock, 1) == SOCKET_ERROR) {
  fprintf(where,"shutdown data_sock error!/n");
  fflush(where);
  exit(-1);
  }

 cpu_stop(&elapsed_time);

 #ifdef DEBUG_INFO
    fprintf(where,
     "recv_tcp_stream: got %g bytes/n",
     bytes_received);
    fprintf(where,
     "recv_tcp_stream: got %d recvs/n",
     receive_calls);
    fflush(where);
 #endif

 tcp_stream_results->bytes_received = htond(bytes_received);
    tcp_stream_results->elapsed_time = elapsed_time;
    tcp_stream_results->recv_calls = receive_calls;
 
    tcp_stream_results->cpu_method = 0;
    tcp_stream_results->num_cpus   = 0;

 send_response();

 /*close sock*/
 close(data_sock);
 close(lis_sock);
  }
/*******************************************************
函数功能:记录测试的开始时间
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
void cpu_start()
{
  gettimeofday(&time_begin, NULL);
}
/*******************************************************
函数功能:记录测试的结束时间
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
void cpu_stop(float *elapsed)
{
  int sec;
  int usec;
  gettimeofday(&time_stop, NULL);
  if (time_stop.tv_usec < time_begin.tv_usec) {
    time_stop.tv_usec += 1000000;
    time_stop.tv_sec  -= 1;
    }
  sec     = time_stop.tv_sec - time_begin.tv_sec;
  usec    = time_stop.tv_usec - time_begin.tv_usec;
  lib_elapsed     = (float)sec + ((float)usec/(float)1000000.0);
 
  *elapsed = lib_elapsed;
}

/*******************************************************
函数功能:回应函数
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
void send_response()
{
 int  counter = 0;
 int  bytes_send = 0;
 #ifdef DEBUG_INFO
 fprintf(where,"send response.../n");
 fflush(where);
 #endif

  if ((bytes_send = send(server_control,
           (char *)&netbench_response,
           sizeof(netbench_response),
           0)) != sizeof(netbench_response)) {
    perror("send_response: send call failure");
 fprintf(where, "BytesSend: %d/n", bytes_send);
    exit(1);
  }
}

/*******************************************************
函数功能:分配缓冲区
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
struct ring_elt *alloc_ring_buff(unsigned int width, unsigned int size, int align, int offset)
{
 
 struct ring_elt *first_link = NULL;
 struct ring_elt *temp_link  = NULL;
 struct ring_elt *prev_link;
 int i;
 int malloc_size;

 malloc_size = size + align + offset;
 assert(width >= 1);

 prev_link = NULL;
 for(i = 1; i <= width; i++){
  temp_link = (struct ring_elt *)malloc(sizeof(struct ring_elt));
 if(temp_link == NULL){
  printf("malloc(%u) failed!/n", sizeof(struct ring_elt));
  free(temp_link);
  exit(1);
  }
 if(i == 1){
  first_link = temp_link;
  prev_link  = temp_link;
  }
 temp_link->buffer_base = (char *)malloc(malloc_size);
 if(temp_link->buffer_base == NULL){
  printf("malloc(%d) failed!/n",malloc_size);
  free(temp_link->buffer_base);
  exit(1);
  }
 temp_link->buffer_ptr += offset;
 //temp_link->next = NULL;
 prev_link->next = temp_link;
 if(i == width){
  temp_link->next = first_link;
  }
 prev_link = temp_link;
  }
 return (first_link);
}

/*******************************************************
函数功能:
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
void set_hostanme_port(char *hostname, char *portstr, int port)
{
 strcpy(hostname, "0.0.0.0");
 sprintf(portstr, "%u", port);
}

/*******************************************************
函数功能:创建传输数据套接字
输入参数:无
输出参数:无
作者:
时间:2009/08/2
描述:新生成函数
*********************************************************/
int create_data_sock(struct addrinfo *res)
{
  int temp_sock;
  int on = 1;
  temp_sock = socket(res->ai_family,
         res->ai_socktype,
         res->ai_protocol);
  if(temp_sock == INVALID_SOCKET){
       fprintf(where,
     "netbench: create_data_socket: socket: errno %d fam %s type %s prot %s errmsg %s/n",
     inet_ftos(res->ai_family),
     inet_ttos(res->ai_socktype),
     inet_ptos(res->ai_protocol));
    fflush(where);
    exit(1);
   }
  #ifdef DEBUG_INFO
  fprintf(where,"create_data_socket: socket %d obtained.../n", temp_sock);
  fflush(where);
  #endif

  /*set socket buffer size,send and recv*/
  set_sock_buffer(temp_sock, SEND_SIZE, ls_size_req, &ls_size);
  set_sock_buffer(temp_sock, RECV_SIZE, lr_size_req, &lr_size);
  /*set reuse address property*/
  if(setsockopt(temp_sock,
               SOL_SOCKET.
               SO_REUSEADDR,
               &on,
               sizeof(on)) < 0){
               fprintf(where, "netbench:create_data_sock:SO_REUSEADDR failled/n");
    fflush(where);
   }
  /*bind the sock*/
    if (bind(temp_sock,
    res->ai_addr,
    res->ai_addrlen) < 0) {
      fprintf(where,
       "netbench: create_data_socket: data socket bind failed errno %d/n");
      fprintf(where," port: %d/n",get_port_number(res));
      fflush(where);
  }
 return(temp_sock);
}

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值