zoukankan      html  css  js  c++  java
  • 网站压力测试工具-Webbench源码笔记

    Ubuntu 下安装使用

    1、安装依赖包CTAGS

    sudo apt-get install ctage

    2、下载及安装 Webbench

    http://home.tiscali.cz/~cz210552/webbench.html

    解压:

    tar -zxvf webbench-1.5.tar.gz

    切换到解压后的目录:

    cd webbench-1.5

    编译:

    make

    安装:

    sudo make install

    webbench使用

    #webbench -? (查看命令帮助)

    常用参数 说明,-c 表示客户端数,-t 表示时间

    ./webbench -c 500 -t 30 http://xyzp.xaut.edu.cn/Plugins/YongHu_plug/Pages/loginbysjy.aspx

    代码学习:

    众所周知,C程序的主函数有两个参数,其中第一个参数是整数,可以获得包括程序名字的参数个数,第二个参数是字符数组或字符指针的指针,可以按顺序获得命令行上各个字符串的参数。其原型是:

    int main(int argc, char const *argv[])

    或者

    int main(int argc, char const **argv)

    有鉴于此,在Unix和Linux的正式项目上,程序员通常会使用getopt()或者getopt_long()来获得输入的参数。两者的区别在于getopt()仅支持短格式参数,而getopt_long()既支持短格式参数,也支持长格式参数。

    ./webbench -V

    1.5

    ./webbench --version

    1.5

    关于getopt_long()的具体用法参考:man getopt_long

    在处理命令行参数时,用到一个变量 optind, 原来是系统定义的。

    可以在命令行中,通过 man optind 来看相关信息

    optind: the index of the next element to be processed in the argv.  The system initializes it to 1. The caller can reset it to 1 to restart scanning of the same argv or scanning a new argument vector.

    当调用 getopt() , getopt_long() 之类的函数时, optind 的值会变化。如:

    执行 $  ./a.out -ab         当调用  一次  getopt() , 则 optind 值会 +1

    进程间通信的方式之管道:管道分为无名管道(匿名管道)和命名管道

    使用无名管道,则是通信的进程之间需要一个父子关系,通信的两个进程一定是由一个共同的祖先进程启动。但是无名管道没有数据交叉的问题。

    使用命名管道可以解决无名管道中出现的通信的两个进程一定是由通一个共同的祖先进程启动的问题,但是为了数据的安全,很多时候要采用阻塞的FIFO,让写操作变成原子操作。

    只有两个源文件:webbench.c, socket.c。
     1 /* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $
     2  *
     3  * This module has been modified by Radim Kolar for OS/2 emx
     4  */
     5 
     6 /***********************************************************************
     7   module:       socket.c
     8   program:      popclient
     9   SCCS ID:      @(#)socket.c    1.5  4/1/94
    10   programmer:   Virginia Tech Computing Center
    11   compiler:     DEC RISC C compiler (Ultrix 4.1)
    12   environment:  DEC Ultrix 4.3 
    13   description:  UNIX sockets code.
    14  ***********************************************************************/
    15  
    16 #include <sys/types.h>
    17 #include <sys/socket.h>
    18 #include <fcntl.h>
    19 #include <netinet/in.h>
    20 #include <arpa/inet.h>
    21 #include <netdb.h>
    22 #include <sys/time.h>
    23 #include <string.h>
    24 #include <unistd.h>
    25 #include <stdio.h>
    26 #include <stdlib.h>
    27 #include <stdarg.h>
    28 
    29 
    30 /*
    31 根据通信地址和端口号建立网络连接
    32 @host:网络地址
    33 @clientPort:端口号
    34 成功返回建立连接套接字
    35 建立套接字失败返回-1
    36 */
    37 int Socket(const char *host, int clientPort)
    38 {
    39     int sock;
    40     unsigned long inaddr;
    41     struct sockaddr_in ad;
    42     struct hostent *hp;
    43     
    44     memset(&ad, 0, sizeof(ad));
    45     ad.sin_family = AF_INET;
    46     //将点分十进制的IP地址转化为无符号的长整形
    47     inaddr = inet_addr(host);
    48     if (inaddr != INADDR_NONE)
    49         memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    50     else
    51     {
    52         //如果host是域名,则通过域名获取IP地址
    53         hp = gethostbyname(host);
    54         if (hp == NULL)
    55             return -1;
    56         memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    57     }
    58     ad.sin_port = htons(clientPort);
    59     
    60     sock = socket(AF_INET, SOCK_STREAM, 0);
    61     if (sock < 0)
    62         return sock;
    63     if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
    64         return -1;
    65     return sock;
    66 }
    socket.c
     
      1 /*
      2  * (C) Radim Kolar 1997-2004
      3  * This is free software, see GNU Public License version 2 for
      4  * details.
      5  *
      6  * Simple forking WWW Server benchmark:
      7  *
      8  * Usage:
      9  *   webbench --help
     10  *
     11  * Return codes:
     12  *    0 - sucess
     13  *    1 - benchmark failed (server is not on-line)
     14  *    2 - bad param
     15  *    3 - internal error, fork failed
     16  * 
     17  */ 
     18 #include "socket.c"
     19 #include <unistd.h>
     20 #include <sys/param.h>
     21 #include <rpc/types.h>
     22 #include <getopt.h>
     23 #include <strings.h>
     24 #include <time.h>
     25 #include <signal.h>
     26 
     27 /* values */
     28 volatile int timerexpired=0;
     29 int speed=0;
     30 int failed=0;
     31 int bytes=0;
     32 /* globals */
     33 int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
     34 /* Allow: GET, HEAD, OPTIONS, TRACE */
     35 #define METHOD_GET 0
     36 #define METHOD_HEAD 1
     37 #define METHOD_OPTIONS 2
     38 #define METHOD_TRACE 3
     39 #define PROGRAM_VERSION "1.5"
     40 int method=METHOD_GET;
     41 int clients=1;
     42 int force=0;
     43 int force_reload=0;
     44 int proxyport=80;
     45 char *proxyhost=NULL;
     46 int benchtime=30;
     47 /* internal */
     48 int mypipe[2];
     49 
     50 //主机名的最大长度通常是由头文件<sys/param.h>定义的常值MAXHOSTNAMELEN
     51 char host[MAXHOSTNAMELEN];
     52 #define REQUEST_SIZE 2048
     53 char request[REQUEST_SIZE];
     54 
     55 static const struct option long_options[]=
     56 {
     57     {"force",no_argument,&force,1},
     58     {"reload",no_argument,&force_reload,1},
     59     {"time",required_argument,NULL,'t'},
     60     {"help",no_argument,NULL,'?'},
     61     {"http09",no_argument,NULL,'9'},
     62     {"http10",no_argument,NULL,'1'},
     63     {"http11",no_argument,NULL,'2'},
     64     {"get",no_argument,&method,METHOD_GET},
     65     {"head",no_argument,&method,METHOD_HEAD},
     66     {"options",no_argument,&method,METHOD_OPTIONS},
     67     {"trace",no_argument,&method,METHOD_TRACE},
     68     {"version",no_argument,NULL,'V'},
     69     {"proxy",required_argument,NULL,'p'},
     70     {"clients",required_argument,NULL,'c'},
     71     {NULL,0,NULL,0}
     72 };
     73 
     74 /* prototypes */
     75 static void benchcore(const char* host,const int port, const char *request);
     76 static int bench(void);
     77 static void build_request(const char *url);
     78 
     79 static void alarm_handler(int signal)
     80 {
     81    timerexpired=1;
     82 }   
     83 
     84 static void usage(void)
     85 {
     86    fprintf(stderr,
     87     "webbench [option]... URL
    "
     88     "  -f|--force               Don't wait for reply from server.
    "
     89     "  -r|--reload              Send reload request - Pragma: no-cache.
    "
     90     "  -t|--time <sec>          Run benchmark for <sec> seconds. Default 30.
    "
     91     "  -p|--proxy <server:port> Use proxy server for request.
    "
     92     "  -c|--clients <n>         Run <n> HTTP clients at once. Default one.
    "
     93     "  -9|--http09              Use HTTP/0.9 style requests.
    "
     94     "  -1|--http10              Use HTTP/1.0 protocol.
    "
     95     "  -2|--http11              Use HTTP/1.1 protocol.
    "
     96     "  --get                    Use GET request method.
    "
     97     "  --head                   Use HEAD request method.
    "
     98     "  --options                Use OPTIONS request method.
    "
     99     "  --trace                  Use TRACE request method.
    "
    100     "  -?|-h|--help             This information.
    "
    101     "  -V|--version             Display program version.
    "
    102     );
    103 };
    104 
    105 
    106 
    107 int main(int argc, char *argv[])
    108 {
    109     int opt=0;
    110     int options_index=0;
    111     char *tmp=NULL;
    112     
    113     if(argc==1)
    114     {
    115       usage();
    116       return 2;
    117     } 
    118 
    119     //解析命令行参数
    120     while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
    121     {
    122         switch(opt)
    123         {
    124             case  0 : break;
    125             case 'f': force=1;break;
    126             case 'r': force_reload=1;break; 
    127             case '9': http10=0;break;
    128             case '1': http10=1;break;
    129             case '2': http10=2;break;
    130             case 'V': printf(PROGRAM_VERSION"
    ");exit(0);
    131             case 't': benchtime=atoi(optarg);break;       
    132             case 'p': 
    133                 /* proxy server parsing server:port */
    134                 tmp=strrchr(optarg,':');
    135                 proxyhost=optarg;
    136                 if(tmp==NULL)
    137                 {
    138                     break;
    139                 }
    140                 if(tmp==optarg)
    141                 {
    142                     fprintf(stderr,"Error in option --proxy %s: Missing hostname.
    ",optarg);
    143                     return 2;
    144                 }
    145                 if(tmp==optarg+strlen(optarg)-1)
    146                 {
    147                     fprintf(stderr,"Error in option --proxy %s Port number is missing.
    ",optarg);
    148                     return 2;
    149                 }
    150                 *tmp='';
    151                 proxyport=atoi(tmp+1);
    152                 break;
    153             case ':':
    154             case 'h':
    155             case '?': 
    156                 usage();
    157                 return 2;
    158                 break;
    159             case 'c': 
    160                 clients=atoi(optarg);
    161                 break;
    162         }
    163     }
    164     
    165     //判断命令行参数是否解析完成,如果完成代表没有需要访问的超链接
    166     //optind的值随着调用getopt_long次数改变,每调用一次加一
    167     if(optind==argc) 
    168     {
    169         fprintf(stderr,"webbench: Missing URL!
    ");
    170         usage();
    171         return 2;
    172     }
    173 
    174     if(clients==0)
    175     {
    176         clients=1;
    177     } 
    178     if(benchtime==0)
    179     {
    180         benchtime=60;    
    181     }
    182 
    183     /* Copyright */
    184     fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"
    "
    185         "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
    ");
    186     
    187     build_request(argv[optind]);
    188     
    189     //对设置参数的一些显示信息
    190     /* print bench info */
    191     printf("
     Bench marking: ");
    192 
    193     switch(method)
    194     {
    195         case METHOD_GET:
    196         default:
    197             printf("GET");
    198             break;
    199         case METHOD_OPTIONS:
    200             printf("OPTIONS");
    201             break;
    202         case METHOD_HEAD:
    203             printf("HEAD");
    204             break;
    205         case METHOD_TRACE:
    206             printf("TRACE");
    207             break;
    208     }
    209 
    210     printf(" %s",argv[optind]);
    211     switch(http10)
    212     {
    213         case 0: printf(" (using HTTP/0.9)");break;
    214         case 1: printf(" (using HTTP/1.0)");break;
    215         case 2: printf(" (using HTTP/1.1)");break;
    216     }
    217     printf("
    ");
    218     
    219     //显示一些参数
    220     if(clients==1)
    221     {
    222         printf("1 client");
    223     }
    224     else
    225     {
    226         printf("%d clients",clients);
    227     }
    228 
    229     printf(", running %d sec", benchtime);
    230     if(force)
    231     {
    232         printf(", early socket close");
    233     }
    234 
    235     if(proxyhost!=NULL) 
    236     {
    237         printf(", via proxy server %s:%d",proxyhost,proxyport);
    238     }
    239     
    240     if(force_reload) 
    241     {
    242         printf(", forcing reload");
    243     }
    244     printf(".
    ");
    245 
    246     return bench();
    247 }
    248 /*
    249  *输入URL的连接
    250  *解析URl,构造请求头并且将请求头,
    251  *保存到全局变量request中
    252     GET /Plugins/YongHu_plug/Pages/loginbysjy.aspx HTTP/1.0
    253     User-Agent: WebBench 1.5
    254     Host: xyzp.xaut.edu.cn
    255  */
    256 void build_request(const char *url)
    257 {
    258     char tmp[10];
    259     int i;
    260     //清空存储区
    261     bzero(host,MAXHOSTNAMELEN);
    262     bzero(request,REQUEST_SIZE);
    263     //force_reload不等待响应,强制刷新
    264     //proxyhost是否设置了代理
    265     //判断是否设置了Http的通信协议版本
    266     if(force_reload && proxyhost!=NULL && http10<1) 
    267     {
    268         http10=1;    
    269     }
    270     //请求方式和协议版本
    271     if(method==METHOD_HEAD && http10<1)
    272     {
    273         http10=1;
    274     }
    275     if(method==METHOD_OPTIONS && http10<2)
    276     {
    277         http10=2;  
    278     } 
    279     if(method==METHOD_TRACE && http10<2)
    280     {
    281         http10=2;  
    282     } 
    283 
    284     switch(method)
    285     {
    286         default:
    287         case METHOD_GET: strcpy(request,"GET");break;
    288         case METHOD_HEAD: strcpy(request,"HEAD");break;
    289         case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
    290         case METHOD_TRACE: strcpy(request,"TRACE");break;
    291     }
    292             
    293     strcat(request," ");
    294     
    295     //判断Http请求的地址正确性以及有效性
    296     if(NULL==strstr(url,"://"))
    297     {
    298         fprintf(stderr, "
    %s: is not a valid URL.
    ",url);
    299         exit(2);
    300     }
    301     if(strlen(url)>1500)
    302     {
    303         fprintf(stderr,"URL is too long.
    ");
    304         exit(2);
    305     }
    306       
    307     if(proxyhost==NULL)
    308     { 
    309         if (0!=strncasecmp("http://",url,7)) 
    310         {  
    311             fprintf(stderr,"
    Only HTTP protocol is directly supported, set --proxy for others.
    ");
    312             exit(2);
    313         }
    314     }
    315     //跳过超链接的协议头,找到顶级域名  
    316     /* protocol/host delimiter */
    317     i=strstr(url,"://")-url+3;
    318     /* printf("%d
    ",i); */
    319     
    320     //跳过顶级域名
    321     if(strchr(url+i,'/')==NULL) 
    322     {
    323         fprintf(stderr,"
    Invalid URL syntax - hostname don't ends with '/'.
    ");
    324         exit(2);
    325     }
    326     if(proxyhost==NULL)
    327     {
    328         //获取指定的通信端口,如果不存在则使用默认的80端口,同时获得主机名称-域名,网页的绝对路径
    329         /* get port from hostname */
    330         if(index(url+i,':')!=NULL &&
    331             index(url+i,':')<index(url+i,'/'))
    332         {
    333             strncpy(host,url+i,strchr(url+i,':')-url-i);
    334             bzero(tmp,10);
    335             strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
    336             /* printf("tmp=%s
    ",tmp); */
    337             proxyport=atoi(tmp);
    338             if(proxyport==0) proxyport=80;
    339         } 
    340         else
    341         {
    342             strncpy(host,url+i,strcspn(url+i,"/"));
    343         }
    344         //printf("Host=%s
    ",host);
    345         strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
    346     } 
    347     else
    348     {
    349         // printf("ProxyHost=%s
    ProxyPort=%d
    ",proxyhost,proxyport);
    350         strcat(request,url);
    351     }
    352     //设置请求协议的版本,拼接请求头
    353     if(http10==1)
    354     {
    355         strcat(request," HTTP/1.0");
    356     }
    357     else if (http10==2)
    358     {
    359         strcat(request," HTTP/1.1");
    360     }
    361     strcat(request,"
    ");
    362     
    363     if(http10>0)
    364     {
    365         strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"
    ");
    366     }
    367         
    368     if(proxyhost==NULL && http10>0)
    369     {
    370         strcat(request,"Host: ");
    371         strcat(request,host);
    372         strcat(request,"
    ");
    373     }
    374     if(force_reload && proxyhost!=NULL)
    375     {
    376         strcat(request,"Pragma: no-cache
    ");
    377     }
    378     if(http10>1)
    379     {
    380         strcat(request,"Connection: close
    ");
    381     }
    382         
    383     /* add empty line at end */
    384     if(http10>0)
    385     {
    386         strcat(request,"
    ");  
    387     }  
    388     //printf("Req=%s
    ",request);
    389 }
    390 
    391 /* vraci system rc error kod */
    392 static int bench(void)
    393 {
    394     int i,j,k;    
    395     pid_t pid=0;
    396     FILE *f;
    397     
    398     /* check avaibility of target server */
    399     //测试远程主机的是否可用
    400     i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
    401     if(i<0) 
    402     { 
    403         fprintf(stderr,"
    Connect to server failed. Aborting benchmark.
    ");
    404         return 1;
    405     }
    406     close(i);
    407     
    408     /* create pipe */
    409     //创建无名管道
    410     if(pipe(mypipe))
    411     {
    412         perror("pipe failed.");
    413         return 3;
    414     }
    415     
    416     /* not needed, since we have alarm() in childrens */
    417     /* wait 4 next system clock tick */
    418     /*
    419     cas=time(NULL);
    420     while(time(NULL)==cas)
    421           sched_yield();
    422     */
    423     
    424     /* fork childs */
    425     //创建进程
    426     for(i = 0;i < clients;i++)
    427     {
    428         /*
    429         调用fork有一个特殊的地方,就是调用一次却能返回两次,有三种不同的返回值:
    430         1、在父进程中,fork返回新创建的子进程的进程ID
    431         2、在子进程中,fork返回0
    432         3、如果出现错误,fork返回一个负值
    433         */
    434         pid=fork();
    435         if(pid <= (pid_t) 0)
    436         {
    437             /* child process or error*/
    438             sleep(1); /* make childs faster */
    439             /*这个break很重要,它主要让子进程只能从父进程生成,
    440             否则子进程会在创建子进程,子子孙孙无穷尽*/
    441             break;
    442         }
    443     }
    444     
    445     if( pid< (pid_t) 0)
    446     {
    447         fprintf(stderr,"problems forking worker no. %d
    ",i);
    448         perror("fork failed.");
    449         return 3;
    450     }
    451     
    452     if(pid== (pid_t) 0)
    453     {
    454         /* I am a child */
    455         //子进程执行请求,所有子进程都发出请求
    456         if(proxyhost==NULL)
    457         {
    458             benchcore(host,proxyport,request);
    459         }
    460         else
    461         {
    462             benchcore(proxyhost,proxyport,request);
    463         }
    464         /* write results to pipe */
    465         //打开无名管道的写端口
    466         f = fdopen(mypipe[1],"w");
    467         if(f==NULL)
    468         {
    469             perror("open pipe for writing failed.");
    470             return 3;
    471         }
    472         /* fprintf(stderr,"Child - %d %d
    ",speed,failed); */
    473         //向管道中写入数据
    474         fprintf(f,"%d %d %d
    ",speed,failed,bytes);
    475         fclose(f);
    476         return 0;
    477     }
    478     else
    479     {
    480         //父进程中打开管道的读端口
    481         f=fdopen(mypipe[0],"r");
    482         if(f==NULL) 
    483         {
    484             perror("open pipe for reading failed.");
    485             return 3;
    486         }
    487         //设置缓冲区大小
    488         setvbuf(f,NULL,_IONBF,0);
    489         //初始化访问速度、失败次数、字节数
    490         speed=0;
    491         failed=0;
    492         bytes=0;
    493         
    494         while(1)
    495         {   
    496             //获取无名管道中的数据,进行统计
    497             pid=fscanf(f,"%d %d %d",&i,&j,&k);
    498             if(pid<2)    
    499             {
    500                 fprintf(stderr,"Some of our childrens died.
    ");
    501                 break;
    502             }
    503             speed+=i;
    504             failed+=j;
    505             bytes+=k;
    506             /* fprintf(stderr,"*Knock* %d %d read=%d
    ",speed,failed,pid); */
    507             if(--clients==0)
    508             {
    509                 break;  
    510             } 
    511         }
    512         fclose(f);
    513         //显示统计结果
    514         printf("
    Speed=%d pages/min, %d bytes/sec.
    Requests: %d susceed, %d failed.
    ",
    515             (int)((speed+failed)/(benchtime/60.0f)),
    516             (int)(bytes/(float)benchtime),
    517             speed,
    518             failed);
    519     }
    520     return i;
    521 }
    522 
    523 void benchcore(const char *host,const int port,const char *req)
    524 {
    525     int rlen;
    526     char buf[1500];
    527     int s,i;
    528     struct sigaction sa;
    529     
    530     /* setup alarm signal handler */
    531     //这个是关键,当程序执行到指定的秒数之后,发送 SIGALRM 信号 
    532     sa.sa_handler=alarm_handler;
    533     sa.sa_flags=0;
    534     if(sigaction(SIGALRM,&sa,NULL))
    535     {
    536         exit(3);
    537     }
    538         
    539     alarm(benchtime);
    540     
    541     rlen=strlen(req);
    542     //无限执行请求,并发操作,直到接收到 SIGALRM 信号将 timerexpired 设置为 1 时
    543     nexttry:while(1)
    544     {
    545         if(timerexpired)
    546         {
    547             if(failed>0)
    548             {
    549                 /* fprintf(stderr,"Correcting failed by signal
    "); */
    550                 failed--;
    551             }
    552             return;
    553         }
    554         //连接服务器
    555         s=Socket(host,port);                          
    556         if(s<0)
    557         { 
    558             failed++;
    559             continue;
    560         } 
    561         //发送请求头
    562         if( rlen!=write(s,req,rlen) ) 
    563         {
    564             failed++;
    565             close(s);
    566             continue;
    567         }
    568         
    569         if(http10==0)
    570         {
    571             //如果是 http/0.9 则关闭socket的写操作
    572             if(shutdown(s,1)) 
    573             { 
    574                 failed++;
    575                 close(s);
    576                 continue;
    577             }
    578         }            
    579             
    580         if(force==0) 
    581         {
    582             /* read all available data from socket */
    583             //读取服务器的响应数据,计算传输的字节数
    584             while(1)
    585             {
    586                 if(timerexpired)
    587                 {
    588                     break;
    589                 } 
    590                 i = read(s,buf,1500);
    591                 /* fprintf(stderr,"%d
    ",i); */
    592                 //响应失败关闭连接,执行下一次请求,记录失败的次数
    593                 if( i < 0 ) 
    594                 { 
    595                     failed++;
    596                     close(s);
    597                     goto nexttry;
    598                 }
    599                 else if(i==0) 
    600                 {
    601                     break;
    602                 }
    603                 else
    604                 {
    605                     bytes+=i;
    606                 }
    607             }
    608         }
    609         //关闭连接,执行下一次请求
    610         if(close(s)) 
    611         {
    612             failed++;
    613             continue;
    614         }
    615         //记录请求成功的次数
    616         speed++;
    617     }
    618 }
  • 相关阅读:
    linux下实现tomcat定时自动重启
    Mybatis中实现oracle的批量插入、更新
    linux 下ffmpeg和mencoder安装
    jwplayer 源代码重新编译
    如何让android sdk manager飞奔安装sdk
    linux+apache+mod_Jk+tomcat实现tomcat集群
    oracle知识杂记
    Spring Mvc session拦截器实现
    Linux下Tomcat安装、配置
    Linux下安装、配置、授权、调优Mysql
  • 原文地址:https://www.cnblogs.com/stlong/p/4483592.html
Copyright © 2011-2022 走看看