iperf3代码分析(2)

iperf3代码分析(2)

上一篇文章分析了iperf的源码,把整个iperf的代码都贴了上来,并做了代码分析的注释。在走读代码的过程中,发现iperf看似很简单的网络测速工具,代码中却有很多实现细节值得分析,所以延续上一篇文章,让我们再具体分析一下iperf的实现细节。

上一篇文章主要关注iperf的框架实现,包括server端和client端。程序启动后根据命令行参数分析执行server或者client的初始化流程,然后运行主处理循环,根据strem个数创建对应个数的数据收发线程,在数据收发线程中执行实际的数据发送和接收,主处理循环只处理控制通道的报文收发,以及数据平面的建立和销毁。

本篇文章让我们关注一下server和client的状态机是如何运转的,也就是如何从初始化到数据收发,再到测试结束,输出结果的。

对于server端来说,在iperf_run_server中的开始处会调用iperf_server_listen创建控制通道侦听socket。然后调用iperf_set_test_state(test, IPERF_START)设置server为启动状态IPERF_START。因为server必须在client之前启动,所以此时client还没有启动,只需要设置本端状态就可以了,因此用的接口是iperf_set_test_state。当进入while (test->state != IPERF_DONE)循环体之后,select函数返回有数据到来之后,条件(FD_ISSET(test->listener, &read_set))成立,且(test->state != CREATE_STREAMS)之后,则调用iperf_accept创建控制通道连接,让我们看看具体实现。

int
iperf_accept(struct iperf_test *test)
{
    int s;
    int ret = -1;
    signed char rbuf = ACCESS_DENIED;
    socklen_t len;
    struct sockaddr_storage addr;

    len = sizeof(addr);
    // 通过accept创建实际控制通道socket
    if ((s = accept(test->listener, (struct sockaddr *) &addr, &len)) < 0) {
        i_errno = IEACCEPT;
        return ret;
    }

    // 如果为-1表示未创建过,执行初始化流程,ctrl_sck即为控制通道实际socket
    if (test->ctrl_sck == -1) {
        /* Server free, accept new client */
        test->ctrl_sck = s;
        // set TCP_NODELAY for lower latency on control messages
        int flag = 1;
        // 控制通道使能TCP_NODELAY功能,即禁用 Nagle 算法,降低报文延迟
        if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
            i_errno = IESETNODELAY;
            goto error_handling;
        }

#if defined(HAVE_TCP_USER_TIMEOUT)
        int opt;
        // TCP_USER_TIMEOUT含义:已发送但未收到 ACK 的数据包允许等待的最大时长,超时之后recv/send返回ETIMEOUT
        if ((opt = test->settings->snd_timeout)) {
            if (setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
                i_errno = IESETUSERTIMEOUT;
                goto error_handling;
            }
        }
#endif /* HAVE_TCP_USER_TIMEOUT */

#if defined (HAVE_TCP_KEEPALIVE)
        // Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
        // 设置tcp保活,对于长时间的udp测试这个比较有用,因为如果链路断了控制通道快速感知到,避免无意义的测试。
        if (iperf_set_control_keepalive(test) < 0)
            return -1;
#endif //HAVE_TCP_KEEPALIVE

        // 从通道读取cookie信息,也就是client发给server的标识,让我们分析client的时候看看是如何发送的
        if (Nread(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) != COOKIE_SIZE) {
            /*
             * Note this error covers both the case of a system error
             * or the inability to read the correct amount of data
             * (i.e. timed out).
             */
            i_errno = IERECVCOOKIE;
            goto error_handling;
        }
        // 把ctrl_sck加入主处理循环的侦听fd_set和max_fd中。
        FD_SET(test->ctrl_sck, &test->read_set);
        if (test->ctrl_sck > test->max_fd) test->max_fd = test->ctrl_sck;

        // 设置server状态为PARAM_EXCHANGE并把这个状态发送给client。
        if (iperf_set_send_state(test, PARAM_EXCHANGE) != 0)
            goto error_handling;
        // 交换配置参数,其实是把客户端配置的参数发送给服务器端
        if (iperf_exchange_parameters(test) < 0)
            goto error_handling;
        // 设置cpu亲和性
        if (test->server_affinity != -1) {
            if (iperf_setaffinity(test, test->server_affinity) != 0)
                goto error_handling;
        }
        // 执行测试on_connect的回调函数
        if (test->on_connect)
            test->on_connect(test);
    } else {
        // 如果已经创立了连接,说明当前有client在和server进行测试,不再接收其他client的请求,发送ACCESS_DENIED错误消息,客户端显示对应错误提示。
        /*
         * Don't try to read from the socket.  It could block an ongoing test.
         * Just send ACCESS_DENIED.
         * Also, if sending failed, don't return an error, as the request is not related
         * to the ongoing test, and returning an error will terminate the test.
         */
        if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Ptcp) < 0) {
            if (test->debug)
                printf("failed to send ACCESS_DENIED to an unsolicited connection request during active test\n");
        } else {
            if (test->debug)
                printf("successfully sent ACCESS_DENIED to an unsolicited connection request during active test\n");
        }
        close(s);
    }
    return 0;
    error_handling:
        return ret;
}

上面的流程中设置完本地和对端的PARAM_EXCHANGE状态后,最重要的实现就是iperf_exchange_parameters,让我们看看它的实现。

/**
 * iperf_exchange_parameters - handles the param_Exchange part for client
 *
 */

int
iperf_exchange_parameters(struct iperf_test *test)
{
    int s;

    if (test->role == 'c') {
        // 对于客户端,发送本地参数给服务器
        if (send_parameters(test) < 0)
            return -1;

    } else {
        // 对于服务器端,获取客户端发送过来的参数
        if (get_parameters(test) < 0) {
            return -1;
        }

        // 是否支持安全授权检查
#if defined(HAVE_SSL)
        if (test_is_authorized(test) < 0){
            return -1;
        }
#endif //HAVE_SSL

        // 启动对应协议的listen回调,开始侦听数据面的连接请求
        if ((s = test->protocol->listen(test)) < 0) {
            return -1;
        }

        // 把上面侦听返回的socket加入到fd_set中,以及max_fd中,从这里可以看到控制通道和数据通道的侦听结构是一致的。
        FD_SET(s, &test->read_set);
        test->max_fd = (s > test->max_fd) ? s : test->max_fd;
        test->prot_listener = s;

        // 这里发送CREATE_STREAMS状态给客户端,通知客户端可以创建测试流了。
        // Send the control message to create streams and start the test
	if (iperf_set_send_state(test, CREATE_STREAMS) != 0)
            return -1;

    }

    return 0;
}

上面我们分析的是server服务器端,在此时客户端也有状态在运行,让我们具体分析一下。在iperf_run_client的开始处,iperf_connect负责向服务器发起控制通道连接,具体实现如下。

/* iperf_connect -- client to server connection function */
int
iperf_connect(struct iperf_test *test)
{
    int opt, n;
    socklen_t len;

    if (NULL == test)
    {
        iperf_err(NULL, "No test\n");
        return -1;
    }
    FD_ZERO(&test->read_set);
    FD_ZERO(&test->write_set);

    // 生成cookie,一般采用类似于随机数生成机制,
    make_cookie(test->cookie);

    /* Create and connect the control channel */
    // 如果是未创建则创建控制通道socket
    if (test->ctrl_sck < 0)
	// Create the control channel using an ephemeral port
        // netdial内部创建控制通道socket,并通过connect机制连接服务器
	test->ctrl_sck = netdial(test->settings->domain, Ptcp, test->bind_address, test->bind_dev, 0, test->server_hostname, test->server_port, test->settings->connect_timeout);
    if (test->ctrl_sck < 0) {
        i_errno = IECONNECT;
        return -1;
    }

    // 和服务器端类似,设置TCP_NODELAY, keepalive和TCP_USER_TIMEOUT
    // set TCP_NODELAY for lower latency on control messages
    int flag = 1;
    if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int))) {
        i_errno = IESETNODELAY;
        return -1;
    }

#if defined (HAVE_TCP_KEEPALIVE)
        // Set Control Connection TCP Keepalive (especially useful for long UDP test sessions)
        if (iperf_set_control_keepalive(test) < 0)
            return -1;
#endif //HAVE_TCP_KEEPALIVE

#if defined(HAVE_TCP_USER_TIMEOUT)
    if ((opt = test->settings->snd_timeout)) {
        if (setsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
            i_errno = IESETUSERTIMEOUT;
            return -1;
        }
    }
#endif /* HAVE_TCP_USER_TIMEOUT */

    // 发送cookie给服务器端
    if (Nwrite(test->ctrl_sck, test->cookie, COOKIE_SIZE, Ptcp) < 0) {
        i_errno = IESENDCOOKIE;
        return -1;
    }

    // 把控制通道socket加入fd_set和max_fd中
    FD_SET(test->ctrl_sck, &test->read_set);
    if (test->ctrl_sck > test->max_fd) test->max_fd = test->ctrl_sck;

    // tcp和mss设置,从socket获取,然后设置到ctrl_sck_mss中。
    len = sizeof(opt);
    if (getsockopt(test->ctrl_sck, IPPROTO_TCP, TCP_MAXSEG, &opt, &len) < 0) {
        test->ctrl_sck_mss = 0;
    }
    else {
        if (opt > 0 && opt <= MAX_UDP_BLOCKSIZE) {
            test->ctrl_sck_mss = opt;
        }
        else {
            char str[WARN_STR_LEN];
            snprintf(str, sizeof(str),
                     "Ignoring nonsense TCP MSS %d", opt);
            warning(str);

            test->ctrl_sck_mss = 0;
        }
    }

    if (test->verbose) {
	printf("Control connection MSS %d\n", test->ctrl_sck_mss);
    }

    // 对于udp测试来说,如果命令行未设置块大小,通过tcp mss来确定udp报文的大小,避免ip层分片,并且假设双向是一致的。
    /*
     * If we're doing a UDP test and the block size wasn't explicitly
     * set, then use the known MSS of the control connection to pick
     * an appropriate default.  If we weren't able to get the
     * MSS for some reason, then default to something that should
     * work on non-jumbo-frame Ethernet networks.  The goal is to
     * pick a reasonable default that is large but should get from
     * sender to receiver without any IP fragmentation.
     *
     * We assume that the control connection is routed the same as the
     * data packets (thus has the same PMTU).  Also in the case of
     * --reverse tests, we assume that the MTU is the same in both
     * directions.  Note that even if the algorithm guesses wrong,
     * the user always has the option to override.
     */
    if (test->protocol->id == Pudp) {
	if (test->settings->blksize == 0) {
	    if (test->ctrl_sck_mss) {
		test->settings->blksize = test->ctrl_sck_mss;
	    }
	    else {
		test->settings->blksize = DEFAULT_UDP_BLKSIZE;
	    }
	    if (test->verbose) {
		printf("Setting UDP block size to %d\n", test->settings->blksize);
	    }
	}
	/* Initialize GSO parameters when --gsro is used */
	if (test->settings->gso) {
	    test->settings->gso_dg_size = test->settings->blksize;
	    /* use the multiple of datagram size for the best efficiency. */
	    if (test->settings->gso_dg_size > 0) {
                n = test->settings->gso_bf_size / test->settings->gso_dg_size;
                if (n > GSO_MAX_DG_IN_BF) {
                    n = GSO_MAX_DG_IN_BF;
                }
		test->settings->gso_bf_size = n * test->settings->gso_dg_size;
	    } else {
		/* If gso_dg_size is 0 (unlimited bandwidth), use default UDP datagram size */
		test->settings->gso_dg_size = DEFAULT_UDP_BLKSIZE;
	    }
	}

	/*
	 * Regardless of whether explicitly or implicitly set, if the
	 * block size is larger than the MSS, print a warning.
	 */
	if (test->ctrl_sck_mss > 0 &&
	    test->settings->blksize > test->ctrl_sck_mss) {
	    char str[WARN_STR_LEN];
	    snprintf(str, sizeof(str),
		     "UDP block size %d exceeds TCP MSS %d, may result in fragmentation / drops", test->settings->blksize, test->ctrl_sck_mss);
	    warning(str);
	}
    }

    return 0;
}

完成上述流程后,客户端就通过select监听控制通道,直到FD_ISSET(test->ctrl_sck, &read_set),此时会执行iperf_handle_message_client处理服务器发过来的状态,驱动客户端状态机运行,其具体实现如下。

int
iperf_handle_message_client(struct iperf_test *test)
{
    int rval;
    int32_t err;

    if (NULL == test)
    {
        iperf_err(NULL, "No test\n");
	i_errno = IEINITTEST;
        return -1;
    }

    if (test->debug_level >= DEBUG_LEVEL_INFO) {
        iperf_printf(test, "Reading new State from the Server - current state is %d-%s\n", test->state, state_to_text(test->state));
    }

    /*!!! Why is this read() and not Nread()? */
    if ((rval = read(test->ctrl_sck, (char*) &test->state, sizeof(signed char))) <= 0) {
        if (rval == 0) {
            i_errno = IECTRLCLOSE;
            return -1;
        } else {
            i_errno = IERECVMESSAGE;
            return -1;
        }
    }

    if (test->debug_level >= DEBUG_LEVEL_INFO) {
        iperf_printf(test, "State change: client received and changed State to %d-%s\n", test->state, state_to_text(test->state));
    }

    switch (test->state) {
        case PARAM_EXCHANGE:
            // 参数交换状态,执行iperf_exchange_parameters将参数发送给服务器端
            if (iperf_exchange_parameters(test) < 0)
                return -1;
            if (test->on_connect)
                test->on_connect(test);
            break;
        case CREATE_STREAMS:
            // 服务器获取参数后,发送过来的CREATE_STREAMS状态,创建测试流
            if (test->mode == BIDIRECTIONAL)
            {
                if (iperf_create_streams(test, 1) < 0)
                    return -1;
                if (iperf_create_streams(test, 0) < 0)
                    return -1;
            }
            else if (iperf_create_streams(test, test->mode) < 0)
                return -1;
            break;
        // 客户端收到测试开始状态,开始初始化测试结构,创建定时器
        case TEST_START:
            if (iperf_init_test(test) < 0)
                return -1;
            if (create_client_timers(test) < 0)
                return -1;
            if (create_client_omit_timer(test) < 0)
                return -1;
	    if (test->mode)
		if (iperf_create_send_timers(test) < 0)
		    return -1;
            break;
        // 测试开始
        case TEST_RUNNING:
            break;
        case EXCHANGE_RESULTS:
            if (iperf_exchange_results(test) < 0)
                return -1;
            break;
        case DISPLAY_RESULTS:
            if (test->on_test_finish)
                test->on_test_finish(test);
            iperf_client_end(test);
            break;
        case IPERF_DONE:
            break;
        case SERVER_TERMINATE:
            i_errno = IESERVERTERM;

	    /*
	     * Temporarily be in DISPLAY_RESULTS phase so we can get
	     * ending summary statistics.
	     */
	    signed char oldstate = test->state;
	    cpu_util(test->cpu_util);
	    test->state = DISPLAY_RESULTS;
	    test->reporter_callback(test);
	    test->state = oldstate;
            return -1;
        case ACCESS_DENIED:
            i_errno = IEACCESSDENIED;
            return -1;
        case SERVER_ERROR:
            if (Nread(test->ctrl_sck, (char*) &err, sizeof(err), Ptcp) < 0) {
                i_errno = IECTRLREAD;
                return -1;
            }
	    i_errno = ntohl(err);
            if (Nread(test->ctrl_sck, (char*) &err, sizeof(err), Ptcp) < 0) {
                i_errno = IECTRLREAD;
                return -1;
            }
            errno = ntohl(err);
            if (errno > 0) {    
                iperf_err(test, "SERVER ERROR - %s, errno: %s", iperf_strerror(i_errno), strerror(errno));
            } else {
                iperf_err(test, "SERVER ERROR - %s", iperf_strerror(i_errno));
            }
            return -1;
        default:
            i_errno = IEMESSAGE;
            return -1;
    }

    return 0;
}

在客户端的iperf_create_streams实现中,和分别创建服务器的流和客户端的流,让我们看看具体实现。

int
iperf_create_streams(struct iperf_test *test, int sender)
{
    if (NULL == test)
    {
        iperf_err(NULL, "No test\n");
        return -1;
    }
    int i, s;
#if defined(HAVE_TCP_CONGESTION)
    int saved_errno;
#endif /* HAVE_TCP_CONGESTION */
    struct iperf_stream *sp;

    int orig_bind_port = test->bind_port;
    for (i = 0; i < test->num_streams; ++i) {
        // 如果设置的绑定端口,则所有流都从绑定端口开始,否则使用协议栈自动分配的端口
        test->bind_port = orig_bind_port;
	if (orig_bind_port) {
	    test->bind_port += i;
            // If Bidir make sure send and receive ports are different
            if (!sender && test->mode == BIDIRECTIONAL)
                test->bind_port += test->num_streams;
        }
        // 调用具体协议层的connect连接服务器
        s = test->protocol->connect(test);
        test->bind_port = orig_bind_port;
        if (s < 0)
            return -1;

/配置tcp的拥塞控制/
#if defined(HAVE_TCP_CONGESTION)
	if (test->protocol->id == Ptcp) {
	    if (test->congestion) {
		if (setsockopt(s, IPPROTO_TCP, TCP_CONGESTION, test->congestion, strlen(test->congestion)) < 0) {
		    saved_errno = errno;
		    close(s);
		    errno = saved_errno;
		    i_errno = IESETCONGESTION;
		    return -1;
		}
	    }
	    {
		socklen_t len = TCP_CA_NAME_MAX;
		char ca[TCP_CA_NAME_MAX + 1];
                int rc;
		rc = getsockopt(s, IPPROTO_TCP, TCP_CONGESTION, ca, &len);
                if (rc < 0 && test->congestion) {
		    saved_errno = errno;
		    close(s);
		    errno = saved_errno;
		    i_errno = IESETCONGESTION;
		    return -1;
		}
	        if (test->congestion_used) {
	          if (test->debug)
	            printf("Overriding existing congestion algorithm: %s\n", test->congestion_used);
	          free(test->congestion_used);
	        }
                // Set actual used congestion alg, or set to unknown if could not get it
                if (rc < 0)
                    test->congestion_used = strdup("unknown");
                else
                    test->congestion_used = strdup(ca);
		if (test->debug) {
		    printf("Congestion algorithm is %s\n", test->congestion_used);
		}
	    }
	}
#endif /* HAVE_TCP_CONGESTION */

        // iperf_new_stream是实际的创建流实现
        sp = iperf_new_stream(test, s, sender);
        if (!sp)
            return -1;

        /* Perform the new stream callback */
        if (test->on_new_stream)
            test->on_new_stream(sp);
    }

    return 0;
}

client端完成上述处理后,server端的主循环判断if (test->state == CREATE_STREAMS)的条件就会成立,因为上面iperf_exchange_parameters在获取完参数后已经设置过了,同时当客户端执行connect后,服务器的条件if (FD_ISSET(test->prot_listener, &read_set))也会成立,因此就会执行服务器的s = test->protocol->accept(test)建立实际的数据通道。然后服务器通过iperf_common_sockopts设置socket选项,根据发送和接收需要的stream个数,调用iperf_new_stream创建对应的流结构。完成上述创建后,服务器端通过iperf_set_send_state(test, TEST_START)设置状态为TEST_START并发送给客户端。客户端在iperf_handle_message_client中完成测试的初始化,而服务器端在iperf_run_server的循环中完成初始化,之后通过iperf_set_send_state(test, TEST_RUNNING)设置状态为TEST_RUNNING并发送给客户端。之后服务器端通过strem的个数创建对应个数的数据处理线程,开始发送和接收数据,而客户端在iperf_handle_message_client处理该状态,什么操作都不做,而是在iperf_run_client中处理TEST_RUNNING状态,也是通过strem的个数创建对应个数的数据处理线程,开始发送和接收数据。因为本地处理速度一般比网络快,所以应该是服务器的数据处理线程先创建,客户端的后创建。这样客户端作为发送端,服务器作为接收端不会有什么问题,因为接收端的数据处理线程早于发送端。这里有个疑惑,如果配置了-R参数,那么服务器线程如果先创建,如何保证服务器发送报文的时候客户端线程肯定已经创建了呢?

下面我们看看服务器和客户端的数据处理线程实现。上一篇文章其实已经说了,服务器数据线程的实现iperf_server_worker_run和客户端数据线程的实现iperf_client_worker_run其实都是一样的,关键的代码部分如下。

    while (! (test->done) && ! (sp->done)) {
        if (sp->sender) {
            // 发送端执行发送流程
            if (iperf_send_mt(sp) < 0) {
                goto cleanup_and_fail;
            }
        }
        else {
            // 否则一定是接收端则执行接收流程
            if (iperf_recv_mt(sp) < 0) {
                goto cleanup_and_fail;
            }
        }
    }

iperf_send_mt和iperf_recv_mt内部分别会调用strem中的snd和rcv回调来发送和接收数据,需要注意的是iperf_send_mt中可以通过multisend支持报文突发,具体是在用-b配置速率的时候通过在速率后面增加/x设置突发,x是突发的个数。snd和rcv和具体的协议有关,目前tcp, udp和sctp都实现了各自的send和recv功能,在test的stream初始化的时候赋值给snd和rcv(iperf_new_stream)。

经过上面的分析,我们涉及到了待测量的传输协议需要实现的回调函数,分别是listen, accept,connect, send和recv。让我们看看tcp和udp的具体实现,sctp的实现可以留给读者自己分析。其实上面这些回调,从名字看和tcp的网络模型就是对应的,listen和accept用于服务器端侦听和接收连接的api,connect是客户端发起三次握手的api, send和recv是报文发送和接收的流程。对于udp来说,创建socket之后,因为没有三次握手的流程,所以只需要用sendto, sendmsg发送报文,用recvfrom, recvmsg接收报文,没有listen和accept这两个流程, 而connect是支持的。在udp的socket上调用connect只是将路由绑定到socket上,下次就可以用send/recv来发送和接收报文了。

接下来我们分别分析tcp和udp的协议实现的上述回调接口,首先看tcp的实现。

/* iperf_tcp_recv
 *
 * receives the data for TCP
 */
int
iperf_tcp_recv(struct iperf_stream *sp)
{
    int r;
    int sock_opt;

    // 支持配置为MSG_TRUNC模式,返回实际接收报文的大小
#if defined(HAVE_MSG_TRUNC)
    sock_opt = sp->test->settings->skip_rx_copy ? MSG_TRUNC : 0;
#else
    sock_opt = 0;
#endif /* HAVE_MSG_TRUNC */

    // 使用no select形式的 recv api接口接收数据
    r = Nrecv_no_select(sp->socket, sp->buffer, sp->settings->blksize, Ptcp, sock_opt);


    if (r < 0)
        return r;

    /* Only count bytes received while we're in the correct state. */
    /只有测试运行中才更新接收统计/
    if (sp->test->state == TEST_RUNNING) {
	      sp->result->bytes_received += r;
	      sp->result->bytes_received_this_interval += r;
    }
    else {
	      if (sp->test->debug)
	          printf("Late receive, state = %d-%s\n", sp->test->state, state_to_text(sp->test->state));
    }

    return r;
}


/* iperf_tcp_send
 *
 * sends the data for TCP
 */
int
iperf_tcp_send(struct iperf_stream *sp)
{
    int r;

    if (!sp->pending_size)
	      sp->pending_size = sp->settings->blksize;

    // 支持零拷贝方式发送报文,也就是不经过用户态的内容拷贝,内核直接将buffer数据发送出去,Nwrite是普通发送方式
    if (sp->test->zerocopy)
	      r = Nsendfile(sp->buffer_fd, sp->socket, sp->buffer, sp->pending_size);
    else
	      r = Nwrite(sp->socket, sp->buffer, sp->pending_size, Ptcp);

    if (r < 0)
        return r;

    // 这里不需要判断当前运行状态么?
    sp->pending_size -= r;
    sp->result->bytes_sent += r;
    sp->result->bytes_sent_this_interval += r;

    if (sp->test->debug_level >=  DEBUG_LEVEL_DEBUG)
	      printf("sent %d bytes of %d, pending %d, total %" PRIu64 "\n",
	          r, sp->settings->blksize, sp->pending_size, sp->result->bytes_sent);

    return r;
}


/* iperf_tcp_accept
 *
 * accept a new TCP stream connection
 */
int
iperf_tcp_accept(struct iperf_test * test)
{
    int     s;
    signed char rbuf = ACCESS_DENIED;
    char    cookie[COOKIE_SIZE] = {0};
    socklen_t len;
    struct sockaddr_storage addr;

    len = sizeof(addr);
    if ((s = accept(test->listener, (struct sockaddr *) &addr, &len)) < 0) {
        i_errno = IESTREAMCONNECT;
        return -1;
    }
#if defined(HAVE_SO_MAX_PACING_RATE)
    /* If fq socket pacing is specified, enable it. */
    // 是否配置使用TCP的SO_MAX_PACING_RATE选项,也就是通过FQ限制发送速率
    if (test->settings->fqrate) {
	/* Convert bits per second to bytes per second */
	uint64_t fqrate = test->settings->fqrate / 8;
	if (fqrate > 0) {
	    if (test->debug) {
		printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
	    }
	    if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
		warning("Unable to set socket pacing");
	    }
	}
    }
#endif /* HAVE_SO_MAX_PACING_RATE */

    // 读取cookie消息
    if (Nread(s, cookie, COOKIE_SIZE, Ptcp) < 0) {
        i_errno = IERECVCOOKIE;
        close(s);
        return -1;
    }

    // 比较cookie是否匹配,如果不匹配则返回错误ACCESS_DENIED
    if (strncmp(test->cookie, cookie, COOKIE_SIZE) != 0) {
        if (Nwrite(s, (char*) &rbuf, sizeof(rbuf), Ptcp) < 0) {
            iperf_err(test, "failed to send access denied from busy server to new connecting client, errno = %d\n", errno);
        }
        close(s);
    }

    return s;
}


/* iperf_tcp_listen
 *
 * start up a listener for TCP stream connections
 */
int
iperf_tcp_listen(struct iperf_test *test)
{
    int s, opt;
    socklen_t optlen;
    int saved_errno;
    int rcvbuf_actual, sndbuf_actual;

    s = test->listener;

    // 如果支持配置tcp的一些选项,则需要重新创建socket
    /*
     * If certain parameters are specified (such as socket buffer
     * size), then throw away the listening socket (the one for which
     * we just accepted the control connection) and recreate it with
     * those parameters.  That way, when new data connections are
     * set, they'll have all the correct parameters in place.
     *
     * It's not clear whether this is a requirement or a convenience.
     */
    if (test->no_delay || test->mptcp || test->settings->mss || test->settings->socket_bufsize) {
	struct addrinfo hints, *res;
	char portstr[6];
	int proto = 0;

        // 关闭之前已经listen的socket,并清除fd_set
        FD_CLR(s, &test->read_set);
        close(s);

        snprintf(portstr, 6, "%d", test->server_port);
        memset(&hints, 0, sizeof(hints));

        // 如果地址族未指定,且未指定绑定地址,则使用AF_INET6地址族
	/*
	 * If binding to the wildcard address with no explicit address
	 * family specified, then force us to get an AF_INET6 socket.
	 * More details in the comments in netannounce().
	 */
	if (test->settings->domain == AF_UNSPEC && !test->bind_address) {
	    hints.ai_family = AF_INET6;
	}
	else {
	    hints.ai_family = test->settings->domain;
	}
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;
        if ((gerror = getaddrinfo(test->bind_address, portstr, &hints, &res)) != 0) {
            i_errno = IESTREAMLISTEN;
            return -1;
        }

        // 支持mptcp协议
#if defined(HAVE_IPPROTO_MPTCP)
        if (test->mptcp)
	    proto = IPPROTO_MPTCP;
#endif

        // 重新创建socket
        if ((s = socket(res->ai_family, SOCK_STREAM, proto)) < 0) {
	    freeaddrinfo(res);
            i_errno = IESTREAMLISTEN;
            return -1;
        }

        // 下面就是配置具体的socket选项
        if (test->no_delay) {
            opt = 1;
            if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
		close(s);
		freeaddrinfo(res);
		errno = saved_errno;
                i_errno = IESETNODELAY;
                return -1;
            }
        }
        // XXX: Setting MSS is very buggy!
        if ((opt = test->settings->mss)) {
            if (setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
		close(s);
		freeaddrinfo(res);
		errno = saved_errno;
                i_errno = IESETMSS;
                return -1;
            }
        }
        if ((opt = test->settings->socket_bufsize)) {
            if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
		close(s);
		freeaddrinfo(res);
		errno = saved_errno;
                i_errno = IESETBUF;
                return -1;
            }
            if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
		close(s);
		freeaddrinfo(res);
		errno = saved_errno;
                i_errno = IESETBUF;
                return -1;
            }
        }
    // 这个缩进看起来很别扭,可能之前有if条件判断后来删除了。
    {
	unsigned int rate = test->settings->rate / 8;
	if (rate > 0) {
	    if (test->debug) {
		printf("Setting application pacing to %u\n", rate);
	    }
	}
    }
        opt = 1;
        if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
            close(s);
	    freeaddrinfo(res);
	    errno = saved_errno;
            i_errno = IEREUSEADDR;
            return -1;
        }

	/*
	 * If we got an IPv6 socket, figure out if it should accept IPv4
	 * connections as well.  See documentation in netannounce() for
	 * more details.
	 */
        // 是否只支持IPv6,还是创建IPv6支持连接ipv4 socket
#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__)
	if (res->ai_family == AF_INET6 && (test->settings->domain == AF_UNSPEC || test->settings->domain == AF_INET)) {
	    if (test->settings->domain == AF_UNSPEC)
		opt = 0;
	    else
		opt = 1;
	    if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
			   (char *) &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
		close(s);
		freeaddrinfo(res);
		errno = saved_errno;
		i_errno = IEV6ONLY;
		return -1;
	    }
	}
#endif /* IPV6_V6ONLY */

        // 绑定地址,地址从刚才的getaddrinfo获取到的
        if (bind(s, (struct sockaddr *) res->ai_addr, res->ai_addrlen) < 0) {
	    saved_errno = errno;
            close(s);
	    freeaddrinfo(res);
	    errno = saved_errno;
            i_errno = IESTREAMLISTEN;
            return -1;
        }

        freeaddrinfo(res);
        // 开始侦听
        if (listen(s, INT_MAX) < 0) {
            i_errno = IESTREAMLISTEN;
            close(s);
            return -1;
        }
        // 更新listener
        test->listener = s;
    }

    // 再次校验socket send buffer和recv buffuer,如果实际的值小于配置的值返回错误
    /* Read back and verify the sender socket buffer size */
    optlen = sizeof(sndbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
	saved_errno = errno;
	close(s);
	errno = saved_errno;
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
	i_errno = IESETBUF2;
    close(s);
	return -1;
    }

    /* Read back and verify the receiver socket buffer size */
    optlen = sizeof(rcvbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
	saved_errno = errno;
	close(s);
	errno = saved_errno;
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
	i_errno = IESETBUF2;
        close(s);
	return -1;
    }

    /* Set common socket options */
    if (iperf_common_sockopts(test, s) < 0) {
        close(s);
        return -1;
    }

    if (test->json_output) {
	cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
	cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
	cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
    }

    return s;
}


/* iperf_tcp_connect
 *
 * connect to a TCP stream listener
 * This function is roughly similar to netdial(), and may indeed have
 * been derived from it at some point, but it sets many TCP-specific
 * options between socket creation and connection.
 */
int
iperf_tcp_connect(struct iperf_test *test)
{
    struct addrinfo *server_res;
    int s, opt;
    socklen_t optlen;
    int saved_errno;
    int rcvbuf_actual, sndbuf_actual;
    int proto = 0;

#if defined(HAVE_IPPROTO_MPTCP)
    if (test->mptcp)
        proto = IPPROTO_MPTCP;
#endif

    // 客户端socket创建
    s = create_socket(test->settings->domain, SOCK_STREAM, proto, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, &server_res);
    if (s < 0) {
	i_errno = IESTREAMCONNECT;
	return -1;
    }

    // 设置tcp选项
    /* Set socket options */
    if (test->no_delay) {
        opt = 1;
        if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETNODELAY;
            return -1;
        }
    }
    if ((opt = test->settings->mss)) {
        if (setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETMSS;
            return -1;
        }
    }
    if ((opt = test->settings->socket_bufsize)) {
        if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETBUF;
            return -1;
        }
        if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETBUF;
            return -1;
        }
    }
#if defined(HAVE_TCP_USER_TIMEOUT)
    if ((opt = test->settings->snd_timeout)) {
        if (setsockopt(s, IPPROTO_TCP, TCP_USER_TIMEOUT, &opt, sizeof(opt)) < 0) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETUSERTIMEOUT;
            return -1;
        }
    }
#endif /* HAVE_TCP_USER_TIMEOUT */

    // 再次校验socket send buffer和recv buffuer,如果实际的值小于配置的值返回错误
    /* Read back and verify the sender socket buffer size */
    optlen = sizeof(sndbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
	saved_errno = errno;
	close(s);
	freeaddrinfo(server_res);
	errno = saved_errno;
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
        close(s);
        freeaddrinfo(server_res);
	i_errno = IESETBUF2;
	return -1;
    }

    /* Read back and verify the receiver socket buffer size */
    optlen = sizeof(rcvbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
	saved_errno = errno;
	close(s);
	freeaddrinfo(server_res);
	errno = saved_errno;
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
        close(s);
        freeaddrinfo(server_res);
	i_errno = IESETBUF2;
	return -1;
    }

    if (test->json_output) {
    cJSON *sock_bufsize_item = cJSON_GetObjectItem(test->json_start, "sock_bufsize");
    if (sock_bufsize_item == NULL) {
    cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
    }

    cJSON *sndbuf_actual_item = cJSON_GetObjectItem(test->json_start, "sndbuf_actual");
    if (sndbuf_actual_item == NULL) {
	cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
    }
        
    cJSON *rcvbuf_actual_item = cJSON_GetObjectItem(test->json_start, "rcvbuf_actual");
    if (rcvbuf_actual_item == NULL) {
	cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
    }
    }

    // 支持配置ipv6的flow label
#if defined(HAVE_FLOWLABEL)
    if (test->settings->flowlabel) {
        if (server_res->ai_addr->sa_family != AF_INET6) {
	    saved_errno = errno;
	    close(s);
	    freeaddrinfo(server_res);
	    errno = saved_errno;
            i_errno = IESETFLOW;
            return -1;
	} else {
	    struct sockaddr_in6* sa6P = (struct sockaddr_in6*) server_res->ai_addr;
            char freq_buf[sizeof(struct in6_flowlabel_req)];
            struct in6_flowlabel_req *freq = (struct in6_flowlabel_req *)freq_buf;
            int freq_len = sizeof(*freq);

            memset(freq, 0, sizeof(*freq));
            freq->flr_label = htonl(test->settings->flowlabel & IPV6_FLOWINFO_FLOWLABEL);
            freq->flr_action = IPV6_FL_A_GET;
            freq->flr_flags = IPV6_FL_F_CREATE;
            freq->flr_share = IPV6_FL_S_ANY;
            memcpy(&freq->flr_dst, &sa6P->sin6_addr, 16);

            if (setsockopt(s, IPPROTO_IPV6, IPV6_FLOWLABEL_MGR, freq, freq_len) < 0) {
		saved_errno = errno;
                close(s);
                freeaddrinfo(server_res);
		errno = saved_errno;
                i_errno = IESETFLOW;
                return -1;
            }
            sa6P->sin6_flowinfo = freq->flr_label;

            opt = 1;
            if (setsockopt(s, IPPROTO_IPV6, IPV6_FLOWINFO_SEND, &opt, sizeof(opt)) < 0) {
		saved_errno = errno;
                close(s);
                freeaddrinfo(server_res);
		errno = saved_errno;
                i_errno = IESETFLOW;
                return -1;
            }
	}
    }
#endif /* HAVE_FLOWLABEL */

    // 支持设置TCP的SO_MAX_PACING_RATE,也就是发送速率
#if defined(HAVE_SO_MAX_PACING_RATE)
    /* If socket pacing is specified try to enable it. */
    if (test->settings->fqrate) {
	/* Convert bits per second to bytes per second */
	uint64_t fqrate = test->settings->fqrate / 8;
	if (fqrate > 0) {
	    if (test->debug) {
		printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
	    }
	    if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
		warning("Unable to set socket pacing");
	    }
	}
    }
#endif /* HAVE_SO_MAX_PACING_RATE */
    {
	unsigned int rate = test->settings->rate / 8;
	if (rate > 0) {
	    if (test->debug) {
		printf("Setting application pacing to %u\n", rate);
	    }
	}
    }

    /* Set common socket options */
    if (iperf_common_sockopts(test, s) < 0) {
        saved_errno = errno;
	close(s);
	freeaddrinfo(server_res);
	errno = saved_errno;
        return -1;
    }

    // 使用connect连接服务器端
    if (timeout_connect(s, (struct sockaddr *) server_res->ai_addr, server_res->ai_addrlen,
                        DEFAULT_NO_MSG_RCVD_TIMEOUT) < 0 && errno != EINPROGRESS) {
	saved_errno = errno;
	close(s);
	freeaddrinfo(server_res);
	errno = saved_errno;
        i_errno = IESTREAMCONNECT;
        return -1;
    }

    freeaddrinfo(server_res);
    
    // 发送cookie给服务器端进行校验
    /* Send cookie for verification */
    if (Nwrite(s, test->cookie, COOKIE_SIZE, Ptcp) < 0) {
	saved_errno = errno;
	close(s);
	errno = saved_errno;
        i_errno = IESENDCOOKIE;
        return -1;
    }

    return s;
}

接下来我们分析udp的实现,就像之前分析的那样,udp是没有listen, accept的实现的,那么是如何实现类似效果的回调,如何适配到iperf的整体框架中的,也就是使用同一套的connect, send和recv实现流程的呢,让我们看具体的代码吧。

/* iperf_udp_recv
 *
 * receives the data for UDP
 */
int
iperf_udp_recv(struct iperf_stream *sp)
{
    uint32_t  sec, usec;
    uint64_t  pcount;
    int       r;
    int       size = sp->settings->blksize;
    int       first_packet = 0;
    double    transit = 0, d = 0;
    struct iperf_time sent_time, arrival_time, temp_time;
    struct iperf_test *test = sp->test;
    int sock_opt = 0;
    int       dgram_sz;
    int       buf_sz;
    char      *dgram_buf;
    char      *dgram_buf_end;
    const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */

#if defined(HAVE_MSG_TRUNC)
    // UDP recv() with MSG_TRUNC reads only the size bytes, but return the length of the full packet
    if (sp->test->settings->skip_rx_copy) {
        sock_opt = MSG_TRUNC;
        size = sizeof(sec) + sizeof(usec) + sizeof(pcount);
    }
#endif /* HAVE_MSG_TRUNC */

    // 支持gro和非gro两种方式,dgram_sz是配置的块大小,buf_sz是实际返回的大小
    /* Configure loop parameters based on GRO availability */
    if (sp->test->settings->gro) {
	size = sp->test->settings->gro_bf_size;
	r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz);
	/* Use negotiated block size for GRO segment stride to ensure correct parsing. */
	dgram_sz = sp->settings->blksize;
	buf_sz = r;
    } else {
	/* GRO disabled or unavailable - use normal UDP receive and single packet size */
	r = Nrecv_no_select(sp->socket, sp->buffer, size, Pudp, sock_opt);
	dgram_sz = sp->settings->blksize;
	buf_sz = r;
    }

    /*
     * If we got an error in the read, or if we didn't read anything
     * because the underlying read(2) got a EAGAIN, then skip packet
     * processing.
     */
    if (r <= 0)
        return r;

    // Check dgram_sz is greater than 0
    if (dgram_sz <= 0){
        return -1;
    }

    // 只有测试运行中才需要更新统计
    /* Only count bytes received while we're in the correct state. */
    if (test->state == TEST_RUNNING) {

	/*
	 * For jitter computation below, it's important to know if this
	 * packet is the first packet received.
	 */
        // jitter计算需要直到收到的是否是首包
	if (sp->result->bytes_received == 0) {
	    first_packet = 1;
	}

        // 更新接收统计
	sp->result->bytes_received += r;
	sp->result->bytes_received_this_interval += r;

	if (sp->test->debug)
	    printf("received %d bytes of %d, total %" PRIu64 "\n", r, size, sp->result->bytes_received);

        // 下面的流程计算udp报文是否乱序,计算jitter(不需要两端时钟同步),具体流程待分析
	/* Unified loop: processes single packet when GRO off, multiple when GRO on */
	dgram_buf = sp->buffer;
	dgram_buf_end = sp->buffer + buf_sz;

	while (buf_sz >= dgram_sz && dgram_buf + dgram_sz <= dgram_buf_end) {

	    /* Ensure we have enough bytes for the packet header */
	    if (buf_sz < min_pkt_size)
		break;

	    /* Extract packet headers */
	    if (sp->test->udp_counters_64bit) {
		/* Verify we have enough space for 64-bit counter */
		if (buf_sz < sizeof(uint32_t) * 2 + sizeof(uint64_t))
		    break;
		memcpy(&sec, dgram_buf, sizeof(sec));
		memcpy(&usec, dgram_buf+4, sizeof(usec));
		memcpy(&pcount, dgram_buf+8, sizeof(pcount));
		sec = ntohl(sec);
		usec = ntohl(usec);
		pcount = be64toh(pcount);
		sent_time.secs = sec;
		sent_time.usecs = usec;
	    } else {
		uint32_t pc;
		memcpy(&sec, dgram_buf, sizeof(sec));
		memcpy(&usec, dgram_buf+4, sizeof(usec));
		memcpy(&pc, dgram_buf+8, sizeof(pc));
		sec = ntohl(sec);
		usec = ntohl(usec);
		pcount = ntohl(pc);
		sent_time.secs = sec;
		sent_time.usecs = usec;
	    }

            /*
             * Try to handle out of order packets.  The way we do this
             * uses a constant amount of storage but might not be
             * correct in all cases.  In particular we seem to have the
             * assumption that packets can't be duplicated in the network,
             * because duplicate packets will possibly cause some problems here.
             *
             * First figure out if the sequence numbers are going forward.
             * Note that pcount is the sequence number read from the packet,
             * and sp->packet_count is the highest sequence number seen so
             * far (so we're expecting to see the packet with sequence number
             * sp->packet_count + 1 arrive next).
             */
	    if (pcount >= sp->packet_count + 1) {

                /* Forward, but is there a gap in sequence numbers? */
		if (pcount > sp->packet_count + 1) {
                    /* There's a gap so count that as a loss. */
		    sp->cnt_error += (pcount - 1) - sp->packet_count;
                    if (test->debug_level >= DEBUG_LEVEL_INFO)
                        fprintf(stderr, "LOST %" PRIu64 " PACKETS - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", (pcount - sp->packet_count + 1), pcount, sp->packet_count + 1, sp->socket);
		}
                /* Update the highest sequence number seen so far. */
		sp->packet_count = pcount;
	    } else {

                /*
                 * Sequence number went backward (or was stationary?!?).
                 * This counts as an out-of-order packet.
                 */
		sp->outoforder_packets++;

                /*
                 * If we have lost packets, then the fact that we are now
                 * seeing an out-of-order packet offsets a prior sequence
                 * number gap that was counted as a loss.  So we can take
                 * away a loss.
                 */
		if (sp->cnt_error > 0)
		    sp->cnt_error--;

                /* Log the out-of-order packet */
                if (test->debug_level >= DEBUG_LEVEL_INFO)
                    fprintf(stderr, "OUT OF ORDER - received packet %" PRIu64 " but expected sequence %" PRIu64 " on stream %d\n", pcount, sp->packet_count + 1, sp->socket);
	    }

            /*
             * jitter measurement
             *
             * This computation is based on RFC 1889 (specifically
             * sections 6.3.1 and A.8).
             *
             * Note that synchronized clocks are not required since
             * the source packet delta times are known.  Also this
             * computation does not require knowing the round-trip
             * time.
             */
	    iperf_time_now(&arrival_time);
	    iperf_time_diff(&arrival_time, &sent_time, &temp_time);
	    transit = iperf_time_in_secs(&temp_time);

            /* Hack to handle the first packet by initializing prev_transit. */
	    if (first_packet)
		sp->prev_transit = transit;
	    d = transit - sp->prev_transit;
	    if (d < 0)
		d = -d;
	    sp->prev_transit = transit;
	    sp->jitter += (d - sp->jitter) / 16.0;
	    first_packet = 0;

	    dgram_buf += dgram_sz;
	    buf_sz -= dgram_sz;
	}
    }
    else {
	if (test->debug_level >= DEBUG_LEVEL_INFO)
	    printf("Late receive, state = %d\n", test->state);
    }

    return r;
}


/* iperf_udp_send
 *
 * sends the data for UDP
 */
int
iperf_udp_send(struct iperf_stream *sp)
{
    int r;
    int       size = sp->settings->blksize;
    struct iperf_time before;
    int       dgram_sz;
    int       buf_sz;
    int       cnt = 0;
    char      *dgram_buf;
    char      *dgram_buf_end;
    const int min_pkt_size = sizeof(uint32_t) * 3; /* sec + usec + pcount (32-bit) */

    // 支持gro和非gro场景下的dgram_sz 计算
    /* Configure loop parameters based on GSO availability */
    if (sp->test->settings->gso) {
	dgram_sz = sp->test->settings->gso_dg_size;
	buf_sz = sp->test->settings->gso_bf_size;
	/* Validate GSO parameters */
	if (dgram_sz <= 0 || dgram_sz < min_pkt_size || dgram_sz > buf_sz) {
	    if (sp->test->debug_level >= DEBUG_LEVEL_INFO)
		printf("Invalid GSO dgram_sz %d for buf_sz %d, disabling GSO\n", dgram_sz, buf_sz);
	    dgram_sz = buf_sz = size;
	    sp->test->settings->gso = 0;  /* Disable GSO for safety */
	}
    } else {
	/* GSO disabled or unavailable - single packet */
	dgram_sz = buf_sz = size;
    }

    // 发送流程处理,注意udp的净荷中包含了时间戳和序列号,以便接收端计算是否乱序,计算jitter抖动。
    dgram_buf = sp->buffer;
    dgram_buf_end = sp->buffer + buf_sz;

    /* Unified loop: processes single packet when GSO off, multiple when GSO on */
    while (buf_sz > 0 && dgram_buf + dgram_sz <= dgram_buf_end) {
	cnt++;

	if (sp->test->debug_level  >= DEBUG_LEVEL_DEBUG)
	    printf("%d (%d) remaining %d\n", cnt, dgram_sz, buf_sz);

	/* Prevent buffer underflow */
	if (buf_sz < dgram_sz) {
	    if (sp->test->debug_level >= DEBUG_LEVEL_INFO)
		printf("Buffer underflow protection: buf_sz %d < dgram_sz %d\n", buf_sz, dgram_sz);
	    break;
	}

	iperf_time_now(&before);
	++sp->packet_count;

	if (sp->test->udp_counters_64bit) {
	    uint32_t  sec, usec;
	    uint64_t  pcount;

	    sec = htonl(before.secs);
	    usec = htonl(before.usecs);
	    pcount = htobe64(sp->packet_count);

	    memcpy(dgram_buf, &sec, sizeof(sec));
	    memcpy(dgram_buf+4, &usec, sizeof(usec));
	    memcpy(dgram_buf+8, &pcount, sizeof(pcount));
	} else {
	    uint32_t  sec, usec, pcount;

	    sec = htonl(before.secs);
	    usec = htonl(before.usecs);
	    pcount = htonl(sp->packet_count);

	    memcpy(dgram_buf, &sec, sizeof(sec));
	    memcpy(dgram_buf+4, &usec, sizeof(usec));
	    memcpy(dgram_buf+8, &pcount, sizeof(pcount));
	}

	dgram_buf += dgram_sz;
	buf_sz -= dgram_sz;
    }

    /* Warn if we didn't process all the buffer due to size mismatch */
    if (buf_sz > 0 && sp->test->debug_level >= DEBUG_LEVEL_INFO) {
	printf("GSO: %d bytes remaining unprocessed\n", buf_sz);
    }

    // 支持gso和非gso两种发送方式
    if (sp->test->settings->gso) {
        size = sp->test->settings->gso_bf_size;
        r = Nwrite_gso(sp->socket, sp->buffer, size, Pudp, sp->test->settings->gso_dg_size);
    } else {
        r = Nwrite(sp->socket, sp->buffer, size, Pudp);
    }

    // 发送失败处理,如果返回0则会重新发送,小于0则返回错误
    if (r <= 0) {
        --sp->packet_count;     /* Don't count messages that no data was sent from them.
                                 * Allows "resending" a massage with the same numbering */
        if (r < 0) {
            if (r == NET_SOFTERROR && sp->test->debug_level >= DEBUG_LEVEL_INFO)
                printf("UDP send failed on NET_SOFTERROR. errno=%s\n", strerror(errno));
            return r;
        }
    }

    // 更新发送统计信息
    sp->result->bytes_sent += r;
    sp->result->bytes_sent_this_interval += r;

    if (sp->test->debug_level >=  DEBUG_LEVEL_DEBUG)
	printf("sent %d bytes of %d, total %" PRIu64 "\n", r, size, sp->result->bytes_sent);

    return r;
}

// 这种下面的注释所说,因为udp是看向无连接的,喂了模拟tcp行为需要做一些特殊处理
/**************************************************************************/

/*
 * The following functions all have to do with managing UDP data sockets.
 * UDP of course is connectionless, so there isn't really a concept of
 * setting up a connection, although connect(2) can (and is) used to
 * bind the remote end of sockets.  We need to simulate some of the
 * connection management that is built-in to TCP so that each side of the
 * connection knows about each other before the real data transfers begin.
 */

// 配置并校验socket 的 send buffer和 recv buffer。
/*
 * Set and verify socket buffer sizes.
 * Return 0 if no error, -1 if an error, +1 if socket buffers are
 * potentially too small to hold a message.
 */
int
iperf_udp_buffercheck(struct iperf_test *test, int s)
{
    int rc = 0;
    int sndbuf_actual, rcvbuf_actual;

    /*
     * Set socket buffer size if requested.  Do this for both sending and
     * receiving so that we can cover both normal and --reverse operation.
     */
    int opt;
    socklen_t optlen;

    if ((opt = test->settings->socket_bufsize)) {
        if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)) < 0) {
            i_errno = IESETBUF;
            return -1;
        }
        if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)) < 0) {
            i_errno = IESETBUF;
            return -1;
        }
    }

    /* Read back and verify the sender socket buffer size */
    optlen = sizeof(sndbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf_actual, &optlen) < 0) {
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("SNDBUF is %u, expecting %u\n", sndbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > sndbuf_actual) {
	i_errno = IESETBUF2;
	return -1;
    }
    if (test->settings->blksize > sndbuf_actual) {
	char str[WARN_STR_LEN];
	snprintf(str, sizeof(str),
		 "Block size %d > sending socket buffer size %d",
		 test->settings->blksize, sndbuf_actual);
	warning(str);
	rc = 1;
    }

    /* Read back and verify the receiver socket buffer size */
    optlen = sizeof(rcvbuf_actual);
    if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf_actual, &optlen) < 0) {
	i_errno = IESETBUF;
	return -1;
    }
    if (test->debug) {
	printf("RCVBUF is %u, expecting %u\n", rcvbuf_actual, test->settings->socket_bufsize);
    }
    if (test->settings->socket_bufsize && test->settings->socket_bufsize > rcvbuf_actual) {
	i_errno = IESETBUF2;
	return -1;
    }
    if (test->settings->blksize > rcvbuf_actual) {
	char str[WARN_STR_LEN];
	snprintf(str, sizeof(str),
		 "Block size %d > receiving socket buffer size %d",
		 test->settings->blksize, rcvbuf_actual);
	warning(str);
	rc = 1;
    }

    if (test->json_output) {
    cJSON *sock_bufsize_item = cJSON_GetObjectItem(test->json_start, "sock_bufsize");
    if (sock_bufsize_item == NULL) {
    cJSON_AddNumberToObject(test->json_start, "sock_bufsize", test->settings->socket_bufsize);
    }

    cJSON *sndbuf_actual_item = cJSON_GetObjectItem(test->json_start, "sndbuf_actual");
    if (sndbuf_actual_item == NULL) {
	cJSON_AddNumberToObject(test->json_start, "sndbuf_actual", sndbuf_actual);
    }
        
    cJSON *rcvbuf_actual_item = cJSON_GetObjectItem(test->json_start, "rcvbuf_actual");
    if (rcvbuf_actual_item == NULL) {
	cJSON_AddNumberToObject(test->json_start, "rcvbuf_actual", rcvbuf_actual);
    }
    }

    return rc;
}

// 设置udp gso支持情况,即Udp分片卸载功能
#ifdef HAVE_UDP_SEGMENT
int
iperf_udp_gso(struct iperf_test *test, int s)
{
    int rc;
    int gso = test->settings->gso_dg_size;

    rc = setsockopt(s, IPPROTO_UDP, UDP_SEGMENT, (char*) &gso, sizeof(gso));
    if (rc) {
	if (test->debug)
	    iperf_printf(test, "No GSO (%d)\n", rc);
        test->settings->gso = 0;
    } else {
	if (test->debug)
	    iperf_printf(test, "GSO (%d)\n", gso);
    }

    return rc;
}
#else
int
iperf_udp_gso(struct iperf_test *test, int s)
{
    /* GSO not supported on this platform */
    test->settings->gso = 0;
    return -1;
}
#endif

// 设置udp gro支持情况,即udp分片接收卸载
#ifdef HAVE_UDP_GRO
int
iperf_udp_gro(struct iperf_test *test, int s)
{
    int rc;
    int gro = 1;

    rc = setsockopt(s, IPPROTO_UDP, UDP_GRO, (char*) &gro, sizeof(gro));
    if (rc) {
	if (test->debug)
	    iperf_printf(test, "No GRO (%d)\n", rc);
        test->settings->gro = 0;
    } else {
	if (test->debug)
	    iperf_printf(test, "GRO\n");
    }

    return rc;
}
#else
int
iperf_udp_gro(struct iperf_test *test, int s)
{
    /* GRO not supported on this platform */
    test->settings->gro = 0;
    return -1;
}
#endif

/*
 * iperf_udp_accept
 *
 * Accepts a new UDP "connection"
 */
int
iperf_udp_accept(struct iperf_test *test)
{
    struct sockaddr_storage sa_peer;
    unsigned int buf;
    socklen_t len;
    int       sz, s;
    int	      rc;

    // 用当前的listener socket作为接收的socket, 然后创建一个新的socket负责listener。
    /*
     * Get the current outstanding socket.  This socket will be used to handle
     * data transfers and a new "listening" socket will be created.
     */
    s = test->prot_listener;

    // 通过recvfrom获取对端的数据,并获取对端信息,以便通过connect绑定连接,后续则可以支持支持send/recv。
    /*
     * Grab the UDP packet sent by the client.  From that we can extract the
     * client's address, and then use that information to bind the remote side
     * of the socket to the client.
     */
    len = sizeof(sa_peer);
    if ((sz = recvfrom(test->prot_listener, &buf, sizeof(buf), 0, (struct sockaddr *) &sa_peer, &len)) < 0) {
        i_errno = IESTREAMACCEPT;
        return -1;
    }

    if (connect(s, (struct sockaddr *) &sa_peer, len) < 0) {
        i_errno = IESTREAMACCEPT;
        return -1;
    }

    /* Check and set socket buffer sizes */
    rc = iperf_udp_buffercheck(test, s);
    if (rc < 0)
	/* error */
	return rc;
    /*
     * If the socket buffer was too small, but it was the default
     * size, then try explicitly setting it to something larger.
     */
    if (rc > 0) {
	if (test->settings->socket_bufsize == 0) {
            char str[WARN_STR_LEN];
	    int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA;
	    snprintf(str, sizeof(str), "Increasing socket buffer size to %d",
	             bufsize);
	    warning(str);
	    test->settings->socket_bufsize = bufsize;
	    rc = iperf_udp_buffercheck(test, s);
	    if (rc < 0)
		return rc;
	}
    }

    if (test->settings->gso)
        iperf_udp_gso(test, s);
    if (test->settings->gro)
        iperf_udp_gro(test, s);

    // SO_MAX_PACING_RATE设置,udp socket也支持?
#if defined(HAVE_SO_MAX_PACING_RATE)
    /* If socket pacing is specified, try it. */
    if (test->settings->fqrate) {
	/* Convert bits per second to bytes per second */
	uint64_t fqrate = test->settings->fqrate / 8;
	if (fqrate > 0) {
	    if (test->debug) {
		printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
	    }
	    if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
		warning("Unable to set socket pacing");
	    }
	}
    }
#endif /* HAVE_SO_MAX_PACING_RATE */
    {
	unsigned int rate = test->settings->rate / 8;
	if (rate > 0) {
	    if (test->debug) {
		printf("Setting application pacing to %u\n", rate);
	    }
	}
    }

    /* Set common socket options */
    if (iperf_common_sockopts(test, s) < 0) {
        return -1;
    }

    // 创建一个新的socket并赋值给prot_listener,因为刚才的prot_listener已经作为数据通道接收或者发送数据了。
    /*
     * Create a new "listening" socket to replace the one we were using before.
     */
    FD_CLR(test->prot_listener, &test->read_set); // No control messages from old listener
    test->prot_listener = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port);
    if (test->prot_listener < 0) {
        i_errno = IESTREAMLISTEN;
        return -1;
    }

    // 将新的socket加入fd_set并更新到max_fd中
    FD_SET(test->prot_listener, &test->read_set);
    test->max_fd = (test->max_fd < test->prot_listener) ? test->prot_listener : test->max_fd;

    // 发送消息UDP_CONNECT_REPLY给客户端,告诉客户端可以接收另外一个udp流量了。
    /* Let the client know we're ready "accept" another UDP "stream" */
    buf = UDP_CONNECT_REPLY;
    if (write(s, &buf, sizeof(buf)) < 0) {
        i_errno = IESTREAMWRITE;
        return -1;
    }

    // 返回的是上一次的listener,也就是当前的数据通道fd
    return s;
}

/*
 * iperf_udp_listen
 *
 * Start up a listener for UDP stream connections.  Unlike for TCP,
 * there is no listen(2) for UDP.  This socket will however accept
 * a UDP datagram from a client (indicating the client's presence).
 */
int
iperf_udp_listen(struct iperf_test *test)
{
    int s;

    // 调用 netannounce声明socket,内部是创建socket并bind到对应地址和端口
    if ((s = netannounce(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->server_port)) < 0) {
        i_errno = IESTREAMLISTEN;
        return -1;
    }

    /*
     * The caller will put this value into test->prot_listener.
     */
    return s;
}

/*
 * iperf_udp_connect
 *
 * "Connect" to a UDP stream listener.
 */
int
iperf_udp_connect(struct iperf_test *test)
{
    int s, sz;
    unsigned int buf;
#ifdef SO_RCVTIMEO
    struct timeval tv;
#endif
    int rc;
    int i, max_len_wait_for_reply;

    // 客户端发起到服务器的连接请求
    /* Create and bind our local socket. */
    if ((s = netdial(test->settings->domain, Pudp, test->bind_address, test->bind_dev, test->bind_port, test->server_hostname, test->server_port, -1)) < 0) {
        i_errno = IESTREAMCONNECT;
        return -1;
    }

    // 下面这部分和iperf_udp_accept中部分代码其实是一样的,都是检查buffer配置,设置gso和gro,设置发送速率配置,设置socket选项。
    /* Check and set socket buffer sizes */
    rc = iperf_udp_buffercheck(test, s);
    if (rc < 0)
	/* error */
	return rc;

    if (test->settings->gso)
        iperf_udp_gso(test, s);
    if (test->settings->gro)
        iperf_udp_gro(test, s);

    /*
     * If the socket buffer was too small, but it was the default
     * size, then try explicitly setting it to something larger.
     */
    if (rc > 0) {
	if (test->settings->socket_bufsize == 0) {
            char str[WARN_STR_LEN];
	    int bufsize = test->settings->blksize + UDP_BUFFER_EXTRA;
	    snprintf(str, sizeof(str), "Increasing socket buffer size to %d",
	             bufsize);
	    warning(str);
	    test->settings->socket_bufsize = bufsize;
	    rc = iperf_udp_buffercheck(test, s);
	    if (rc < 0)
		return rc;
	}
    }

#if defined(HAVE_SO_MAX_PACING_RATE)
    /* If socket pacing is available and not disabled, try it. */
    if (test->settings->fqrate) {
	/* Convert bits per second to bytes per second */
	uint64_t fqrate = test->settings->fqrate / 8;
	if (fqrate > 0) {
	    if (test->debug) {
		printf("Setting fair-queue socket pacing to %"PRIu64"\n", fqrate);
	    }
	    if (setsockopt(s, SOL_SOCKET, SO_MAX_PACING_RATE, &fqrate, sizeof(fqrate)) < 0) {
		warning("Unable to set socket pacing");
	    }
	}
    }
#endif /* HAVE_SO_MAX_PACING_RATE */
    {
	unsigned int rate = test->settings->rate / 8;
	if (rate > 0) {
	    if (test->debug) {
		printf("Setting application pacing to %u\n", rate);
	    }
	}
    }

    /* Set common socket options */
    if (iperf_common_sockopts(test, s) < 0) {
        return -1;
    }

    // 设置接收超时时间
#ifdef SO_RCVTIMEO
    /* 30 sec timeout for a case when there is a network problem. */
    tv.tv_sec = 30;
    tv.tv_usec = 0;
    setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
#endif

    // 发送一个连接消息给服务器端,以便上面的iperf_udp_accpet可以获取客户端的地址信息。
    /*
     * Write a datagram to the UDP stream to let the server know we're here.
     * The server learns our address by obtaining its peer's address.
     */
    buf = UDP_CONNECT_MSG;
    if (test->debug) {
        printf("Sending Connect message to Socket %d\n", s);
    }
    if (write(s, &buf, sizeof(buf)) < 0) {
        // XXX: Should this be changed to IESTREAMCONNECT?
        i_errno = IESTREAMWRITE;
        return -1;
    }

    // 等待服务器端返回UDP_CONNECT_REPLY消息,支持完成udp的连接建立
    /*
     * Wait until the server replies back to us with the "accept" response.
     */
    i = 0;
    max_len_wait_for_reply = sizeof(buf);
    if (test->reverse) /* In reverse mode allow few packets to have the "accept" response - to handle out of order packets */
        max_len_wait_for_reply += MAX_REVERSE_OUT_OF_ORDER_PACKETS * test->settings->blksize;
    do {
        if ((sz = recv(s, &buf, sizeof(buf), 0)) < 0) {
            i_errno = IESTREAMREAD;
            return -1;
        }
        if (test->debug) {
            printf("Connect received for Socket %d, sz=%d, buf=%x, i=%d, max_len_wait_for_reply=%d\n", s, sz, buf, i, max_len_wait_for_reply);
        }
        i += sz;
    } while (buf != UDP_CONNECT_REPLY && buf != LEGACY_UDP_CONNECT_REPLY && i < max_len_wait_for_reply);

    if (buf != UDP_CONNECT_REPLY  && buf != LEGACY_UDP_CONNECT_REPLY) {
        i_errno = IESTREAMREAD;
        return -1;
    }

    return s;
}

/* iperf_udp_init
 *
 * initializer for UDP streams in TEST_START
 */
int
iperf_udp_init(struct iperf_test *test)
{
    return 0;
}

至此,我们完成了iperf主要状态流程的处理,下一篇文章我们看看数据发送和接收中的统计和计算流程,以及最后的结果结算和展示过程。

Comments are closed.