linux網絡編程:併發服務器的模型

這篇博客主要是參考了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. }

還有其他的一些方法:如線程池(其實和第二種方法是類似的)之類的處理方法也是可以實現併發服務器的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章