zoukankan      html  css  js  c++  java
  • C语言 HTTP 下载文件

    C语言 HTTP协议下载文件,实现断点续传,socket通讯,目前只支持ip和port方式连接,有兴趣的读者可完善域名方式。
          代码分为 http.c: 实现http协议下载文件 ,socket.c: 封装linux socket函数,移植时只需修改socket.c中的函数即可。
         希望对大家有帮助,本人亲测可用!
    http.c

    点击(此处)折叠或打开

    1. //http.c
    2. //作者:王振
    3. #include <stdio.h>
    4. #include <stdlib.h>
    5. #include <errno.h>
    6. #include <string.h>
    7. #include <sys/types.h>
    8. #include <sys/stat.h>
    9. #include <fcntl.h>
    10. #include "socket.h"
    11. #include "http.h"
    12.  
    13. #define MAX_RECV_SIZE    1440//硬件单包最大的接收字节数
    14. char g_host[URL_LEN];
    15. char g_ip[URL_LEN+1];//ip/域名
    16. char g_port[5+1];
    17. char g_buf_send[4*1024];//发送数据暂存区
    18. char g_buf_recv[10*1024];//接收数据暂存区
    19. BreakPoint_ST g_break_point;
    20. /*
    21. 功能:判断断点有效性,现在校验url是否一致
    22. 参数:
    23. 返回:
    24. >0---------有效,已下载文件大小
    25. -1----------无效
    26. */
    27. int Get_Breakpoint_Available(BreakPoint_ST *breakpoint,char *url,char *file_crc)
    28. {
    29.     
    30.     //判断断点是否有效,后续加入文件校验码
    31.     if((memcmp(breakpoint->url,url,strlen(url))== 0)&&(breakpoint->recv_size== MAX_RECV_SIZE))
    32.         return breakpoint->download_size;
    33.     else
    34.     {
    35.         return -1;
    36.     }
    37.     
    38. }
    39. /*
    40. 功能:判断要下载文件是否存在断点
    41. 参数:
    42. filename---要下载的文件名
    43. file_crc----服务器返回下载文件的校验码
    44. 返回:
    45. 0---------无断点
    46. >0--------有断点,已下载文件大小
    47. */
    48. int Get_Breakpoint(char *url,char *filename,char *file_crc)
    49. {
    50.     char filename_bp[64];
    51.     int fd = -1;
    52.     int ret;
    53.     BreakPoint_ST break_point;
    54.     
    55.     //断点文件名 filename+bp
    56.     sprintf(filename_bp,"%s.bp",filename);
    57.     //检测是否存在filename断点文件
    58.     fd = open(filename_bp,O_RDONLY,S_IRUSR|S_IWUSR);
    59.     if(fd == -1)
    60.     {    
    61.         #ifdef DEBUG_HTTP
    62.         printf("no exsit %s ",filename_bp);
    63.         #endif
    64.         return 0;
    65.     }
    66.     //存在断点
    67.     ret = read(fd,&break_point,sizeof(break_point));
    68.     if(ret != sizeof(break_point))
    69.     {
    70.         perror("ERR:Get_Breakpoint read");
    71.         exit(-1);
    72.     }
    73.     close(fd);
    74.     //判断断点是否有效
    75.     ret = Get_Breakpoint_Available(&break_point,url,file_crc);
    76.     if(ret > 0)
    77.         return ret;
    78.     else
    79.     {
    80.         
    81.         printf("%s not available ",filename_bp);
    82.         remove(filename);
    83.         remove(filename_bp);
    84.         return 0;
    85.         
    86.     }
    87. }
    88. /*
    89. 功能:保存断点信息,文件名filename.bp
    90. 参数:
    91. filename---要下载的文件名
    92. file_crc----服务器返回下载文件的校验码
    93. 返回:
    94. 0---------成功
    95. >0--------有断点,已下载文件大小
    96. */
    97. int Save_Breakpoint(char *url,char *filename,int download_size,char *file_crc)
    98. {
    99.     int fd;
    100.     BreakPoint_ST breakpoint;
    101.     char filename_bp[128];//断点信息文件名,包含路径
    102.     sprintf(filename_bp,"%s.bp",filename);
    103.     /* 创建目的文件 */
    104.     if((fd=open(filename_bp,O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
    105.     {
    106.         fprintf(stderr,"Open %s Error:%s ",filename_bp,strerror(errno));
    107.         exit(1);
    108.     }
    109.     memset(&breakpoint,0x0,sizeof(breakpoint));
    110.     strcpy(breakpoint.url,url);
    111.     //strcpy(breakpoint.crc,file_crc);
    112.     strcpy(breakpoint.filename,filename);
    113.     breakpoint.download_size = download_size;
    114.     breakpoint.recv_size= MAX_RECV_SIZE;
    115.     
    116.     //xu tioa zheng wei fen ci xie ru
    117.     if(write(fd,&breakpoint,sizeof(breakpoint)) != sizeof(breakpoint))
    118.     {
    119.         perror("ERR:Save_Breakpoint");
    120.         exit(1);
    121.     }
    122.     close(fd);
    123.     return 0;
    124. }
    125. /*
    126. 功能:保存文件,追加写
    127. 参数:
    128. 返回:
    129. 0---------成功
    130. */
    131. int Save_File(char *filebuf,int filelength,char *filename)
    132. {
    133.     int fd;
    134.     /* 创建目的文件追加写 */
    135.     if((fd=open(filename,O_WRONLY|O_CREAT|O_APPEND,S_IRUSR|S_IWUSR))==-1)
    136.     {
    137.         fprintf(stderr,"Open %s Error:%s ",filename,strerror(errno));
    138.         exit(1);
    139.     }
    140.     //xu tioa zheng wei fen ci xie ru
    141.     if(write(fd,filebuf,filelength) != filelength)
    142.     {
    143.         perror("ERR:Save_File");
    144.         exit(1);
    145.     }
    146.     close(fd);
    147.     return 0;
    148. }
    149.  
    150. int HTTP_GetResponseCode(void)
    151. {
    152.  
    153.  
    154. }
    155.  /*
    156. 功能:读取http返回的协议实体主体长度
    157. 参数:
    158. revbuf--------接收到的返回值
    159. 返回值:
    160. >=0---------内容(实体主体)的长度
    161. -1-----------数据返回错误
    162. */
    163. int HTTP_GetRecvLength(char *revbuf)
    164. {
    165.     char *p1 = NULL;
    166.     int HTTP_Body = 0;//内容体长度
    167.     int HTTP_Head = 0;//HTTP 协议头长度
    168.     HTTP_Body = HTTP_GetContentLength(revbuf);
    169.     if(HTTP_Body == -1)
    170.         return -1;
    171.     p1=strstr(revbuf," ");
    172.     if(p1==NULL)
    173.         return -1;
    174.     else
    175.     {
    176.         HTTP_Head = p1- revbuf +4;// 4是 的长度
    177.         return HTTP_Body+HTTP_Head;
    178.     }
    179. }
    180. /*
    181. 功能:读取http返回的Content-Length长度
    182. 参数:
    183. revbuf--------接收到的数据
    184. 返回值:
    185. >=0---------Content-Length长度
    186. -1-----------数据返回错误
    187. */
    188. int HTTP_GetContentLength(char *revbuf)
    189. {
    190.     char *p1 = NULL, *p2 = NULL;
    191.     int HTTP_Body = 0;//内容体长度
    192.     p1 = strstr(revbuf,"Content-Length");
    193.     if(p1 == NULL)
    194.         return -1;
    195.     else
    196.     {
    197.         p2 = p1+strlen("Content-Length")+ 2;
    198.         HTTP_Body = atoi(p2);
    199.         return HTTP_Body;
    200.     }
    201. }
    202.  /*
    203.  功能:
    204.  参数:
    205.  sockfd--------接收到的返回值
    206.  返回值:
    207.  >0---------接收到长度
    208.  -1----------失败
    209.  =0---------服务端断开连接
    210.  注:内部接收缓冲10k
    211.  */
    212. int HTTP_Recv(int sockfd,char *buf_recv)
    213. {
    214.     int ret;
    215.     int recvlen=0;
    216.     int downloadlen = 0;
    217.     //int contentlen=0;
    218.     char buf_recv_tmp[10*1024+1];
    219.     
    220.     memset(buf_recv_tmp,0x0,sizeof(buf_recv_tmp));
    221.     while(1)
    222.     {
    223.         ret = Recv(sockfd,buf_recv_tmp+recvlen,sizeof(buf_recv_tmp)-1,0);
    224.         if(ret <= 0)//下载失败
    225.         {
    226.             perror("ERR:recv fail");
    227.             return ret;
    228.         }
    229.     
    230.     
    231.         if(recvlen == 0)
    232.         {
    233.             #ifdef DEBUG_HTTP_RECV
    234.             printf("recv len = %d ", ret);
    235.              printf("recv = %s ", buf_recv_tmp);
    236.             #endif
    237.             //获取需要下载长度;
    238.             downloadlen = HTTP_GetRecvLength(buf_recv_tmp);
    239.             #ifdef DEBUG_HTTP_RECV
    240.             printf("downloadlen = %d ",downloadlen);
    241.             #endif
    242.         }
    243.         recvlen += ret;
    244.         #ifdef DEBUG_HTTP_RECV
    245.         printf("total recvlen = %d ",recvlen);
    246.         #endif
    247.         if(downloadlen == recvlen)//下载完成
    248.             break;
    249.     }
    250.     memcpy(buf_recv,buf_recv_tmp,downloadlen);
    251.     return recvlen;
    252. }
    253. /*
    254. 功能:获取下载url中的文件名,最后一个/后的字符
    255. 参数:
    256. 返回值:
    257. 0-----------成功
    258. -1----------失败
    259. :内部接收缓冲10k
    260. */
    261. int HTTP_GetFileName(char *url,char *filename)
    262. {
    263.     //提取url中最后一个/后的内容
    264.     int len;
    265.     int i;
    266.     len = strlen(url);
    267.     for(i=len-1;i>0;i--)
    268.     {
    269.         if(url[i] == '/')
    270.             break;
    271.     }
    272.     if(i == 0)//下载地址错误
    273.     {
    274.         printf("url not contain '/' ");
    275.         return -1;
    276.     }
    277.     else
    278.     {
    279.     
    280.         strcpy(filename,url+i+1);
    281.         #ifdef DEBUG_HTTP
    282.         printf("filename=%s ",filename);
    283.         #endif
    284.         return 0;
    285.     }
    286. }
    287. /*
    288. 功能:获取下载url中的路径,第一个/后的字符
    289. 参数:
    290. 返回值:
    291. 0-----------成功
    292. -1----------失败
    293. :url ex "http://host:port/path"
    294. */
    295. int HTTP_GetPath(char *url,char *path)
    296. {
    297.     char *p;
    298.     p = strstr(url,"http://");
    299.     if(p == NULL)
    300.     {
    301.         p = strchr(url,'/');
    302.         if(p == NULL)
    303.             return -1;
    304.         else
    305.         {
    306.             strcpy(path,p);
    307.             return 0;
    308.         }
    309.     }
    310.     else
    311.     {
    312.         p = strchr(url+strlen("http://"),'/');
    313.         if(p == NULL)
    314.             return -1;
    315.         else
    316.         {
    317.             strcpy(path,p);
    318.             return 0;
    319.         }
    320.     }
    321. }
    322. /*
    323. 功能:获取下载url中的ip和port,ip支持域名,端口默认为80
    324. 参数:
    325. 返回值:
    326. 1-----------域名式
    327. 2-----------ip port式
    328. -1----------失败
    329. :url ex "http://host:port/path"
    330. */
    331. int HTTP_Get_IP_PORT(char *url,char *ip,char *port)
    332. {
    333.     char *p = NULL;
    334.     int offset = 0;
    335.     char DOMAIN_NAME[128];
    336.     p = strstr(url,"http://");
    337.     if(p == NULL)
    338.     {
    339.         offset = 0;
    340.     }
    341.     else
    342.     {
    343.         offset = strlen("http://");
    344.     }
    345.     p = strchr(url+offset,'/');
    346.     if(p == NULL)
    347.     {
    348.         printf("url:%s format error ",url);
    349.         return -1;
    350.         
    351.     }
    352.     else
    353.     {
    354.         memset(DOMAIN_NAME,0x0,sizeof(DOMAIN_NAME));
    355.         memcpy(DOMAIN_NAME,url+offset,(p-url-offset));
    356.         p = strchr(DOMAIN_NAME,':');
    357.         if(p == NULL)
    358.         {
    359.             strcpy(ip,DOMAIN_NAME);
    360.             strcpy(port,"80");
    361.             //printf("ip %p,port %p ",ip,port);
    362.             
    363.             #ifdef DEBUG_HTTP
    364.             printf("ip=%s,port=%s ",ip,port);//debug info
    365.             #endif
    366.             return 1;
    367.         }
    368.         else
    369.         {    
    370.             *p = '';
    371.             strcpy(ip,DOMAIN_NAME);
    372.             strcpy(port,p+1);
    373.             
    374.             #ifdef DEBUG_HTTP
    375.             printf("ip=%s,port=%s ",ip,port);//debug info
    376.             #endif
    377.             return 2;
    378.         }
    379.         return 0;
    380.     }
    381.     
    382. }
    383. void Package_Url_Get_File(char *path, char *range)
    384. {
    385.     char buf[64];
    386.     memset(g_buf_send,0x0,sizeof(g_buf_send));        
    387.     sprintf(g_buf_send, "GET %s",path);
    388.     
    389.     //HTTP/1.1 前面需要一个空格
    390.     strcat(g_buf_send," HTTP/1.1 ");
    391.     strcat(g_buf_send, "Host: ");
    392.     strcat(g_buf_send, g_host);
    393.     //strcat(g_buf_send, ":");
    394.     //strcat(g_buf_send, PORT);
    395.     
    396.     sprintf(buf, " Range: bytes=%s",range);
    397.     strcat(g_buf_send,buf);
    398.     strcat(g_buf_send, " Keep-Alive: 200");
    399.     strcat(g_buf_send," Connection: Keep-Alive ");
    400.     
    401. }
    402. int Package_Url_Get_FileSize(char *url)
    403. {
    404.     
    405.     memset(g_buf_send,0x0,sizeof(g_buf_send));        
    406.     sprintf(g_buf_send, "HEAD %s",url);
    407.         //HTTP/1.1 前面需要一个空格
    408.     strcat(g_buf_send," HTTP/1.1 ");
    409.     strcat(g_buf_send, "Host: ");
    410.     strcat(g_buf_send, g_host);
    411.     //strcat(g_buf_send, ":");
    412.     //strcat(g_buf_send, PORT);
    413.     strcat(g_buf_send," Connection: Keep-Alive ");
    414.     return 0;
    415. }
    416. int HTTP_GetFileSize(int sockfd,char *path)
    417. {
    418.     int ret = -1;
    419.     char buf_recv_tmp[10*1024+1];
    420.     Package_Url_Get_FileSize(path);
    421. #ifdef DEBUG_HTTP
    422.     printf("send = %s ",g_buf_send);
    423. #endif
    424.     Send(sockfd, g_buf_send, strlen(g_buf_send), 0);
    425.     memset(buf_recv_tmp,0x0,sizeof(buf_recv_tmp));                                                
    426.     ret = Recv(sockfd,buf_recv_tmp,sizeof(buf_recv_tmp)-1,0);
    427. #ifdef DEBUG_HTTP
    428.     printf("recv len = %d ", ret);
    429.     printf("recv = %s ", buf_recv_tmp);
    430. #endif
    431.     if(ret <= 0)
    432.     {
    433.         perror("ERR:recv fail GetFileSize()");
    434.         return -1;
    435.     }
    436.     ret = HTTP_GetContentLength(buf_recv_tmp);
    437.     if(ret <= 0)
    438.         return -1;
    439.     else
    440.         return ret;
    441. }
    442. /*
    443. 功能:分段下载文件
    444. 参数:
    445. 返回值:
    446. >0----------已下载文件大小(不包含上次下载)
    447. -1----------失败
    448. */
    449. int HTTP_GetFile(int sockfd,char *path,int filelength,int download_size,char *filebuf)
    450. {
    451.     int count;
    452.     char range[32];
    453.     int i;
    454.     int j = 0;//成功下载次数
    455.     int ret = -1;
    456.     char *p = NULL;
    457.     int download_index;//下载开始索引
    458.     count = (filelength%MAX_RECV_SIZE)?(filelength/MAX_RECV_SIZE +1):(filelength/MAX_RECV_SIZE);
    459.     download_index = download_size/MAX_RECV_SIZE;
    460.     for(i=download_index;i<count;i++)
    461.     {
    462.         //if(i == 20)//测试断点
    463.             //break;
    464.         if((i == (count-1))&&(filelength%MAX_RECV_SIZE))
    465.             sprintf(range,"%d-%d",i*MAX_RECV_SIZE,filelength-1);
    466.         else
    467.             sprintf(range,"%d-%d",i*MAX_RECV_SIZE,(i+1)*MAX_RECV_SIZE-1);
    468.         Package_Url_Get_File(path,range);
    469.         #ifdef DEBUG_HTTP
    470.          printf("send = %s ",g_buf_send);
    471.         #endif
    472.          Send(sockfd, g_buf_send, strlen(g_buf_send), 0);
    473.         /*需改为提取http 返回协议头和协议体总长,然后定长接收*/
    474.         memset(g_buf_recv,0x0,sizeof(g_buf_recv));                                             
    475.         ret = HTTP_Recv(sockfd,g_buf_recv);
    476.         if(ret < 0)
    477.             break;
    478.         if(ret == 0 )//服务端断开连接
    479.         {
    480.             sockfd = Socket_Connect(g_ip,g_port);
    481.              i--;
    482.             continue;
    483.         }
    484.         /*提取协议体数据,保存在filebuf中*/
    485.         p = strstr(g_buf_recv," ");
    486.         if(p == NULL)//jia ru duan dian baocun
    487.         {
    488.             printf("ERR:g_buf_recv not contain end flag ");
    489.             break;
    490.         }
    491.          else
    492.          {
    493.              memcpy(filebuf+j*MAX_RECV_SIZE,p+4,MAX_RECV_SIZE);
    494.              j++;
    495.          }
    496.     }
    497.     if(i == count)
    498.         return (filelength-download_size);
    499.     else
    500.         return (i*MAX_RECV_SIZE-download_size);
    501. }
    502. /*
    503. 功能:HTTP下载文件
    504. 参数:
    505. 返回值:
    506. 0----------下载完成
    507. -1---------失败
    508. -2---------部分下载完成
    509. :保存文件到bin所在目录
    510. */
    511. int HTTP_DownloadFile(char *url,char *save_path)
    512. {
    513.     int ret;
    514.     int sockfd;
    515.     int filesize;
    516.     int download_size;
    517.     char filename[FILENAME_LEN+1];
    518.     char filename_bp[FILENAME_LEN+3+1];
    519.     char *filebuf;
    520.     char save_file_path[FILENAME_LEN+1];//保存下载文件的路径+文件名
    521.     char path[PATH_LEN+1];//url中的path
    522.     //提取ip和port或url(url 暂不实现,需要gethostbyname linux)
    523.     ret = HTTP_Get_IP_PORT(url,g_ip,g_port);
    524.     if(ret == -1)
    525.         return -1;
    526.     else
    527.     {
    528.         sprintf(g_host,"%s:%s",g_ip,g_port);
    529.     }
    530.     //提取下载文件名
    531.     ret = HTTP_GetFileName(url,filename);
    532.     if(ret == -1)
    533.         return -1;
    534.     ret = HTTP_GetPath(url,path);
    535.     if(ret == -1)
    536.         return -1;
    537.     //sleep(3);//debug info
    538.     //建立连接
    539.     sockfd = Socket_Connect(g_ip,g_port);
    540.     //获取下载文件总大小
    541.     filesize = HTTP_GetFileSize(sockfd,path);
    542.     if(filesize == -1)
    543.         return -1;
    544.     //#ifdef DEBUG_HTTP
    545.     printf("http need download size %d ",filesize);
    546.     //#endif
    547.     //malloc分配存储文件空间
    548.     filebuf = (char *)malloc(filesize);
    549.     if(filebuf == NULL)
    550.     {
    551.         perror("malloc filebuf fail");
    552.         return -1;
    553.     }
    554.     else
    555.         memset(filebuf,0x0,filesize);
    556.     download_size = Get_Breakpoint(url,filename,NULL);
    557.     #ifdef DEBUG_HTTP
    558.     printf("breakpoint download_size=%d ",download_size);//debug info
    559.     sleep(3);//debug info
    560.     #endif
    561.     //分段下载文件
    562.     ret = HTTP_GetFile(sockfd,path,filesize,download_size,filebuf);
    563.     Close(sockfd);
    564.     if(ret < 0)
    565.     {
    566.         free(filebuf);
    567.         return -1;
    568.     }
    569.     else
    570.     {
    571.         sprintf(save_file_path,"%s%s",save_path,filename);
    572.         
    573.         #ifdef DEBUG_HTTP
    574.         printf("save_path=%s ",save_path);
    575.         printf("filename=%s ",filename);
    576.         printf("save_file_path=%s ",save_file_path);
    577.         printf("download_size = %d ",ret);
    578.         #endif
    579.         Save_File(filebuf,ret,save_file_path);
    580.         free(filebuf);
    581.         if((ret+download_size) == filesize)//全部下载完成
    582.         {
    583.             sprintf(filename_bp,"%s.bp",filename);
    584.             remove(filename_bp);
    585.             
    586.      printf("download success ");
    587.             return 0;
    588.         }
    589.         else//部分下载完成
    590.         {
    591.             printf("part download success ");
    592.             //保存断点信息
    593.             Save_Breakpoint(url,save_file_path,ret+download_size,NULL);
    594.             return -2;
    595.         }
    596.     }
    597. }

    socket.

    点击(此处)折叠或打开

    1. //socket.c
    2. //作者:王振
    3. #include <stdio.h>
    4. #include <stdlib.h>
    5. #include <errno.h>
    6. #include <string.h>
    7. #include <sys/types.h>
    8. #include <netinet/in.h>
    9. #include <sys/socket.h>
    10. #include <sys/wait.h>
    11. #include "socket.h"
    12. int Connect(int fd, struct sockaddr *addr, socklen_t len)
    13. {
    14.     int result;
    15.     if ((result = connect(fd, addr, len)) == -1) {
    16.         perror("connect");
    17.         exit(EXIT_FAILURE);
    18.     }
    19.     return 0;
    20. }
    21. int Socket_Connect(char *ip,char *port)
    22. {
    23.     struct sockaddr_in addr;
    24.     int sockfd;
    25.     int len;
    26.     
    27.     addr.sin_family = AF_INET;
    28.     addr.sin_addr.s_addr = inet_addr(ip);//127.0.0.1为本机ip
    29.     addr.sin_port = htons(atoi(port));
    30.     len = sizeof(addr);
    31.         
    32.     sockfd = socket(AF_INET, SOCK_STREAM, 0);
    33.     Connect(sockfd, (struct sockaddr*)&addr, len);
    34.     return sockfd;
    35. }
    36. /*
    37. 功能:向socketfd发送数据,内部实现了循环发送len长度
    38. 参数:
    39. sockfd 是代表你与远程程序连接的套接字描述符。
    40. msg 是一个指针,指向你想发送的信息的地址。
    41. len 是你想发送信息的长度。
    42. flags 发送标记。一般都设为0
    43. 返回:
    44. 0-------- 成功
    45. 退出---失败
    46. 修改:
    47. 备注:
    48. 王振
    49. */
    50. int Send(int sockfd, char *sendbuf, int len, int flags)
    51. {
    52.     int sendlen = 0;
    53.     int ret = -1;
    54.     while(sendlen < len)
    55.     {
    56.         ret = send(sockfd, sendbuf+sendlen, len-sendlen, flags);
    57.         if(-1 == ret)
    58.         {
    59.             perror("send");
    60.             exit(EXIT_FAILURE);
    61.         }
    62.         else
    63.             sendlen += ret;
    64.     }
    65.     return 0;
    66. }
    67. int Close(int sockfd)
    68. {
    69.     return close(sockfd);
    70.     
    71. }
    72. int Recv(int sockfd, char *recvbuf, int len, int flags)
    73. {
    74.     int recv_len;
    75.     if ((recv_len = recv(sockfd, recvbuf, len, flags)) < 0)
    76.     {
    77.         perror("recv error");
    78.         exit(EXIT_FAILURE);
    79.     }
    80.     return recv_len;
    81. }
  • 相关阅读:
    tricky c++ new(this)
    MCI使用
    LoadIcon的使用
    深入浅出Node.js (2)
    洛谷 P1464 Function
    洛谷 P1722 矩阵 II
    洛谷 P1036 选数
    洛谷 P1303 A*B Problem
    洛谷 P2694 接金币
    洛谷 P1679 神奇的四次方数
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/13513924.html
Copyright © 2011-2022 走看看