簡體   English   中英

如何使用C ++中的recv或read函數從tcp套接字讀取大請求?

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

我正在開發將接收HTTP請求的服務器。 一切正常,但是當請求很大(例如30 mb)時,此請求的一部分將丟失。 換句話說,當接收到大請求時,服務器無法讀取所有數據。

這是我的tcp服務器的代碼:

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

我認為,問題出在recvAll函數中。 我試圖在第19行將poll的超時設置為20毫秒以上(SO_RCVTIMEO等於20),並且服務器開始讀取較大的請求。 但是增加超時不是解決方案,因為我不知道1 GB或更大請求需要什么超時值。 這是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;
}

如何解決此功能,或者如何使服務器使其完全讀取大型請求?

您需要繼續調用recv直到請求完成為止。 TCP不能告訴您請求何時完成-但這不是它所做的(嗯,它可以告訴您何時關閉連接,但這會關閉連接)。 因此,您需要另一種方法來知道何時停止閱讀。

此刻,只要20毫秒內沒有任何數據,您就停止讀取。 那不是很可靠。 網絡中斷可能會導致20毫秒的延遲,然后您就停下來了。

在HTTP中,客戶端通過使用Content-Length標頭告訴您請求主體有多長時間。 該請求由請求標頭和請求正文組成,因此服務器需要讀取標頭(在空行后停止),然后查看標頭以查看請求正文有多長時間,然后讀取正文(終止)在該字節數之后)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM