简体   繁体   中英

How to read large request from tcp socket using recv or read function in C++?

I'm developing a server which will be receiving http requests. All works fine, but when the request is large, for example 30 mb, a part of this request is missing. In other words, server is not able to read all data when large request is received.

Here is a code of my tcp server:

TcpServer.h :

#include <cstring>
#include <string>
#include <vector>
#include <thread>
#include <functional>
#include <fcntl.h>
#include <chrono>
#include <thread>
#include <iostream>
#include <poll.h>

#if defined(_WIN32) || defined(_WIN64)

#include <winsock2.h>
#include <ws2tcpip.h>

typedef SOCKET socket_t;
typedef int msg_size_t;

#define WSA_LAST_ERR WSAGetLastError()
#define SOCKET_SEND SD_SEND
#define SOCKET_RECEIVE SD_RECEIVE

#pragma comment (lib, "Ws2_32.lib")

#elif defined(__unix__) || defined(__linux__)

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

typedef int socket_t;
typedef ssize_t msg_size_t;

#define INVALID_SOCKET (socket_t)(-1)
#define SOCKET_ERROR (-1)
#define SOCKET_SEND SHUT_RDWR
#define SOCKET_RECEIVE SHUT_RDWR
#define WSA_LAST_ERR errno

#else
#error Library is not supported on this platform

#endif

#define DEFAULT_HOST "127.0.0.1"
#define DEFAULT_PORT 8000

#define MAX_BUFF_SIZE 8192 * 8 - 1

typedef std::function<void(const std::string&, const socket_t&)> tcpHandler;

class TcpServer
{
private:
    uint16_t _port;
    const char* _host;
    tcpHandler _handler;
    socket_t _socket;
    sockaddr_in _socketAddr{};
    void startListener();
    void serveConnection(const socket_t& client);
    static int closeSocket(const socket_t& socket);
    static void wsaCleanUp();
    void cleanUp(const socket_t& connection);
    std::string recvAll(const socket_t& connection);
    int init();

    enum ReadResult
    {
        Continue, None
    };

    ReadResult _handleError(char* buffer, int& status);

public:
    struct Context
    {
        const char* host = nullptr;
        uint16_t port = 0;
        tcpHandler handler = nullptr;
    };

    explicit TcpServer(TcpServer::Context ctx);
    void listenAndServe();
    static void send(const char* data, const socket_t& connection);
    static void write(const char* data, size_t bytesToWrite, const socket_t& connection);
    ~TcpServer();
};

TcpServer.cpp :

#include "tcp_server.h"

bool set_socket_blocking(int fd, bool blocking)
{
    if (fd < 0)
    {
        return false;
    }

#if defined(_WIN32) || defined(_WIN64)
    unsigned long mode = blocking ? 0 : 1;
    return ioctlsocket(fd, FIONBIO, &mode) == 0;
#else
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1)
    {
        return false;
    }

    flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
    return fcntl(fd, F_SETFL, flags) == 0;
#endif
}

TcpServer::TcpServer(TcpServer::Context ctx)
{
    if (ctx.host == nullptr)
    {
        ctx.host = DEFAULT_HOST;
    }

    if (ctx.port == 0)
    {
        ctx.port = DEFAULT_PORT;
    }

    this->_host = ctx.host;
    this->_port = ctx.port;

    if (ctx.handler == nullptr)
    {
        throw std::invalid_argument("Context::handler can not be null");
    }

    this->_handler = ctx.handler;

    if (ctx.logger == nullptr)
    {
        ctx.logger = Logger::getInstance();
    }

    this->_socketAddr = {};
    this->_socket = {};
}

TcpServer::~TcpServer()
{
    TcpServer::cleanUp(this->_socket);
}

int TcpServer::init()
{
    this->_socketAddr.sin_family = AF_INET;
    this->_socketAddr.sin_port = htons(this->_port);
    this->_socketAddr.sin_addr.s_addr = inet_addr(this->_host);

    memset(this->_socketAddr.sin_zero, '\0', sizeof this->_socketAddr.sin_zero);

    this->_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (this->_socket == INVALID_SOCKET)
    {
        std::cout << "Failed to initialize server at port " + std::to_string(this->_port) << '\n';
        TcpServer::wsaCleanUp();
        return INVALID_SOCKET;
    }

    if (bind(this->_socket, (sockaddr*)&this->_socketAddr, sizeof(this->_socketAddr)) == SOCKET_ERROR)
    {
        std::cout << "Failed to bind socket to port " + std::to_string(this->_port) << '\n';
        TcpServer::cleanUp(this->_socket);
        return SOCKET_ERROR;
    }

    if (listen(this->_socket, SOMAXCONN) == SOCKET_ERROR)
    {
        std::cout << "Failed to listen at port " + std::to_string(this->_port) << '\n';
        return SOCKET_ERROR;
    }

    return 0;
}

TcpServer::ReadResult TcpServer::_handleError(
    char* buffer, int& status, int line, const char *function, const char *file
)
{
    switch (errno)
    {
        case EBADF:
        case EFAULT:
        case EINVAL:
        case ENXIO:
            // Fatal error.
            free(buffer);
            throw "Critical error";
        case EIO:
        case ENOBUFS:
        case ENOMEM:
            // Resource acquisition failure or device error.
            free(buffer);
            throw "Resource failure";
        case EINTR:
            // TODO: Check for user interrupt flags.
        case ETIMEDOUT:
        case EAGAIN:
            // Temporary error.
            return ReadResult::Continue;
        case ECONNRESET:
        case ENOTCONN:
            // Connection broken.
            // Return the data we have available and exit
            // as if the connection was closed correctly.
            status = 0;
            break;
        default:
            free(buffer);
            throw "Returned -1";
    }
    return ReadResult::None;
}

void TcpServer::listenAndServe()
{
    if (this->init() != 0)
    {
        return;
    }

#if defined(_WIN32) || defined(_WIN64)
    int status;
    WSADATA wsaData;
    status = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (status != 0)
    {
        std::cout << "WSAStartup() failed with error #" + std::to_string(status) << '\n';
        return;
    }
#endif

    this->startListener();
    TcpServer::wsaCleanUp();
}

void TcpServer::startListener()
{
    bool listening = true;
    while (listening)
    {
        try
        {
            socklen_t connectionLen = sizeof(this->_socketAddr);
            socket_t connection = accept(this->_socket, (sockaddr*)&this->_socketAddr, &connectionLen);


            if (connection != INVALID_SOCKET)
            {
                    set_socket_blocking(connection, false);

                std::thread newThread(&TcpServer::serveConnection, this, connection);
                newThread.detach();
            }
            else
            {
                std::cout << "Invalid socket connection" << '\n';
            }
        }
        catch (const std::exception& exc)
        {
            std::cout << exc.what() << '\n';
            listening = false;
        }
        catch (const char* exc)
        {
            std::cout << exc << '\n';
            listening = false;
        }
        catch (...)
        {
            std::cout << "Error occurred while listening for socket connection" << '\n';
            listening = false;
        }
    }
}

void TcpServer::serveConnection(const socket_t& connection)
{
    try
    {
        std::string data = TcpServer::recvAll(connection);

        if (!data.empty())
        {
            this->_handler(data, connection);
        }
    }
    catch (const BaseException& exc)
    {
        std::cout << exc.what() << '\n';
    }
    catch (const std::exception& exc)
    {
        std::cout << exc.what() << '\n';
    }
    TcpServer::cleanUp(connection);
}

std::string TcpServer::recvAll(const socket_t& connection)
{
    msg_size_t ret = 0;
    int status = 0;
    unsigned long size = 0;
    std::string data;

    // Poll descriptor structure
    struct pollfd descriptor{};

    // Input stream
    descriptor.fd = connection;
    descriptor.events = POLLIN;

    char* buffer = (char*) calloc(MAX_BUFF_SIZE, sizeof(char));
    do
    {
        // Wait 20 ms
        status = poll(&descriptor, 1, SO_RCVTIMEO);
        if (status == -1)
        {
            this->_handleError(buffer, status, _ERROR_DETAILS_);
        }
        else if (status == 0)
        {
            // Timeout, skip
        }
        else
        {
            // Reset the descriptor.revents to reuse the structure
            if (descriptor.revents == POLLIN)
            {
                descriptor.revents = 0;
            }

            ret = read(connection, buffer, MAX_BUFF_SIZE);
            if (ret > 0)
            {
                data.append(buffer, ret);
                size += ret;
            }
            else if (ret == -1)
            {
                this->_handleError(buffer, status);
            }
        }
    }
    while (status > 0);

    free(buffer);

    if (data.size() != size)
    {
        throw "Invalid request data total size";
    }
    return data;
}

void TcpServer::send(const char* data, const socket_t& connection)
{
    if (::send(connection, data, std::strlen(data), 0) == SOCKET_ERROR)
    {
        throw "Failed to send bytes to socket connection";
    }
}

void TcpServer::write(const char* data, size_t bytesToWrite, const socket_t& connection)
{
    if (::write(connection, data, bytesToWrite) == -1)
    {
        throw "Failed to send bytes to socket connection";
    }
}

int TcpServer::closeSocket(const socket_t& socket)
{
#if defined(_WIN32) || defined(_WIN64)
    return ::closesocket(socket);
#elif defined(__unix__) || defined(__linux__)
    return ::close(socket);
#endif
}

void TcpServer::wsaCleanUp()
{
#if defined(_WIN32) || defined(_WIN64)
    WSACleanup();
#endif
}

void TcpServer::cleanUp(const socket_t& socket)
{
    if (TcpServer::closeSocket(socket) == SOCKET_ERROR)
    {
        std::cout << "Failed to close socket connection" << '\n';
    }
    TcpServer::wsaCleanUp();
}

I think, the problem is in recvAll function. I've tried to set poll 's timeout to more than 20 ms in line 19 (SO_RCVTIMEO equals 20), and the server became reading larger requests. But increasing timeout is not a solution, bacause I don't know what timeout value is required for 1 gb or greater request. Here is a code of recvAll function:

std::string TcpServer::recvAll(const socket_t& connection)
{
    msg_size_t ret = 0;
    int status = 0;
    unsigned long size = 0;
    std::string data;

    // Poll descriptor structure
    struct pollfd descriptor{};

    // Input stream
    descriptor.fd = connection;
    descriptor.events = POLLIN;

    char* buffer = (char*) calloc(MAX_BUFF_SIZE, sizeof(char));
    do
    {
        // Wait 20 ms
        status = poll(&descriptor, 1, SO_RCVTIMEO);
        if (status == -1)
        {
            this->_handleError(buffer, status, _ERROR_DETAILS_);
        }
        else if (status == 0)
        {
            // Timeout, skip
        }
        else
        {
            // Reset the descriptor.revents to reuse the structure
            if (descriptor.revents == POLLIN)
            {
                descriptor.revents = 0;
            }

            ret = read(connection, buffer, MAX_BUFF_SIZE);
            if (ret > 0)
            {
                data.append(buffer, ret);
                size += ret;
            }
            else if (ret == -1)
            {
                this->_handleError(buffer, status);
            }
        }
    }
    while (status > 0);

    free(buffer);

    if (data.size() != size)
    {
        throw "Invalid request data total size";
    }
    return data;
}

How can I fix this function, or maybe the server to make it read large requests fully?

You need to keep calling recv until the request is finished. TCP can't tell you when the request is finished - that's just not something it does (well, it can tell you when the connection is closed, but that would close the connection). So you need another way to know when to stop reading.

At the moment, you stop reading whenever there isn't any data for 20ms. That's not very reliable. A network disruption could cause a 20ms delay and then you'd stop too early.

In HTTP, the client tells you how long the request body is by using the Content-Length header. The request consists of the request headers and the request body, so the server needs to read the headers (which stop after a blank line), then look at the headers to see how long the request body is, then read the body (which stops after that number of bytes).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM