How does recv () understand that all data is received in TCP cases?

As far as I understand, data over TCP is transmitted as a continuous stream, until the connection is broken. If you look at the structure of the TCP segment, there is not even information about the length of the data (unlike UDP, for example). Thus, if we read something from a TCP socket to the buffer, the read will occur until the connection is closed, or the buffer overflows.

However, if you look at the actual code, this is not the case-recv () on the server reads exactly as many bytes as are sent from the client using send()

How does recv () understand that all the data is received and the control needs to be returned to the calling code?

Complete and minimal example on bare sockets:

Server:

#include <iostream>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <memory>
#include <arpa/inet.h>

const int BUFFER_SIZE = 1024;
const int PORT = 12345;

int main() {
    //create server socket
    int socketFd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (socketFd < 0) {
        return -1;
    }
    int opt_val = 1;
    setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof opt_val);


    //bind to address
    sockaddr_in socketAddress;
    socketAddress.sin_family = AF_INET;
    socketAddress.sin_port = htons(PORT);
    socketAddress.sin_addr.s_addr = htons(INADDR_ANY);
    int rc = ::bind(socketFd,
                    reinterpret_cast<sockaddr*>(&socketAddress),
                    sizeof(socketAddress));
    if (rc < 0) {
        return -2;
    }


    //listen
    rc = ::listen(socketFd, SOMAXCONN);
    if (rc < 0) {
        return -3;
    }

    //accept new connection
    sockaddr_in socketAdress;
    unsigned int sizeOfSocketAdress = sizeof(socketAdress);
    int clientSocket = ::accept(socketFd, (struct sockaddr *)&socketAdress, &sizeOfSocketAdress);
    if (clientSocket < 0) {
        return -4;
    }

    //receive
    char buffer[BUFFER_SIZE];
    int receivedBytes = ::recv(clientSocket, buffer, BUFFER_SIZE, MSG_NOSIGNAL);

    std::cout << "Received " << receivedBytes << " bytes : "  << buffer << std::endl; // Прочитано 5 байт "hello", хотя буфер не заполнен и соединение не прервано
    return 0;
}

Client:

#include <iostream>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    struct sockaddr_in sa;
    int res;
    int socketFd;

    socketFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketFd == -1) {
      perror("cannot create socket");
      exit(EXIT_FAILURE);
    }

    memset(&sa, 0, sizeof sa);

    sa.sin_family = AF_INET;
    sa.sin_port = htons(12345);
    res = inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr);

    if (connect(socketFd, (struct sockaddr *)&sa, sizeof sa) == -1) {
      perror("connect failed");
      close(socketFd);
      exit(EXIT_FAILURE);
    }

    auto buf = "hello";
    auto len = 5;

    int sentBytes = ::send(socketFd, buf, len, 0);

    std::cout << "sent " << sentBytes << "bytes: " << buf << std::endl;

    std::string tmp;
    std::getline(std::cin, tmp); //приостановка выполнения, соединение все еще не закрыто
    return EXIT_SUCCESS;
}
Author: NikBond, 2018-10-10

3 answers

No, recv reads the number of bytes no more than of the specified buffer size, without worrying at all about getting all the data. In the best case, a packet with the PSH flag comes from the client, hinting that it makes sense to give the data to the reader now.

 2
Author: user7860670, 2018-10-10 17:57:12

How does recv () understand that all data is received, and control need to return to the calling code?

Nothing. This is the concern of the app. From the application's point of view, a TCP channel is a regular file. How does the application know that a certain portion of data is accepted completely? There are only two ways:

  1. There is a specific record separator character. For most text files, this character is '\n '
  2. At the beginning of the data portion, the block length is written. How the specific way this is done depends on the specific application.

Both of these points are 100% applicable to TCP connections. For example, the HTTP protocol uses a variant of paragraph 1: each message BEGINS with a special header line and ENDS with an empty line. The data is transmitted in a slightly different way, but the idea is the same.

 1
Author: Sergey, 2018-10-12 03:00:33

TCP behavior is unpredictable :) there is no 100% guarantee that the 'user data package' is delivered in part or in full. This is up to you to decide in your program. It makes sense to sometimes perform additional checks for the presence of data, I will show you an example of a function that subtracts all the data for асинхронного сокета:

// функция работает как 'умный' TCP.socket.data.flush

void tcp_recv_empty(int sock, ssize_t sz)
{
    ssize_t       rsz;
    unsigned char rbuf[65536]; // 1500 более бережно :)

#   if !defined(MSG_WAITALL)
#   define MSG_WAITALL 0x40
#   endif

    if (!sz)
    {
        if ((ioctl(sock, FIONREAD, &sz) != 0) || (!sz)) { return; }
    }
    while (sz > 0)
    {
        errno = 0;
        switch ((rsz = recv(sock, rbuf, sizeof(rbuf), MSG_WAITALL)))
        {
            case (ssize_t)-1:
            {
#               if defined(EAGAIN)
                if ((errno == EAGAIN) || (errno == EINTR))
#               elif defined(EWOULDBLOCK)
                if ((errno == EWOULDBLOCK) || (errno == EINTR))
#               endif
                {
                    continue;
                }
                return;
            }
            case 0:
            {
                return;
            }
            default:
            {
                sz -= rsz;
            }
        }
    }
}

You should always rely on the return code recv*() and the state obtained by such a function, for example:

int net_socket_iserror(int sock)
{
    int se;
    socklen_t sl = sizeof(int);

#   if defined(SO_ERROR)
    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &se, &sl) < 0)
    {
        return errno;
    }
    return se;
#   else
    return 0;
#   endif
}
 0
Author: NewView, 2018-10-10 19:24:29