/*
* (C) Radim Kolar 1997-2004
* This is free software, see GNU Public License version 2 for
* details.
*
* Simple forking WWW Server benchmark:
*
* Usage:
* webbench --help
*
* Return codes:
* 0 - sucess
* 1 - benchmark failed (server is not on-line)
* 2 - bad param
* 3 - internal error, fork failed
*
*/
#include "socket.c"
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h> /* values */
volatile int timerexpired=;//判断测压市场是否已经达到设定的时间
int speed=;//记录进程成功得到服务器相应的数量
int failed=;//记录失败的数量(speed表示成功数,failed表示失败数)
int bytes=;//记录进程成功读取的字节数
/* globals */
int http10=; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 *///HTTP版本
/* Allow: GET, HEAD, OPTIONS, TRACE */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
int method=METHOD_GET;//定义HTTP请求方法:默认方式GET请求
int clients=;//并发数目,默认只有一个进程发送请求,通过 -c 参数设置
int force=;//是否需要等待读取从server返回的数据,0表示要等待读取
int force_reload=;//是否使用缓存,1表示不缓存,0表示可以缓存页面
int proxyport=;//代理服务器的端口
char *proxyhost=NULL;//代理服务器的端口
int benchtime=;//测压时间,默认30秒,通过 -t 参数设置
/* internal */
int mypipe[];//使用管道进行父进程和子进程的通信
char host[MAXHOSTNAMELEN];//服务器端IP
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE];//发送HTTP请求的内容 static const struct option long_options[]=
{
{"force",no_argument,&force,},
{"reload",no_argument,&force_reload,},
{"time",required_argument,NULL,'t'},
{"help",no_argument,NULL,'?'},
{"http09",no_argument,NULL,''},
{"http10",no_argument,NULL,''},
{"http11",no_argument,NULL,''},
{"get",no_argument,&method,METHOD_GET},
{"head",no_argument,&method,METHOD_HEAD},
{"options",no_argument,&method,METHOD_OPTIONS},
{"trace",no_argument,&method,METHOD_TRACE},
{"version",no_argument,NULL,'V'},
{"proxy",required_argument,NULL,'p'},
{"clients",required_argument,NULL,'c'},
{NULL,,NULL,}
}; /* prototypes */
static void benchcore(const char* host,const int port, const char *request);
static int bench(void);
static void build_request(const char *url); /*
webbench在运行时可以设定压测的持续时间,以秒为单位。
例如我们希望测试30秒,也就意味着压测30秒后程序应该退出了。
webbench中使用信号(signal)来控制程序结束。
函数1是在到达结束时间时运行的信号处理函数。
它仅仅是将一个记录是否超时的变量timerexpired标记为true。
后面会看到,在程序的while循环中会不断检测此值,
只有timerexpired=1,程序才会跳出while循环并返回。
*/
static void alarm_handler(int signal)
{
timerexpired=;
} /*
教你如何使用webbench的函数,
在linux命令行调用webbench方法不对的时候运行,作为提示。
有一些比较常用的,比如-c来指定并发进程的多少;
-t指定压测的时间,以秒为单位;
支持HTTP0.9,HTTP1.0,HTTP1.1三个版本;
支持GET,HEAD,OPTIONS,TRACE四种请求方式。
不要忘了调用时,命令行最后还应该附上要测的服务端URL。
*/
static void usage(void)
{
fprintf(stderr,
"webbench [option]... URL\n"
" -f|--force Don't wait for reply from server.\n"
" -r|--reload Send reload request - Pragma: no-cache.\n"
" -t|--time <sec> Run benchmark for <sec> seconds. Default 30.\n"
" -p|--proxy <server:port> Use proxy server for request.\n"
" -c|--clients <n> Run <n> HTTP clients at once. Default one.\n"
" -9|--http09 Use HTTP/0.9 style requests.\n"
" -1|--http10 Use HTTP/1.0 protocol.\n"
" -2|--http11 Use HTTP/1.1 protocol.\n"
" --get Use GET request method.\n"
" --head Use HEAD request method.\n"
" --options Use OPTIONS request method.\n"
" --trace Use TRACE request method.\n"
" -?|-h|--help This information.\n"
" -V|--version Display program version.\n"
);
};
int main(int argc, char *argv[])
{
int opt=;
int options_index=;
char *tmp=NULL; if(argc==)
{
usage();
return ;
} while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
{
switch(opt)
{
case : break;
case 'f': force=;break;
case 'r': force_reload=;break;
case '': http10=;break;
case '': http10=;break;
case '': http10=;break;
case 'V': printf(PROGRAM_VERSION"\n");exit();
/**
*C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)
*int atoi(const char *str)
*str -- 要转换为整数的字符串。
*该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
*/
case 't': benchtime=atoi(optarg);break;
case 'p':
/* proxy server parsing server:port */
/**
*strrchr() 函数用于查找某字符在字符串中最后一次出现的位置,其原型为:
*char * strrchr(const char *str, int c);
*【参数】str 为要查找的字符串,c 为要查找的字符。
*strrchr() 将会找出 str 字符串中最后一次出现的字符 c 的地址,然后将该地址返回。
*注意:字符串 str 的结束标志 NUL 也会被纳入检索范围,所以 str 的组后一个字符也可以被定位。
*【返回值】如果找到就返回该字符最后一次出现的位置,否则返回 NULL。
*返回的地址是字符串在内存中随机分配的地址再加上你所搜索的字符在字符串位置。设字符在字符串中首次出现的位置为 i,那么返回的地址可以理解为 str + i。
*/
/**
*optarg : char *optarg; //选项的参数指针
*/
tmp=strrchr(optarg,':');
proxyhost=optarg;
if(tmp==NULL)
{
break;
}
if(tmp==optarg)
{
fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
return ;
}
/**
*C 库函数 size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
*size_t strlen(const char *str)
*参数:str -- 要计算长度的字符串。
*该函数返回字符串的长度。
*/
if(tmp==optarg+strlen(optarg)-)
{
fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
return ;
}
*tmp='\0';//把:替换为\0
//从\0之后到\0之前
proxyport=atoi(tmp+);break;
case ':':
case 'h':
case '?': usage();return ;break;
case 'c': clients=atoi(optarg);break;
}
}
//int optind:argv的当前索引值。当getopt函数在while循环中使用时,剩下的字符串为操作数,下标从optind到argc-1
//argc,argv 参考:https://www.cnblogs.com/lanshanxiao/p/11568037.html
//getopt_long()中的函数,参考:https://www.cnblogs.com/xhg940420/p/7016574.html
//扫描完毕后,optind指向非长选项和非短选项和非参数的字段,这里应该指向URL
if(optind==argc) {
fprintf(stderr,"webbench: Missing URL!\n");
usage();
return ;
} if(clients==) clients=;
if(benchtime==) benchtime=;
/* Copyright */
fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
"Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
);
/**
*命令读取完成后,argv[optind]中应该存放着URL,
*建立完整的http请求,http请求存放在变量char request[REQUEST_SIZE]中
*/
build_request(argv[optind]);
/* print bench info *///输出平台信息
printf("\nBenchmarking: ");
switch(method)
{
case METHOD_GET:
default:
printf("GET");break;
case METHOD_OPTIONS:
printf("OPTIONS");break;
case METHOD_HEAD:
printf("HEAD");break;
case METHOD_TRACE:
printf("TRACE");break;
}
printf(" %s",argv[optind]);//打印出URL
switch(http10)
{
case : printf(" (using HTTP/0.9)");break;
case : printf(" (using HTTP/1.1)");break;
}
printf("\n");
if(clients==) printf("1 client");
else
printf("%d clients",clients); printf(", running %d sec", benchtime);
if(force) printf(", early socket close");
if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport);
if(force_reload) printf(", forcing reload");
printf(".\n");
//压力测试最后一句话,所有的压力测试都在bench函数中实现
return bench();
} /*
函数主要操作全局变量char request[REQUEST_SIZE],根据url填充其内容。
典型的HTTP的GET请求:
GET /test.jpg HTTP/1.1
User-Agent: WebBench 1.5
Host:192.168.10.1
Pragma: no-cache
Connection: close build_request函数的目的就是要把
类似于以上这一大坨信息全部存到全局变量request[REQUEST_SIZE]中,
其中换行操作使用的是”\r\n”。
而以上这一大坨信息的具体内容是要根据命令行输入的参数,以及url来确定的。
该函数使用了大量的字符串操作函数,
例如strcpy,strstr,strncasecmp,strlen,strchr,index,strncpy,strcat。
对这些基础函数不太熟悉的同学可以借这个函数复习一下。
*/
void build_request(const char *url)
{
char tmp[];
int i; bzero(host,MAXHOSTNAMELEN);//bzero():置host(字节字符串)前MAXHOSTNAMELEN个字节为0,包括'\0')
bzero(request,REQUEST_SIZE); if(force_reload && proxyhost!=NULL && http10<) http10=;//满足一定条件,更换HTTP协议
if(method==METHOD_HEAD && http10<) http10=;
if(method==METHOD_OPTIONS && http10<) http10=;
if(method==METHOD_TRACE && http10<) http10=; switch(method)
{
default:
//strcpy() 函数用于对字符串进行复制(拷贝)。
//char* strcpy(char* strDestination, const char* strSource);
//strSource 指向的字符串复制到 strDestination
case METHOD_GET: strcpy(request,"GET");break;
case METHOD_HEAD: strcpy(request,"HEAD");break;
case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
case METHOD_TRACE: strcpy(request,"TRACE");break;
} //char*strcat(char* strDestination, const char* strSource);
/*
strcat() 函数用来将两个字符串连接(拼接)起来。
strcat() 函数把 strSource 所指向的字符串追加到 strDestination 所指向的字符串的结尾,
所以必须要保证 strDestination 有足够的内存空间来容纳两个字符串,否则会导致溢出错误。
注意:strDestination 末尾的\0会被覆盖,strSource 末尾的\0会一起被复制过去,最终的字符串只有一个\0。
*/
strcat(request," "); /*
char *strstr(const char *haystack, const char *needle)
在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 '\0'。
该函数返回在 haystack 中第一次出现 needle 的地址,如果未找到则返回 null。
*/
if(NULL==strstr(url,"://"))
{
fprintf(stderr, "\n%s: is not a valid URL.\n",url);
exit();
} /*
strlen(char *);
检测字符串实际长度。
strlen(char *)检测的是'\0',strlen(char *)碰到'\0'就返回'\0'以前的字符数(不包括'\0')。
strlen(char*)函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个'\0',
如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到'\0'停止。
*/
if(strlen(url)>)
{
fprintf(stderr,"URL is too long.\n");
exit();
}
if(proxyhost==NULL)
/*
int strncasecmp(const char *s1, const char *s2, size_t n);
strncasecmp()用来比较参数s1 和s2 字符串前n个字符,比较时会自动忽略大小写的差异。
若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值,s1 若小于s2 则返回小于0 的值。
*/
if (!=strncasecmp("http://",url,))
{
fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
exit();
}
/* protocol/host delimiter */
i=strstr(url,"://")-url+;
/* printf("%d\n",i); */ /*
char *strchr(const char *str, char c)
该函数返回在字符串 str 中第一次出现字符 c 的地址,如果未找到该字符则返回 NULL。
*/
if(strchr(url+i,'/')==NULL) {
fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n");
exit();
}
if(proxyhost==NULL)
{
/* get port from hostname */
if(index(url+i,':')!=NULL &&
index(url+i,':')<index(url+i,'/'))
{
/*
char * strncpy(char *s1,char *s2,size_t n);
   将字符串s2中最多n个字符复制到字符数组s1中,返回指向s1的指针。
   注意:如果源串长度大于n,则strncpy不复制最后的'\0'结束符,
所以是不安全的,复制完后需要手动添加字符串的结束符才行。
*/
strncpy(host,url+i,strchr(url+i,':')-url-i);
bzero(tmp,);
strncpy(tmp,index(url+i,':')+,strchr(url+i,'/')-index(url+i,':')-);
/* printf("tmp=%s\n",tmp); */ /*
C语言库函数名: atoi
   功 能: 把字符串转换成整型数.
   名字来源:array to integer 的缩写.
   函数说明: atoi()会扫描参数nptr字符串,如果第一个字符不是数字也不是正负号返回零,
否则开始做类型转换,之后检测到非数字或结束符 \0 时停止转换,返回整型数。
   原型: int atoi(const char *nptr);
*/
proxyport=atoi(tmp);
if(proxyport==) proxyport=;
} else
{
/*
size_t strcspn(const char *s, const char * reject);
函数说明:strcspn()从参数s 字符串的开头计算连续的字符,
而这些字符都完全不在参数reject 所指的字符串中。
简单地说, 若strcspn()返回的数值为n,则代表字符串s 开头连续有n 个字符都不含字符串reject 内的字符。
返回值:返回字符串s 开头连续不含字符串reject 内的字符数目。
*/
strncpy(host,url+i,strcspn(url+i,"/"));
}
// printf("Host=%s\n",host);
strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
} else
{
// printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
strcat(request,url);
}
if(http10==)
strcat(request," HTTP/1.0");
else if (http10==)
strcat(request," HTTP/1.1");
strcat(request,"\r\n");
if(http10>)
strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");
if(proxyhost==NULL && http10>)
{
strcat(request,"Host: ");
strcat(request,host);
strcat(request,"\r\n");
}
if(force_reload && proxyhost!=NULL)
{
strcat(request,"Pragma: no-cache\r\n");
}
if(http10>)
strcat(request,"Connection: close\r\n");
/* add empty line at end */
if(http10>) strcat(request,"\r\n");
// printf("Req=%s\n",request);
} /**
*先进行了一次socket连接,确认能连通以后,才进行后续步骤。
*调用pipe函数初始化一个管道,用于子进行向父进程汇报测试数据。
*子进程根据clients数量fork出来。
*每个子进程都调用函数5进行测试,并将结果输出到管道,供父进程读取。
*父进程负责收集所有子进程的测试数据,并汇总输出。
*/
/* vraci system rc error kod */
static int bench(void)
{
int i,j,k;
pid_t pid=;
FILE *f; /* check avaibility of target server */
//检测目标服务器的可用性,调用socket.c文件中的函数
i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
if(i<) {//处理错误
fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
return ;
}
close(i);
/* create pipe */
if(pipe(mypipe))//管道用于子进程向父进程回报数据
{//错误处理
perror("pipe failed.");
return ;
} /* not needed, since we have alarm() in childrens */
/* wait 4 next system clock tick */
/*
cas=time(NULL);
while(time(NULL)==cas)
sched_yield();
*/ /* fork childs */
for(i=;i<clients;i++)//根据clients大小fork出来足够的子进程进行测试
{
pid=fork();
if(pid <= (pid_t) )
{
/* child process or error*/
sleep(); /* make childs faster */
break;
}
} if( pid< (pid_t) )
{//错误处理
fprintf(stderr,"problems forking worker no. %d\n",i);
perror("fork failed.");
return ;
} if(pid== (pid_t) )//若是子进程,调用benchcore进行测试
{
/* I am a child */
if(proxyhost==NULL)
benchcore(host,proxyport,request);
else
benchcore(proxyhost,proxyport,request); /* write results to pipe */
f=fdopen(mypipe[],"w");//子进程将测试结果输出到管道
if(f==NULL)
{//错误处理
perror("open pipe for writing failed.");
return ;
}
/* fprintf(stderr,"Child - %d %d\n",speed,failed); */
fprintf(f,"%d %d %d\n",speed,failed,bytes);
fclose(f);
return ;
} else
{//若是父进程,则从管道读取子进程输出,并做汇总
f=fdopen(mypipe[],"r");
if(f==NULL)
{//错误处理
perror("open pipe for reading failed.");
return ;
}
setvbuf(f,NULL,_IONBF,);
speed=;
failed=;
bytes=; while()
{//从管道读取数据,fscanf为阻塞式函数
pid=fscanf(f,"%d %d %d",&i,&j,&k);
if(pid<)
{//错误处理
fprintf(stderr,"Some of our childrens died.\n");
break;
}
speed+=i;
failed+=j;
bytes+=k;
/* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
if(--clients==) break;//这句用于记录已经读了多少个子进程的数据,读完就退出
}
fclose(f);
//最后将结果打印到屏幕上
printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
(int)((speed+failed)/(benchtime/60.0f)),
(int)(bytes/(float)benchtime),
speed,
failed);
}
return i;
} /**
*benchcore是子进程进行压力测试的函数,被每个子进程调用。
*这里使用了SIGALRM信号来控制时间,
*alarm函数设置了多少时间之后产生SIGALRM信号,一旦产生此信号,将运行alarm_handler(),
*使得timerexpired=1,这样可以通过判断timerexpired值来退出程序。
*另外,全局变量force表示我们是否在发出请求后需要等待服务器的响应结果
*/
void benchcore(const char *host,const int port,const char *req)
{
int rlen;
char buf[];//记录服务器响应请求所返回的数据
int s,i;
struct sigaction sa; /* setup alarm signal handler */
sa.sa_handler=alarm_handler;//将函数alarm_handler地址赋值给sa.alarm_handler,作为信号处理函数
sa.sa_flags=;
if(sigaction(SIGALRM,&sa,NULL))//超时会产生信号SIGALRM,用sa中的指定函数处理
exit();
alarm(benchtime);//开始计时 rlen=strlen(req);
nexttry:while()
{
if(timerexpired)//一旦超时则返回
{
if(failed>)
{
/* fprintf(stderr,"Correcting failed by signal\n"); */
failed--;
}
return;
}
s=Socket(host,port);//调用socket建立TCP连接
if(s<) { failed++;continue;}
if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}//发出请求
if(http10==) //针对http0.9做的特殊处理
if(shutdown(s,)) { failed++;close(s);continue;}
if(force==) //全局变量force表示是否要等待服务器返回的数据
{
/* read all available data from socket */
while()
{
if(timerexpired) break;
i=read(s,buf,);//从socket读取返回数据
/* fprintf(stderr,"%d\n",i); */
if(i<)
{
failed++;
close(s);
goto nexttry;
}
else
if(i==) break;
else
bytes+=i;
}
}
if(close(s)) {failed++;continue;}
speed++;
}
}

webbench网站测压工具源码分析的更多相关文章

  1. ab webbench 网站测压解决

    ab 网站测压解决 ab –c 100 –n 100 http://192.168.1.117/forum.php (测试方式) 同时发100人发100个请求 ab –c 100 –n 1000 ht ...

  2. bootstrap_栅格系统_响应式工具_源码分析

    -----------------------------------------------------------------------------margin 为负 ​使盒子重叠 ​等高 等高 ...

  3. [软件测试]网站压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  4. 网站(Web)压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  5. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  6. 多渠道打包工具Walle源码分析

    一.背景 首先了解多渠道打包工具Walle之前,我们需要先明确一个概念,什么是渠道包. 我们要知道在国内有无数大大小小的APP Store,每一个APP Store就是一个渠道.当我们把APP上传到A ...

  7. 开源网站流量统计系统Piwik源码分析——参数统计(一)

    Piwik现已改名为Matomo,这是一套国外著名的开源网站统计系统,类似于百度统计.Google Analytics等系统.最大的区别就是可以看到其中的源码,这正合我意.因为我一直对统计的系统很好奇 ...

  8. WebBench源码分析

    源码分析共享地址:https://github.com/fivezh/WebBench 下载源码后编译源程序后即可执行: sudo make clean sudo make & make in ...

  9. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第1节: FastThreadLocal的使用和创建

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 概述: FastThreadLocal我们在剖析堆外内存分配的时候简单介绍过, 它类似于JDK的ThreadL ...

随机推荐

  1. MongoDB权限配置

    参考文章:https://blog.csdn.net/qq_26896281/article/details/81206492 https://blog.csdn.net/u012373281/art ...

  2. haproxy 2.0 dataplaneapi docker 镜像

    为了方便测试dataplaneapi 基于官方的docker镜像,制作了一个简单的包含dataplaneapi 的镜像 下载dataplaneapi https://github.com/haprox ...

  3. SDU暑假排位第一场 (Gym - 100889)

    啊今天有点挂机啊 D题和队友暴力后发现一组数据跑得飞快 然后遇上1e5组数据就没了..... 然后我疯狂优化暴力 然后去世了 最后半小时F也没写出来 主要还是最后有点慌并且没有考虑清楚 导致情况越写越 ...

  4. 钠 GZY整理贪心

    目录 CF140C New Year Snowmen CF161B Discounts P1842 奶牛玩杂技 CF140C New Year Snowmen #include <bits/st ...

  5. Android开发:文本控件详解——RadioButton和CheckBox(一)基本属性

    一.RadioButton和RadioGroup: RadioButton是单个的圆形单选框,而RadioGroup是可以容纳多个RadioButton存在的容器,因此RadioButton和Radi ...

  6. 一个按权重(weight)进行LB的算法

    package netty; import com.google.common.collect.ImmutableList; import lombok.SneakyThrows; import ja ...

  7. HDFS部署测试记录(2019/05)

    目录 HDFS部署测试记录 0.HDFS基础知识 1.基本组成结构与文件访问过程 2.NameNode启动时如何维护元数据 3.HDFS文件上传流程 1.系统环境 1.安装大致记录: 2.磁盘分区 3 ...

  8. windows7平台android studio新建Android项目,报错

    Failed to install the following Android SDK packages as some licences have not been accepted. platfo ...

  9. Jenkins 使用 SonarQube 扫描 Coding

    Jenkins 使用 SonarQube 扫描 Coding   系统环境: Jenkins 版本:2.176 SonarQube 版本:7.4.0 一.SonarQube 介绍 1.SonarQub ...

  10. git 常用命令的总结

    1. git 查看分支 git branch (星号代表当前的分支) 2. 创建一个本地分支 git checkout -b 分支名称 3. 将本地新建分支提交到远程 git push origin ...