【问题标题】:How to enable TCP_NODELAY option in both Server and Client?如何在服务器和客户端中启用 TCP_NODELAY 选项?
【发布时间】:2021-04-24 18:41:21
【问题描述】:

我正在 Windows 10 中使用 TCP 实现通信系统(tx,rx)。我的问题是当 tx 向 rx 发送消息时,消息的接收延迟。当 tx 发送多条消息时,rx 仅在发送多条消息后才开始接收。我的猜测是 tx 一直等到它的缓冲区满了,然后才开始完全发送消息(在我的平台缓冲区长度是 512)。

如下图,在开始接收之前,出现这个错误:

ERROR receiving TCP, error #: 10014

我尝试通过启用TCP_NODELAY 选项来解决此问题,以便立即发送消息(但未成功)。但是,我对 TCP 的了解很浅,我不确定自己是否正确。

这是我的问题:

  1. 延迟发送(或接收)是否与TCP的TCP_NODELAY选项有关?

  2. 如果是,我是否正确启用了TCP_NODELAY,如下所示:

    int 是 = 1;

    int resultt = setsockopt(serverSocket[0], IPPROTO_TCP, TCP_NODELAY, (char*)&yes, sizeof(int));    // 1 - on, 0 - off
    
    if (resultt < 0)
        std::cerr << "Error: TCP_NODELAY";
    
  3. 我是否将TCP_NODELAY 选项放在服务器和客户端代码中的正确位置?

这是我的客户代码:

size_t IPTunnel::ipTunnelSend_tcp(size_t process) {

    size_t remaining = process;
    size_t result{ 0 };
    size_t sent{ 0 };
    while (remaining > 0) {
        result = send(clientSocket[0], &(ip_tunnel_buffer[0]) + sent, (int) remaining, 0);
        if (result >= 0) {
            remaining -= result;
            sent += result;
        }
        else { 
            if (getLogValue()) {
                if ( result == 0) std::cerr << "No data send through TCP" << std::endl;
                else std::cerr << "ERROR sending TCP, error #: " << WSAGetLastError() << std::endl;
            }
            break;
        }
    }
    return sent;
}

和:

bool IPTunnel::client_tcp() {

    // STEP 1 - Initialize Winsock
    WSADATA wsData;                         
    WORD ver = MAKEWORD(2, 2);                  
    int wsResult = WSAStartup(ver, &wsData);    
    if (wsResult != 0)                          
    {
        std::cerr << "Can't start Winsock. Error code #" << wsResult << std::endl;
        return false;
    }

    // STEP 2 - Create a socket         
    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);

    int yes = 1;
    int resultt = setsockopt(clientSocket[0], IPPROTO_TCP, TCP_NODELAY, (char*)&yes, 
    sizeof(int));
    if (resultt < 0)
        std::cerr << "Error: TCP_NODELAY";


    clientSocket.push_back(s);
        
    if (clientSocket[0] == INVALID_SOCKET)
    {
        std::cerr << "Can't create socket, Error Code #" << WSAGetLastError() << std::endl;
        WSACleanup();
        return false;
    }

    // STEP 3 - Connect to the server
    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons((u_short)localMachinePort);
    inet_pton(AF_INET, remoteMachineIpAddress.c_str(), &hint.sin_addr);

    int connResult{ -2 };
    while (connResult != 0 || numberOfTrials == 0) {
        connResult = connect(clientSocket[0], (sockaddr*)& hint, sizeof(hint));
        if (connResult == SOCKET_ERROR)
        {
            std::cerr << "Can't connect to server, Err #" << WSAGetLastError() << std::endl;
            std::cerr << "Waiting " << timeIntervalSeconds << " seconds." << std::endl;
            Sleep(timeIntervalSeconds * 1000);
        }

        if (--numberOfTrials == 0) {
            std::cerr << "Reached maximum number of attempts." << std::endl;
        }
    }
    std::cerr << "Connected!\n";
    return true;
}

这是我的服务器代码:

size_t IPTunnel::ipTunnelRecv_tcp(size_t space) {
    char* recv_buffer = &ip_tunnel_buffer[0];
    size_t remaining{ 0 };
    if (outputSignals[0]->getValueType() == (signal_value_type::t_message))
    {
        remaining = ip_tunnel_buffer.size();
    }
    else
    {
        remaining = space * outputSignals[0]->valueTypeSizeOf();
    }
    size_t received{ 0 };
    while (remaining > 0) {
        auto aux{ 0 };
        aux = recv(serverSocket[1], recv_buffer + received, (int) remaining, 0);
        if (aux > 0) {
            received += aux;
            remaining -= received;
        }
        else {
            if (getLogValue()) {
                if (aux == 0) std::cerr << "No data received through TCP" << std::endl;
                else std::cerr << "ERROR receiving TCP, error #: " << WSAGetLastError() << std::endl;
            }
            break;
        }
    }
    return received;
}

和: bool IPTunnel::server_tcp() {

    // STEP 1 - Initialize Winsock

    WSADATA wsData;                             
    WORD ver = MAKEWORD(2, 2);                  
    int wsResult = WSAStartup(ver, &wsData);    
    if (wsResult != 0)                          
    {
        std::cerr << "Can't start Winsock. Error code #" << wsResult << std::endl;
        return false;
    }

    // STEP 2 - Create a socket                 

    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    serverSocket.push_back(s);
    
    if (serverSocket[0] == INVALID_SOCKET)
    {
        std::cerr << "Can't create socket, Error Code #" << WSAGetLastError() << std::endl;
        WSACleanup();
        return false;
    }

    // STEP 3 - Bind the socket
    
    sockaddr_in hint;
    hint.sin_family = AF_INET; // AF_INET=2, IPv4
    inet_pton(AF_INET, localMachineIpAddress.data(), &hint.sin_addr.S_un.S_addr);
    hint.sin_port = ntohs((u_short)localMachinePort);
    
    char ipAddress[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &hint.sin_addr.S_un.S_addr, ipAddress, INET_ADDRSTRLEN);
    std::cerr << "The TCP server is running on IP address: " << ipAddress;
    std::cerr << " Port: " << htons(hint.sin_port) << std::endl;

    if (bind(serverSocket[0], (sockaddr*)& hint, sizeof(hint)) < 0) {
        std::cerr << "Bind failed with error code #" << WSAGetLastError() << std::endl;
        return false;
    }

    // STEP 4 - Listen on the socket for a client

    if (listen(serverSocket[0], SOMAXCONN) == -1) {
            std::cerr << "Listen error!" << std::endl;
            return false;
    }

    // STEP 5 - Accept a connection from a client
    sockaddr_in client;
    int clientSize = sizeof(client);

    s = accept(serverSocket[0], (sockaddr*) &client, &clientSize);
    serverSocket.push_back(s);

    // Provides information
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];

    ZeroMemory(host, NI_MAXHOST);
    ZeroMemory(service, NI_MAXSERV);

    if (getnameinfo((sockaddr*)& client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0)
    {
        std::cerr << host << " connected on port " << service << std::endl;
    }
    else
    {
        inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
        std::cerr << host << " connected on port " << ntohs(client.sin_port) << std::endl;
    }

    int yes = 1;
    int resultt = setsockopt(serverSocket[0], IPPROTO_TCP, TCP_NODELAY, (char*)&yes, sizeof(int));    // 1 - on, 0 - off

    if (resultt < 0)
        std::cerr << "Error: TCP_NODELAY_Server";
    
    return true;
}

这里是发送消息(tx)的代码:

bool LoadFromCommandWindow::runBlock(void) {
    
    int space = outputSignals[0]->space();
    if (!space) return false;

    if (flag && flag1)
    {
        std::getline(std::cin, plainData);
    }
    
    if (plainData.length() == 0)
    {
        flag = false;
        return false;
    }
    else 
    {
        data = plainData.substr(k, paddedDataLength);
        if (data.length() != paddedDataLength) paddedData = padTo(data, paddedDataLength, '\0');
        else paddedData = data;

        outputSignals[0]->bufferPut((std::byte*) paddedData.c_str(), paddedDataLength); \\ This line puts the message into buffer
        k += data.length();
        if (k != plainData.length()) flag1 = false;
        else
        {
            flag1 = true;
            k = 0;
        }

    }

    return flag;
}

std::string LoadFromCommandWindow::padTo(std::string str, const size_t num, const char paddingChar = '\0')
{
    if (num > str.size())
        str.insert(str.size(), num - str.size(), paddingChar);
    return str;
}

以下是接收消息(rx)的代码:

bool PrintData::runBlock(void) {

    int ready = inputSignals[0]->ready();
    int space = outputSignals[0]->space();
    int process = std::min(ready, space);

    if (process == 0) return false;

    inputSignals[0]->bufferGet((std::byte*)decryptedData, decryptedDataLength);

    decryptedDataLength = unpad(decryptedDataLength, decryptedData, '\0');

    for (size_t i = 0; i < decryptedDataLength; i++)
    {
        std::cout << decryptedData[i];
    }
    std::cout << std::endl;

    outputSignals[0]->bufferPut((std::byte*)decryptedData, decryptedDataLength);

    decryptedDataLength = 496;
    return true;
}

谢谢!

【问题讨论】:

  • 简短回答:不。非阻塞模式与任何延迟无关。无论如何,显示的代码中似乎没有任何东西可以真正启用它。而且由于您似乎没有显示任何实际参与发送和/或接收消息的代码,因此不太可能有人能告诉您更多信息。
  • @Sam Varshavchilk 感谢您的回复。当我发送一条长度为 512 的消息时,rx 会立即收到它,因此我认为问题与 TCP 缓冲区大小或阻塞模式有关。但是,我将添加更多代码。这行代码是我尝试启用非阻塞模式的地方:int resultt = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&amp;yes, sizeof(int));
  • TCP_NODELAY 与“非阻塞”模式完全无关。这完全是另一回事。 “启用非阻塞的行代码”不会做任何这样的事情。他们设置了TCP_NODELAY标志,这与“非阻塞”模式无关,这是一个完全不同的设置。非阻塞模式是通过fcntl 设置的O_NONBLOCK 标志。
  • 顺便说一句。阻塞模式意味着 write() 在缓冲区完全发送之前不会返回,read() 在收到某些内容之前不会返回。如果您想在循环中执行其他操作,这与阻塞流输入(标准 C++ 库的唯一选项)一样烦人。对于套接字,您可以选择非阻塞 write()/read()。 IE。 read() 函数立即返回,并仅报告是否收到了某些内容。 (类似于 write()。)这与您正在调查的延迟无关。 (以防万一,你不明白上面的抱怨......)

标签: c++ windows tcp


【解决方案1】:

套接字应该是

    int data_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

应该够了,但要看情况
防弹的东西是使用系统最大缓冲区大小的整个缓冲区传输
https://stackoverflow.com/a/64726689/7172363

【讨论】:

  • @ Алексей Неудачин 感谢您的回复。你的意思是我应该在客户端和服务器代码中写int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 而不是SOCKET s = socket(AF_INET, SOCK_STREAM, 0);?因为我在服务器和客户端都尝试了以下操作,但没有成功:int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); clientSocket.push_back(s); int yes = 1; int resultt = setsockopt(clientSocket[0], IPPROTO_TCP, TCP_NODELAY, (char*)&amp;yes, sizeof(int)); if (resultt &lt; 0) std::cerr &lt;&lt; "Error: TCP_NODELAY_Client";
  • 还必须在send() 上使用完整的读取缓冲区(又名receive() 大小arg)。作为后备,请参阅链接中的代码。它肯定有一些废物选择,但它有效
  • @ Алексей Неудачин 再次感谢。我添加了 send() 和 recv() 的代码。我自己没有编写显示的代码。如果您能重新检查添加的代码,我将不胜感激,因为您的回复对我来说不是很清楚。
猜你喜欢
  • 2012-10-25
  • 1970-01-01
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
  • 2020-05-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多