linux网络编程:并发服务器的模型

本文转自http://blog.csdn.net/liangxanhai/article/details/8881766

感谢原文作者的分享。

这篇博客主要是参考了http://renwen0524.blog.163.com/blog/static/7301945520116116016141/,这篇博客把linux网络编程的并发服务器的模型整理得很详细了,这里我是在此基础上加上了一些我自己的理解。

对于进行套接字编程,选择一个号的服务器模型是非常重要的,为了实现并发的服务,有以下几种方法可以做到:

1、并发服务器模型之一:预先分配进程个数。
2、并发服务器模型之二:预先分配多线程个数,使用互斥锁。
3、并发服务器模型之三:单客户端单进程,统一accept。
4、并发服务器模型之四:单客户端单线程,统一accept。
5、并发服务器模型之五:IO复用循环服务器。


(1)预先分配进程个数:

方法:在服务器端,主程序提前构建多个子进程,当客户端的请求到来的时候,系统从进程池中选取一个子进程来处理客户端的连接,每个子进程处理一个客户端的请求。具体模型为如下:


服务器端的代码为:

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


#define BUFLEN 1024
#define PIDNUM 3


/*******************并发服务器模型之一:预先分配好了进程的个数**********************/


static void handle_fork(int sockfd){
    int newfd;
    struct sockaddr_in c_addr;
    char buf[BUFLEN];
    socklen_t len;
    time_t now;
    while(1){
        len = sizeof(struct sockaddr);
        if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){
            perror("accept");        
            exit(errno);
        }else
        printf("\n*****************通信开始***************\n");
        printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));


        /******处理客户端请求*******/
        bzero(buf,BUFLEN);
        len = recv(newfd,buf,BUFLEN,0);
        if(len >0 && !strncmp(buf,"TIME",4)){
            bzero(buf,BUFLEN);
            /*获取系统当前时间*/
            now = time(NULL);
            /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
            sprintf(buf,"%24s\r\n",ctime(&now));
            //******发送系统时间*******/
            send(newfd,buf,strlen(buf),0);
        }
        /*关闭通讯的套接字*/
        close(newfd);
    }
}


int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in s_addr;
    unsigned int port, listnum;
    pid_t pid[PIDNUM];
    
    /*建立socket*/
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        perror("socket");
        exit(errno);
    }else
        printf("socket create success!\n");
    /*设置服务器端口*/    
    if(argv[2])
        port = atoi(argv[2]);
    else
        port = 4567;
    /*设置侦听队列长度*/
    if(argv[3])
        listnum = atoi(argv[3]);
    else
        listnum = 3;
    /*设置服务器ip*/
    bzero(&s_addr, sizeof(s_addr));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(port);
    if(argv[1])
        s_addr.sin_addr.s_addr = inet_addr(argv[1]);
    else
        s_addr.sin_addr.s_addr = INADDR_ANY;
    /*把地址和端口帮定到套接字上*/
    if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
        perror("bind");
        exit(errno);
    }else
        printf("bind success!\n");
    /*侦听本地端口*/
    if(listen(sockfd,listnum) == -1){
        perror("listen");
        exit(errno);    
    }else
        printf("the server is listening!\n");
    /*处理客户端的连接*/
    int i = 0;
    for(i = 0; i < PIDNUM; i++){
        pid[i] = fork();
        if(pid[i] == 0)
            handle_fork(sockfd);    
    }
    /*关闭服务器的套接字*/
    close(sockfd);
    return 0;
}

这个程序执行的时候各个子进程将变成孤儿进程。因为在子进程真正执行的时候父进程已经结束了。为了使子进程不变成孤儿进程的话,可以使用waitpid函数来等待子进程结束。具体用法就是在close(sockfd);语句之前加上相应的waitpid函数来等待子进程结束。


(2)预先分配n个线程,使用互斥锁

使用预先分配线程的并发服务器与之前使用预先分配进程的并发服务器的主要过程是一致的。主程序先建立多个处理线程,然后等待线程的结束,在多个线程中对客户端的请求进行处理。处理过程包括接收客户端的链接,处理数据,发送响应过程。这个可以结合线程池来进行相应的处理工作。

相应的模型如下


相应的服务器端的程序如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. #include <time.h>
  11. #include <pthread.h>

  12. #define BUFLEN 1024
  13. #define THREADNUM 3
  14. /*互斥量*/
  15. pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER;

  16. /****并发服务器模型之二:预先分配多个线程(单客户端单线程,各个线程统一accept,并且使用互斥锁) ****/

  17. static void *handle_thread(void *argv){
  18.     int sockfd = *((int *)argv);    
  19.     int newfd;
  20.     struct sockaddr_in c_addr;
  21.     socklen_t len;
  22.     char buf[BUFLEN];
  23.     time_t now;

  24.     while(1){
  25.         len = sizeof(struct sockaddr);
  26.         /*拒绝客户端的请求*/
  27.         pthread_mutex_lock(&ALOCK);
  28.         if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){
  29.             perror("accept");        
  30.             exit(errno);
  31.         }else{
  32.             printf("\n*****************通信开始***************\n");
  33.             printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));        
  34.         }
  35.         /*接受客户端的请求*/
  36.         pthread_mutex_unlock(&ALOCK);
  37.         /******处理客户端请求*******/
  38.         bzero(buf,BUFLEN);
  39.         len = recv(newfd,buf,BUFLEN,0);
  40.         if(len >&& !strncmp(buf,"TIME",4)){
  41.             bzero(buf,BUFLEN);
  42.             /*获取系统当前时间*/
  43.             now = time(NULL);
  44.             /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
  45.             sprintf(buf,"%24s\r\n",ctime(&now));
  46.             //******发送系统时间*******/
  47.             send(newfd,buf,strlen(buf),0);
  48.         }
  49.         /*关闭通讯的套接字*/
  50.         close(newfd);
  51.     }
  52.     return NULL;
  53. }

  54. int main(int argc, char **argv)
  55. {
  56.     int sockfd;
  57.     struct sockaddr_in s_addr;
  58.     unsigned int port, listnum;
  59.     pthread_t thread_s[THREADNUM];
  60.     
  61.     /*建立socket*/
  62.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  63.         perror("socket");
  64.         exit(errno);
  65.     }else
  66.         printf("socket create success!\n");
  67.     /*设置服务器端口*/    
  68.     if(argv[2])
  69.         port = atoi(argv[2]);
  70.     else
  71.         port = 4567;
  72.     /*设置侦听队列长度*/
  73.     if(argv[3])
  74.         listnum = atoi(argv[3]);
  75.     else
  76.         listnum = 3;
  77.     /*设置服务器ip*/
  78.     bzero(&s_addr, sizeof(s_addr));
  79.     s_addr.sin_family = AF_INET;
  80.     s_addr.sin_port = htons(port);
  81.     if(argv[1])
  82.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  83.     else
  84.         s_addr.sin_addr.s_addr = INADDR_ANY;
  85.     /*把地址和端口帮定到套接字上*/
  86.     if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
  87.         perror("bind");
  88.         exit(errno);
  89.     }else
  90.         printf("bind success!\n");
  91.     /*侦听本地端口*/
  92.     if(listen(sockfd,listnum) == -1){
  93.         perror("listen");
  94.         exit(errno);    
  95.     }else
  96.         printf("the server is listening!\n");
  97.     /*处理客户端的连接*/
  98.     int i = 0;
  99.     for(= 0; i < THREADNUM; i++){
  100.         /*创建线程来处理连接*/
  101.         pthread_create(&thread_s[i],NULL,handle_thread,(void *)&sockfd);        
  102.     }
  103.     /*等待线程结束*/
  104.     for(= 0; i < THREADNUM; i++){
  105.         pthread_join(thread_s[i],NULL);
  106.     }
  107.     /*关闭服务器的套接字*/
  108.     close(sockfd);
  109.     return 0;
  110. }

(3)单客户端单进程,统一accept

实质就会统一accept,当accept之后就给相应的开一个进程进行处理这个已经连接了的套接字,而另一方面,父进程则继续等待客户端的连接请求。即:主进程运行时,等待客户端连接的到来,当客户端连接请求到来时,服务器的accept()函数成功返回,此时服务器端进行进程分叉,父进程继续等待客户端的连接请求;而子进程则处理客户端的业务请求,接收客户端的数据,分析数据并返回结果。


相应的代码如下;

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. #include <time.h>

  11. #define BUFLEN 1024

  12. /*******************并发服务器模型之三:单客户端单进程,统一accept**********************/

  13. static void handle_request(int newfd){
  14.     char buf[BUFLEN];
  15.     int len;
  16.     time_t now;
  17.     /******处理客户端请求*******/
  18.     bzero(buf,BUFLEN);
  19.     len = recv(newfd,buf,BUFLEN,0);
  20.     if(len >&& !strncmp(buf,"TIME",4)){
  21.         bzero(buf,BUFLEN);
  22.         /*获取系统当前时间*/
  23.         now = time(NULL);
  24.         /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
  25.         sprintf(buf,"%24s\r\n",ctime(&now));
  26.         //******发送系统时间*******/
  27.         send(newfd,buf,strlen(buf),0);
  28.     }
  29.     /*关闭通讯的套接字*/
  30.     close(newfd);
  31. }

  32. static void handle_connect(int sockfd){
  33.     int newfd;
  34.     struct sockaddr_in c_addr;
  35.     socklen_t len;

  36.     while(1){
  37.         len = sizeof(struct sockaddr);
  38.         if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){
  39.             perror("accept");        
  40.             exit(errno);
  41.         }else{
  42.             /*创建进程来处理连接*/
  43.             if(fork() > 0)
  44.                 close(newfd);            
  45.             else{
  46.                 printf("\n*****************通信开始***************\n");
  47.                 printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
  48.                 handle_request(newfd);
  49.             }
  50.         }
  51.     }
  52. }

  53. int main(int argc, char **argv)
  54. {
  55.     int sockfd;
  56.     struct sockaddr_in s_addr;
  57.     unsigned int port, listnum;
  58.     
  59.     /*建立socket*/
  60.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  61.         perror("socket");
  62.         exit(errno);
  63.     }else
  64.         printf("socket create success!\n");
  65.     /*设置服务器端口*/    
  66.     if(argv[2])
  67.         port = atoi(argv[2]);
  68.     else
  69.         port = 4567;
  70.     /*设置侦听队列长度*/
  71.     if(argv[3])
  72.         listnum = atoi(argv[3]);
  73.     else
  74.         listnum = 3;
  75.     /*设置服务器ip*/
  76.     bzero(&s_addr, sizeof(s_addr));
  77.     s_addr.sin_family = AF_INET;
  78.     s_addr.sin_port = htons(port);
  79.     if(argv[1])
  80.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  81.     else
  82.         s_addr.sin_addr.s_addr = INADDR_ANY;
  83.     /*把地址和端口帮定到套接字上*/
  84.     if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
  85.         perror("bind");
  86.         exit(errno);
  87.     }else
  88.         printf("bind success!\n");
  89.     /*侦听本地端口*/
  90.     if(listen(sockfd,listnum) == -1){
  91.         perror("listen");
  92.         exit(errno);    
  93.     }else
  94.         printf("the server is listening!\n");
  95.     /*处理客户端的连接*/
  96.     handle_connect(sockfd);
  97.     /*关闭服务器的套接字*/
  98.     close(sockfd);
  99.     return 0;
  100. }
(4)单客户单线程,统一accept
在一个主程序中,接收客户端的连接,当客户端连接到来的时候,使用pthread_create()函数来建立一个线程来处理客户端的请求。

相应的代码为:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <errno.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <netinet/in.h>
  8. #include <sys/types.h>
  9. #include <unistd.h>
  10. #include <time.h>
  11. #include <pthread.h>

  12. #define BUFLEN 1024

  13. /*******************并发服务器模型之四:单客户端单线程,统一accept**********************/

  14. static void *handle_request(void *argv){
  15.     int newfd = *((int *)argv);    
  16.     char buf[BUFLEN];
  17.     int len;
  18.     time_t now;
  19.     /******处理客户端请求*******/
  20.     bzero(buf,BUFLEN);
  21.     len = recv(newfd,buf,BUFLEN,0);
  22.     if(len >&& !strncmp(buf,"TIME",4)){
  23.         bzero(buf,BUFLEN);
  24.         /*获取系统当前时间*/
  25.         now = time(NULL);
  26.         /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
  27.         sprintf(buf,"%24s\r\n",ctime(&now));
  28.         //******发送系统时间*******/
  29.         send(newfd,buf,strlen(buf),0);
  30.     }
  31.     /*关闭通讯的套接字*/
  32.     close(newfd);
  33.     return NULL;
  34. }

  35. static void handle_connect(int sockfd){
  36.     int newfd;
  37.     struct sockaddr_in c_addr;
  38.     socklen_t len;
  39.     pthread_t thread_s;

  40.     while(1){
  41.         len = sizeof(struct sockaddr);
  42.         if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) >0){
  43.             printf("\n*****************通信开始***************\n");
  44.             printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
  45.             /*创建线程来处理连接*/
  46.             pthread_create(&thread_s,NULL,handle_request,(void *)&newfd);            
  47.         }
  48.     }
  49. }

  50. int main(int argc, char **argv)
  51. {
  52.     int sockfd;
  53.     struct sockaddr_in s_addr;
  54.     unsigned int port, listnum;
  55.     
  56.     /*建立socket*/
  57.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  58.         perror("socket");
  59.         exit(errno);
  60.     }else
  61.         printf("socket create success!\n");
  62.     /*设置服务器端口*/    
  63.     if(argv[2])
  64.         port = atoi(argv[2]);
  65.     else
  66.         port = 4567;
  67.     /*设置侦听队列长度*/
  68.     if(argv[3])
  69.         listnum = atoi(argv[3]);
  70.     else
  71.         listnum = 3;
  72.     /*设置服务器ip*/
  73.     bzero(&s_addr, sizeof(s_addr));
  74.     s_addr.sin_family = AF_INET;
  75.     s_addr.sin_port = htons(port);
  76.     if(argv[1])
  77.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
  78.     else
  79.         s_addr.sin_addr.s_addr = INADDR_ANY;
  80.     /*把地址和端口帮定到套接字上*/
  81.     if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
  82.         perror("bind");
  83.         exit(errno);
  84.     }else
  85.         printf("bind success!\n");
  86.     /*侦听本地端口*/
  87.     if(listen(sockfd,listnum) == -1){
  88.         perror("listen");
  89.         exit(errno);    
  90.     }else
  91.         printf("the server is listening!\n");
  92.     /*处理客户端的连接*/
  93.     handle_connect(sockfd);
  94.     /*关闭服务器的套接字*/
  95.     close(sockfd);
  96.     return 0;
  97. }
  98. (5)IO多路复用的方法
  99. 具体的简介:前面的服务器模型主要集中在并发服务器上,并发服务器有个比较大的缺陷,它需要建立多个并行的处理单元。当客户端增加时,随着处理单元的增加,系统的负载会逐渐转移到并行单元的现场切换上。因此有一个比较新型的IO复用循环服务器。该模型在系统开始时,建立多个不同工作类型的处理单元,当客户端的请求到来时,将客户端的连接放到一个状态池中,对所有客户端的连接状态在一个处理单元中进行轮询处理。

  100. 相应的代码为如下:
    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. #include <errno.h>
    5. #include <sys/socket.h>
    6. #include <arpa/inet.h>
    7. #include <netinet/in.h>
    8. #include <sys/types.h>
    9. #include <unistd.h>
    10. #include <time.h>
    11. #include <pthread.h>

    12. #define BUFLEN 1024
    13. #define THREADNUM 2
    14. #define CLIENTNUM 1024

    15. int connect_host[CLIENTNUM];
    16. int connect_num = 0;
    17. /*******************并发服务器模型之五:IO复用循环服务器**********************/

    18. static void *handle_request(void *argv){
    19.     char buf[BUFLEN];
    20.     int len;
    21.     time_t now;
    22.     int maxfd = -1;
    23.     fd_set rfds;
    24.     struct timeval tv;
    25.     tv.tv_sec = 1;
    26.     tv.tv_usec = 0;
    27.     int i =0;
    28.     int err = -1;

    29.     while(1){
    30.         FD_ZERO(&rfds);
    31.         for(= 0; i < CLIENTNUM; i++){
    32.             if(connect_host[i] != -1){
    33.                 FD_SET(connect_host[i],&rfds);
    34.                 if(maxfd < connect_host[i])
    35.                     maxfd = connect_host[i];
    36.             }    
    37.         }
    38.         err = select(maxfd+1, &rfds, NULL, NULL, &tv);
    39.         switch(err){
    40.             case 0: break;
    41.             case -1: break;
    42.             default:
    43.                 if (connect_num < 0)
    44.                     break;
    45.                 for(= 0; i < CLIENTNUM; i++){
    46.                     if(connect_host[i] != -1){
    47.                         if(FD_ISSET(connect_host[i],&rfds)){
    48.                             /******处理客户端请求*******/
    49.                             bzero(buf,BUFLEN);
    50.                             len = recv(connect_host[i],buf,BUFLEN,0);
    51.                             if(len >&& !strncmp(buf,"TIME",4)){
    52.                                 bzero(buf,BUFLEN);
    53.                                 /*获取系统当前时间*/
    54.                                 now = time(NULL);
    55.                                 /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/
    56.                                 sprintf(buf,"%24s\r\n",ctime(&now));
    57.                                 //******发送系统时间*******/
    58.                                 send(connect_host[i],buf,strlen(buf),0);
    59.                             }
    60.                             /*关闭通讯的套接字*/
    61.                             close(connect_host[i]);
    62.                             /*更新文件描述符在数组中的值*/
    63.                             connect_host[i] = -1;
    64.                             connect_num--;    
    65.                         }
    66.                     }            
    67.                 }
    68.                 break;
    69.         }
    70.     }
    71.     return NULL;
    72. }

    73. static void *handle_connect(void *arg){
    74.     int sockfd = *((int *)arg);
    75.     int newfd;
    76.     struct sockaddr_in c_addr;
    77.     socklen_t len;
    78.     int i;

    79.     while(1){
    80.         len = sizeof(struct sockaddr);
    81.         if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) >0){
    82.             printf("\n*****************通信开始***************\n");
    83.             printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));
    84.             for(= 0; i < CLIENTNUM; i++){
    85.                 if(connect_host[i] == -1){
    86.                     connect_host[i] = newfd;
    87.                     /*客户端计数器*/
    88.                     connect_num++;
    89.                     /*继续等待新的客户端*/
    90.                     break;            
    91.                 }
    92.             }
    93.         }
    94.     }
    95.     return NULL;
    96. }

    97. int main(int argc, char **argv)
    98. {
    99.     int sockfd;
    100.     struct sockaddr_in s_addr;
    101.     unsigned int port, listnum;
    102.     pthread_t thread_s[2];
    103.     
    104.     /**/    
    105.     memset(connect_host,-1,CLIENTNUM);
    106.     /*建立socket*/
    107.     if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
    108.         perror("socket");
    109.         exit(errno);
    110.     }else
    111.         printf("socket create success!\n");
    112.     /*设置服务器端口*/    
    113.     if(argv[2])
    114.         port = atoi(argv[2]);
    115.     else
    116.         port = 4567;
    117.     /*设置侦听队列长度*/
    118.     if(argv[3])
    119.         listnum = atoi(argv[3]);
    120.     else
    121.         listnum = 3;
    122.     /*设置服务器ip*/
    123.     bzero(&s_addr, sizeof(s_addr));
    124.     s_addr.sin_family = AF_INET;
    125.     s_addr.sin_port = htons(port);
    126.     if(argv[1])
    127.         s_addr.sin_addr.s_addr = inet_addr(argv[1]);
    128.     else
    129.         s_addr.sin_addr.s_addr = INADDR_ANY;
    130.     /*把地址和端口帮定到套接字上*/
    131.     if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){
    132.         perror("bind");
    133.         exit(errno);
    134.     }else
    135.         printf("bind success!\n");
    136.     /*侦听本地端口*/
    137.     if(listen(sockfd,listnum) == -1){
    138.         perror("listen");
    139.         exit(errno);    
    140.     }else
    141.         printf("the server is listening!\n");
    142.     /*创建线程处理客户端的连接*/
    143.     pthread_create(&thread_s[0],NULL,handle_connect,(void *)&sockfd);
    144.     /*创建线程处理客户端的请求*/
    145.     pthread_create(&thread_s[1],NULL,handle_request, NULL);
    146.     /*等待线程结束*/
    147.     int i;
    148.     for(= 0; i < THREADNUM; i++){
    149.         pthread_join(thread_s[i],NULL);
    150.     }
    151.     /*关闭服务器的套接字*/
    152.     close(sockfd);
    153.     return 0;
    154. }

还有其他的一些方法:如线程池(其实和第二种方法是类似的)之类的处理方法也是可以实现并发服务器的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值