You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TCP发送15字符至GCP VM时75-200μs RTT是否正常?

问题描述

我正在开发一个基于C++的RTT(ping)实时监控项目:客户端运行在本地机器,服务端部署在Google Cloud VM的Docker容器中。目前遇到的问题是计算出的RTT异常偏低(75-200微秒),和客户端、服务端同机运行时的结果几乎一致。按预期,通过TCP向外部服务器发送数据的RTT应该更长,不确定是对TCP/网络的理解有误,还是代码存在问题。已确认VM端有日志输出,排除了误连本地服务的情况。


客户端代码(client.cpp)

#define _WIN32_WINNT 0x0600
#include <iostream>
#include <winsock2.h>
#include <chrono>
#include <ctime>
#include <ws2tcpip.h>
#include <matplot/matplot.h>

#define PORT 8080
#define MAX_BUFFER_SIZE 1024
#define TRUE 1
#define FALSE 0

int main() {
    WSADATA wsaData;
    SOCKET clientSocket;
    sockaddr_in serverAddr;
    const char* helloMessage = "Hello from client";
    char buffer[MAX_BUFFER_SIZE];

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed.\n";
        return 1;
    }

    if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        std::cerr << "Could not create socket.\n";
        return 1;
    }

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    if (InetPton(AF_INET, "MY GCLOUD IP", &serverAddr.sin_addr) <= 0) {
        std::cerr << "Invalid address/Address not supported.\n";
        return 1;
    }

    if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        std::cerr << "Connection failed.\n";
        return 1;
    }

    double iteration = 0;
    std::vector<double> iterations;
    std::vector<double> pings;

    using namespace matplot;

    line_handle fig = plot(iterations, pings);

    fig.get()->display_name("Real-time ping monitoring");

    while(TRUE) {
        auto start = std::chrono::high_resolution_clock::now();
        send(clientSocket, helloMessage, strlen(helloMessage), 0);

        int recvSize = recv(clientSocket, buffer, MAX_BUFFER_SIZE, 0);
        auto end = std::chrono::high_resolution_clock::now();
        int64_t ping = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        buffer[recvSize] = '\0';

        std::cout << "Server: " << buffer << std::endl;
        std::cout << "Time elapsed: " << ping << "us\n";

        pings.push_back(ping);
        iterations.push_back(++iteration);

        try {
            fig.get()->x_data(iterations);
            fig.get()->y_data(pings);
            fig.get()->touch();
        } catch (const std::runtime_error& e) {
            std::cerr << "Caught a runtime_error exception: " << e.what() << '\n';
        }

        Sleep(1000);
    }

    closesocket(clientSocket);
    WSACleanup();

    return 0;
}

服务端代码(server.cpp)

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#include <csignal>
#include <iostream>

#define PORT 8080
#define MAX_BUFFER_SIZE 1024

int serverSocket;

void cleanUpOnCrash(int sig) {
    close(serverSocket);
    std::cout << "Server closed\n";
    exit(0);
}

int main() {
    signal(SIGINT, cleanUpOnCrash);

    sockaddr_in serverAddr, clientAddr;
    char buffer[MAX_BUFFER_SIZE];
    socklen_t clientAddrSize = sizeof(clientAddr);

    // Create a socket
    if ((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        std::cerr << "Could not create socket.\n";
        return 1;
    }

    // Bind the socket to an IP address and port
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        std::cerr << "Bind failed.\n";
        return 1;
    }

    // Listen for incoming connections
    listen(serverSocket, 3);

    // Accept a client connection
    while(1){
        std::cout << "Waiting for incoming connections...\n";
        int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrSize);
        if (clientSocket == -1) {
            std::cerr << "Accept failed.\n";
            return 1;
        }
        std::cout << "Connection accepted.\n";

        // Send a welcome message to the client
        const char* welcomeMessage = "Welcome to the server!\n";
        send(clientSocket, welcomeMessage, strlen(welcomeMessage), 0);

        // Echo back any messages received from the client
        int recvSize;
        while ((recvSize = recv(clientSocket, buffer, MAX_BUFFER_SIZE, 0)) > 0) {
            buffer[recvSize] = '\0'; // Null-terminate the received string before printing
            std::cout << "Client: " << buffer << std::endl;
            send(clientSocket, buffer, recvSize, 0);
        }

        // Clean up
        close(clientSocket);
    }
    
    return 0;
}

问题分析与修复方案

问题根源

当前计时逻辑没有准确测量真实的网络RTT,核心原因:

  1. TCP缓冲区与Nagle算法:TCP是面向连接的协议,connect建立连接后,后续send的数据会先存入操作系统发送缓冲区,recv也可能直接从本地接收缓冲区读取数据,而非等待公网链路往返。加上Nagle算法会合并小数据包,进一步弱化了真实网络延迟的体现。
  2. 计时点偏差:send调用成功仅代表数据拷贝到缓冲区,不代表已发送到网络;recv成功也可能是读取本地缓存,导致计时结果偏向本地操作耗时,而非真实RTT。

修复方案

1. 禁用Nagle算法(可选但推荐)

在客户端connect成功后添加代码,避免小数据包合并:

int flag = 1;
setsockopt(clientSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag));

2. 优化计时逻辑

确保测量真实的网络往返:

  • 客户端:发送数据后,需等待服务端的真实响应(而非本地缓存);可在send后检查发送字节数,确保数据完整写入缓冲区。
  • 对比验证:先用系统ping命令测试GCLOUD IP的真实RTT,若结果为几十毫秒,说明程序计时确实存在偏差。

3. 改用UDP实现(更适合ping场景)

UDP是无连接协议,每次发送都需要完整的网络往返,能更准确测量RTT,避免TCP连接复用和缓冲区优化的干扰。


内容的提问来源于stack exchange,提问作者TheMorgan8697

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:20:57