1

I am implementing a communication system (tx, rx) using TCP, in windows 10. My problem is when tx sends a message to rx, the message is received with delay. When tx sends several messages, the rx starts receiving only after several messages are sent. My guess is that tx waits until its buffer gets full and then starts sending messages altogether (in my platform buffer length is 512).

As shown in the bellow picture, before receiving is started, this error appears:

ERROR receiving TCP, error #: 10014

Here is a picture of the tx and rx:

I tried to solve this problem by enabling the TCP_NODELAY option so that the messages are being sent immediately (which was not successful). However, my knowledge of TCP is shallow and I am not sure if I am doing it correctly.

Here are my questions:

  1. Does the delayed sending (or receiving) is related to the TCP_NODELAY option of the TCP?

  2. If yes, am I enabling the TCP_NODELAY correctly as follows:

    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";
    
  3. Am I putting the TCP_NODELAY option in the right place in both server and client codes?

Here are my codes for client:

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;
}

and:

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;
}

Here are my codes for server:

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;
}

and: 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;
}

Here are the codes for sending the message (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;
}

Here are the codes for receiving the message (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;
}

Thank you!

Zeinab Rahmani
  • 77
  • 3
  • 10
  • 2
    Short answer: no. Nonblocking mode has nothing to do, whatsoever, with any delays. There doesn't appear to be anything in the shown code that actually enables it, anyway. And since you did not appear to have shown any code that's actually involved in sending and/or receiving messages, it's unlikely anyone will be able to tell you anything more. – Sam Varshavchik Jan 20 '21 at 12:15
  • @Sam Varshavchilk Thank you for your reply. When I send a message with length 512, the rx receives it immediately, therefore I think the problem is related to TCP buffer size or Blocking mode. However, I will add more codes. This line of code is where I am trying to enable Non-Blocking mode: `int resultt = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&yes, sizeof(int));` – Zeinab Rahmani Jan 20 '21 at 12:29
  • Did you read the 4th sentence in my first comment? What about the first two sentences? – Sam Varshavchik Jan 20 '21 at 12:33
  • You never mentioned whether enabling `TCP_NODELAY` worked. Also, "blocking" and "non-blocking" modes are unrelated to this, so please don't refer to it with those terms. – molbdnilo Jan 20 '21 at 12:38
  • @Sam Varshavchilk I read all of your sentences accurately and I added more information. You mentioned that there is nothing in the shown code that actually enables it (in my perception "it" refers to Non-Blocking mode, that's why I sent you the line codes that enable Non-Blocking). About adding more codes, I said that I will add even though they are too long. And something that you never consider is that even the simple stuff is sometimes difficult for the beginners and if you aim to help them, it would be great. But what you always do is closing or downvoting my questions without any help. – Zeinab Rahmani Jan 20 '21 at 12:57
  • @Sam Varshavchilk No `TCP_NODELAY` didn't work. But I thought maybe I am not enabling it correctly. And if this delay is not related to TCP mode, I won't mention it anymore. – Zeinab Rahmani Jan 20 '21 at 13:01
  • 1
    FYI: [Best Practices for TCP Optimization in 2019](https://www.extrahop.com/company/blog/2016/tcp-nodelay-nagle-quickack-best-practices/) – Scheff's Cat Jan 20 '21 at 13:03
  • 1
    `TCP_NODELAY` has absolutely nothing to do, whatsoever, with "non-blocking" mode. This is something else entirely. The "line codes that enable non-blocking" don't do any such thing. They set the `TCP_NODELAY` flag, which has nothing to do with "non-blocking" mode, which is a completely different setting. Non-blocking mode is the `O_NONBLOCK` flag set via `fcntl`. – Sam Varshavchik Jan 20 '21 at 13:11
  • 1
    Btw. blocking mode means a write() doesn't return until buffer was sent completely, read() doesn't return until something was received. If you want to perform something else in your loop, this is as annoying as blocking stream input (the only option of the standard C++ library). For sockets, you have the alternative of non-blocking write()/read(). I.e. the read() function returns immediately and just reports whether or not something was received. (Similar to write().) This has nothing to do with the delays you're investigating. (Just in case, you didn't understand the complaints above...) – Scheff's Cat Jan 20 '21 at 13:12

1 Answers1

0

socket should be

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

it should be enough , but it depends
bulletproof stuff is to use whole buffer transfers with buffer size on system maximum
https://stackoverflow.com/a/64726689/7172363

  • @ Алексей Неудачин Thank you for your reply. Do you mean instead of `SOCKET s = socket(AF_INET, SOCK_STREAM, 0);` I should write `int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);` in client and server codes? Because I tried the following in both server and the client and it didnt work: `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*)&yes, sizeof(int)); if (resultt < 0) std::cerr << "Error: TCP_NODELAY_Client";` – Zeinab Rahmani Jan 22 '21 at 12:50
  • it's necessary also to use full read buffer(aka `receive()` size arg) on `send()`. as a fallback see code in the link. it has some waste options for sure but it works – Алексей Неудачин Jan 22 '21 at 13:53
  • @ Алексей Неудачин Thanx again. I added the codes for send() and recv(). I didn't write the shown codes myself. I appreciate it if you could recheck the added codes as your reply is not so clear to me. – Zeinab Rahmani Jan 22 '21 at 14:23