简体   繁体   English

如何使用C ++中的recv或read函数从tcp套接字读取大请求?

[英]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. 我正在开发将接收HTTP请求的服务器。 All works fine, but when the request is large, for example 30 mb, a part of this request is missing. 一切正常,但是当请求很大(例如30 mb)时,此请求的一部分将丢失。 In other words, server is not able to read all data when large request is received. 换句话说,当接收到大请求时,服务器无法读取所有数据。

Here is a code of my tcp server: 这是我的tcp服务器的代码:

TcpServer.h : 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 : 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. 我认为,问题出在recvAll函数中。 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. 我试图在第19行将poll的超时设置为20毫秒以上(SO_RCVTIMEO等于20),并且服务器开始读取较大的请求。 But increasing timeout is not a solution, bacause I don't know what timeout value is required for 1 gb or greater request. 但是增加超时不是解决方案,因为我不知道1 GB或更大请求需要什么超时值。 Here is a code of recvAll function: 这是recvAll函数的代码:

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. 您需要继续调用recv直到请求完成为止。 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). TCP不能告诉您请求何时完成-但这不是它所做的(嗯,它可以告诉您何时关闭连接,但这会关闭连接)。 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. 此刻,只要20毫秒内没有任何数据,您就停止读取。 That's not very reliable. 那不是很可靠。 A network disruption could cause a 20ms delay and then you'd stop too early. 网络中断可能会导致20毫秒的延迟,然后您就停下来了。

In HTTP, the client tells you how long the request body is by using the Content-Length header. 在HTTP中,客户端通过使用Content-Length标头告诉您请求主体有多长时间。 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). 该请求由请求标头和请求正文组成,因此服务器需要读取标头(在空行后停止),然后查看标头以查看请求正文有多长时间,然后读取正文(终止)在该字节数之后)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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