为了更好的理解函数调用关系,我们可以在iperf_api.h中增加编译宏,然后在每个函数的入口处增加这个宏的调用,可以打印出函数调用关系。
#define PRINTFILEFUNCLINE printf("debug out: func = %-25s,line = %4d, file = %s\r\n",__FUNCTION__,__LINE__,__FILE__);
比如在main()函数中增加如下调用 PRINTFILEFUNCLINE,则可以打印出函数名,文件名,行号。
int
main(int argc, char **argv)
{
struct iperf_test *test;
PRINTFILEFUNCLINE
test = iperf_new_test();
if (!test)
iperf_errexit(NULL, "create new test error - %s", iperf_strerror(i_errno));
iperf_defaults(test); /* sets defaults */
}
执行结果如下:
xxx@xxx-pc:~/iperf3/iperf$ iperf3 -s
debug out: func = main ,line = 62, file = main.c
debug out: func = iperf_new_test ,line = 2731, file = iperf_api.c
debug out: func = iperf_defaults ,line = 2795, file = iperf_api.c
debug out: func = iperf_parse_arguments ,line = 1125, file = iperf_api.c
debug out: func = run ,line = 145, file = main.c
debug out: func = iperf_run_server ,line = 455, file = iperf_server_api.c
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
xxx@xxx-pc:~/iperf3/iperf$ iperf3 -c 127.0.0.1
debug out: func = main ,line = 62, file = main.c
debug out: func = iperf_new_test ,line = 2731, file = iperf_api.c
debug out: func = iperf_defaults ,line = 2795, file = iperf_api.c
debug out: func = iperf_parse_arguments ,line = 1125, file = iperf_api.c
debug out: func = run ,line = 145, file = main.c
debug out: func = iperf_run_client ,line = 526, file = iperf_client_api.c
Connecting to host 127.0.0.1, port 5201
在src/main.c文件里,我们可以找到main函数,除了用TEST_PROC_AFFINITY宏括起来的部分外(TEST_PROC_AFFINITY编译宏是一个未定义宏,所以这部分代码是未启用的,为了阅读方便,在本文中删除了),我们可以看到整个main函数分成4大步骤:
int
main(int argc, char **argv)
{
struct iperf_test *test;
#ifdef TEST_PROC_AFFINITY
//这部分代码是未实现的功能,删除
#endif
//第一步对象实例化,创建对象test,并用iperf_defaults()对test里的变量和指针赋默认值
test = iperf_new_test();
if (!test)
iperf_errexit(NULL, "create new test error - %s", iperf_strerror(i_errno));
iperf_defaults(test); /* sets defaults */
//第二步,根据用户在终端命令行界面输入参数,初始化对象test
if (iperf_parse_arguments(test, argc, argv) < 0) {
iperf_err(test, "parameter error - %s", iperf_strerror(i_errno));
fprintf(stderr, "\n");
usage_long(stdout);
exit(1);
}
//第三步,运行对象test,处理测试过程
if (run(test) < 0)
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
//第四步,测试结束,删除销毁对象test
iperf_free_test(test);
return 0;
}
文章开头介绍过,iperf3是用C语言实现面向对象编程的典范, 所以这里第一步,调用iperf_new_test创建对象,C语言里不支持class, 所以这里用struct里:定义数据+函数指针的方式,来实现类的数据属性+成员函数。然后通过malloc给结构指针附值的方式,实现对象(全部结构指针)test的创建与分配内存(class创建对象的本质是给对象分配内存)。以及给test的settings和bitrate_limit_intervals_traffic_bytes二个struct指针进入分配内存。
struct iperf_test *
iperf_new_test()
{
struct iperf_test *test;
//通过malloc给test指针分配内存---------对象实例化
test = (struct iperf_test *) malloc(sizeof(struct iperf_test));
if (!test) {
i_errno = IENEWTEST;
return NULL;
}
/* initialize everything to zero */
memset(test, 0, sizeof(struct iperf_test));
//通过malloc给test->settings指针分配内存
test->settings = (struct iperf_settings *) malloc(sizeof(struct iperf_settings));
if (!test->settings) {
free(test);
i_errno = IENEWTEST;
return NULL;
}
memset(test->settings, 0, sizeof(struct iperf_settings));
//通过malloc给test->bitrate_limit_intervals_traffic_bytes 指针分配内存
test->bitrate_limit_intervals_traffic_bytes = (iperf_size_t *) malloc(sizeof(iperf_size_t) * MAX_INTERVAL);
if (!test->bitrate_limit_intervals_traffic_bytes) {
free(test->settings);
free(test);
i_errno = IENEWTEST;
return NULL;
}
memset(test->bitrate_limit_intervals_traffic_bytes, 0, sizeof(sizeof(iperf_size_t) * MAX_INTERVAL));
/* By default all output goes to stdout */
test->outfile = stdout;
return test;
}
这一步,本质上是,调用iperf_defaults对test进行初步的初始化,对test里的数据和函数附默认值(比如tcp, udp, sctp分别调用什么函数去进行数据发送与接收,调用什么函数进行连接的函数指针的初始化),这里我们可以把依赖于配置而附不同的函数的函数指针的过程视为虚接口的初始化。这个函数的调用可以视为构造函数的调用。然后读取用户在终端命令行界面上输入的参数,对test进行变量与方法函数的初始化(配置各种属性,以及根据输入参数的不同,比如是测试UDP还是TCP,是客户端还是服务端等等)。
以-c和-s二个参数为例,如果用户输入的命令行为iperf3 -c 则将test初始化为发送端模式,以便后面第三步调用run时可以找到正确的方法(与客户端、服务端,TCP/UDP相应的回调函数)
int
iperf_defaults(struct iperf_test *testp)
{
struct protocol *tcp, *udp;
#if defined(HAVE_SCTP_H)
struct protocol *sctp;
#endif /* HAVE_SCTP_H */
testp->omit = OMIT;
testp->duration = DURATION;
testp->diskfile_name = (char*) 0;
testp->affinity = -1;
testp->server_affinity = -1;
TAILQ_INIT(&testp->xbind_addrs);
#if defined(HAVE_CPUSET_SETAFFINITY)
CPU_ZERO(&testp->cpumask);
#endif /* HAVE_CPUSET_SETAFFINITY */
testp->title = NULL;
testp->extra_data = NULL;
testp->congestion = NULL;
testp->congestion_used = NULL;
testp->remote_congestion_used = NULL;
testp->server_port = PORT;
//以代码省略
}
int
iperf_parse_arguments(struct iperf_test *test, int argc, char **argv)
{
static struct option longopts[] =
{
/*
此处省略大量代码
*/
{"server", no_argument, NULL, 's'},
{"client", required_argument, NULL, 'c'},
/*
此处省略大量代码
*/
{"version4", no_argument, NULL, '4'},
{"version6", no_argument, NULL, '6'},
/*
此处省略大量代码
*/
{NULL, 0, NULL, 0}
};
while ((flag = getopt_long(argc, argv, "p:f:i:D1VJvsc:ub:t:n:k:l:P:Rw:B:M:N46S:L:ZO:F:A:T:C:dI:hX:", longopts, NULL)) != -1) {
switch (flag) {
/*
此处省略大量代码
*/
case 's':
if (test->role == 'c') {
i_errno = IESERVCLIENT;
return -1;
}
iperf_set_test_role(test, 's');
break;
case 'c':
if (test->role == 's') {
i_errno = IESERVCLIENT;
return -1;
}
iperf_set_test_role(test, 'c');
}
break;
case '4':
test->settings->domain = AF_INET;
break;
case '6':
test->settings->domain = AF_INET6;
break;
/*
此处省略大量代码
*/
}
/*
此处省略大量代码
*/
if ((test->role != 'c') && (test->role != 's')) {
i_errno = IENOROLE;
return -1;
}
/*
此处省略大量代码
*/
return 0;
}
在这里,程序会根据自己是运行在服务端还是客户端模式,执行不同的测试流程:
{
/* Termination signals. */
iperf_catch_sigend(sigend_handler);
if (setjmp(sigend_jmp_buf))
iperf_got_sigend(test);
/* Ignore SIGPIPE to simplify error handling */
signal(SIGPIPE, SIG_IGN);
PRINTFILEFUNCLINE
switch (test->role) {
case 's':
if (test->daemon) {
int rc;
rc = daemon(0, 0);
if (rc < 0) {
i_errno = IEDAEMON;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
}
if (iperf_create_pidfile(test) < 0) {
i_errno = IEPIDFILE;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
for (;;) {
int rc;
rc = iperf_run_server(test);
test->server_last_run_rc = rc;
if (rc < 0) {
iperf_err(test, "error - %s", iperf_strerror(i_errno));
if (test->json_output) {
if (iperf_json_finish(test) < 0)
return -1;
}
iflush(test);
if (rc < -1) {
iperf_errexit(test, "exiting");
}
}
iperf_reset_test(test);
if (iperf_get_test_one_off(test) && rc != 2) {
/* Authentication failure doesn't count for 1-off test */
if (rc < 0 && i_errno == IEAUTHTEST) {
continue;
}
break;
}
}
iperf_delete_pidfile(test);
break;
case 'c':
if (iperf_create_pidfile(test) < 0) {
i_errno = IEPIDFILE;
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
}
if (iperf_run_client(test) < 0)
iperf_errexit(test, "error - %s", iperf_strerror(i_errno));
iperf_delete_pidfile(test);
break;
default:
usage();
break;
}
iperf_catch_sigend(SIG_DFL);
signal(SIGPIPE, SIG_DFL);
return 0;
}
测试完成后,调用iperf_free_test()释放所有指针指向的内存,最后释放test指针,退出程序。
iperf_free_test(test);