WSAPoll 接收来自同一客户端的多个连接请求(HTTP 请求之前和之后)

问题描述 投票:0回答:1

我正在 Windows 上试验套接字(C++ 程序)。作为练习,我从浏览器发送 HTTP 请求(纯文本),服务器发送响应。我使用

WSAPoll()
来处理多个客户端连接请求和事件。

程序似乎工作正常,但是即使我只有 1 个客户端并发送 1 个请求,服务器指示它收到 2 个客户端连接请求。我在网页的

fetch
命令被触发后收到一个请求(fd = 196),我似乎在发送响应后立即有1(fd = 200) - 当然,fd号会根据它运行的机器。这些只是我运行程序时得到的数字,用于说明目的。 IP 地址相同(客户端/浏览器),但它是一个新的 fd 值。

这是我在控制台中得到的:

$ ./Server
Size of fds before WSAPoll 1
Size of fds before WSAPoll 1
Size of fds before WSAPoll 1

        Client IP addr: 127.0.0.1
        Size of fds after insert client 2 196

Size of fds before WSAPoll 2
some activity from client 196

Received data from client 196: GET / HTTP/1.1
Host: localhost:27015
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="113", "Chromium";v="113", "Not-A.Brand";v="24"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
sec-ch-ua-platform: "Windows"
Accept: */*
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9


Size of fds before WSAPoll 2

        Client IP addr: 127.0.0.1
        Size of fds after insert client 3 200

Size of fds before WSAPoll 3
Size of fds before WSAPoll 3
Size of fds before WSAPoll 3

为什么我使用不同的 fd 号码(虽然是相同的 IP 地址)收到多个客户端连接请求,即使我似乎只从浏览器发出一个请求?

这是我用于服务器和 HTML 页面的代码:

// Compile with clang++ -o Server Server1.cpp -lWS2_32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <cstdio>
#include <cassert>

#include <vector>
#include <string>

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

int main()
{
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2), &wsadata);
    SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    assert(listen_socket != INVALID_SOCKET);

#if _WIN32
    unsigned long non_blocking = 1;
    ioctlsocket(listen_socket, FIONBIO, &non_blocking);
#else
#endif

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(27015);

    bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

    listen(listen_socket, 4);

    WSAPOLLFD listen_fd {
        .fd = listen_socket,
        .events = POLLRDNORM,
    };

    std::vector<WSAPOLLFD> fds;
    fds.push_back(listen_fd);

    uint32_t count = 0;

    for (;;) {
#if _WIN32
        fprintf(stderr, "Size of fds before WSAPoll %llu\n", fds.size());
        int result = WSAPoll(fds.data(), fds.size(), 1000); // no timeout, wait indefinitely       
        if (fds[0].revents & POLLRDNORM) {
            // accept new cient connection
            sockaddr_in client_addr{};
            int client_addr_size = sizeof(client_addr);
            SOCKET client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &client_addr_size);
            assert(client_socket != INVALID_SOCKET);

            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
            fprintf(stderr, "\n\tClient IP addr: %s\n", client_ip);

            WSAPOLLFD client_fd {
                .fd = client_socket,
                .events = POLLRDNORM,
            };

            fds.push_back(client_fd);
            fprintf(stderr, "\tSize of fds after insert client %llu %llu\n\n", fds.size(), client_fd.fd);
        }

        //for (uint32_t i = 1; i < fds.size(); ++i) {
        for (auto iter = fds.begin() + 1; iter != fds.end();) {
            //for (size_t i = 1; i < fds.size(); ++i) {
            if (iter->revents & (POLLRDNORM | POLLERR | POLLHUP)) {
                fprintf(stderr, "some activity from client %lld\n\n", iter->fd);
                char buffer[1024];
                memset(buffer, 0x0, sizeof(buffer));
                int bytes_read = recv(iter->fd, buffer, sizeof(buffer) - 1, 0);
                if (bytes_read > 0) {
                    fprintf(stderr, "Received data from client %lld: %s\n\n", iter->fd, buffer);
                    std::string msg = "Count = " + std::to_string(count++);
                    std::string response = "HTTP/1.1 200 OK\r\n";
                    response += "Content-Type: text/plain\r\n";
                    response += "Access-Control-Allow-Origin: *\r\n";
                    response += "Content-Length: " + std::to_string(msg.size()) + "\r\n\r\n";
                    response += msg;
                    send(iter->fd, response.c_str(), response.size(), 0);
                }
                else if (bytes_read == 0) {
                    fprintf(stderr, "Client disconnected %lld\n", iter->fd);
                    closesocket(iter->fd);
                    iter = fds.erase(iter);
                    fprintf(stderr, "Number of clients left: %llu\n", fds.size());
                    continue;
                }
                else {
                    assert(0);
                }
            }
            iter++;
        }
#else
        int result = poll(&listen_fd, 1, -1); // indefinite timeout
#endif
    }

    closesocket(listen_socket);
    WSACleanup();

    return 0;
}
<!DOCTYPE html>
<html>
    <head>
        <title>ARTS Testing Server</title>
    </head>
    <body>
        <h1>Welcome to My Webpage</h1>
        <button onclick="sendRequest()">Send Request</button>
        <div id="response"></div>
        <script>
            function sendRequest() {
                console.log("in send request\n");
                fetch('http://localhost:27015/')
                    .then(function(response) {
                        if (response.ok) {
                            console.log("OK\n");
                            return response.text();
                        } else {
                            throw new Error("Request failed with status: " + response.status);
                        }
                    })
                    .then (function(responseText) {
                        console.log("HERE");
                        document.getElementById("response").innerHTML = responseText;
                    })
                    .catch (function(error) {
                    
                    });
            }
        </script>
    </body>
</html>
c++ winsock
1个回答
1
投票

HTTP 是无状态的。即使有 keep-alives,浏览器也根本不需要重用连接,否则连接可能已经超时并在请求之间关闭。

所以,如果浏览器将您的 HTML 页面作为远程文件检索,它使用一个连接(当将 HTML 作为本地文件加载时不是这样),并且不能保证

fetch
命令将重用相同的连接,即使它与 HTML 页面来自同一主机/端口。甚至多个
fetch
es 也可能使用不同的连接。大多数浏览器将同时维护与服务器的多个连接,以帮助它促进并行加载多个资源(以防 HTML 页面引用其他文件,如图像、脚本等)。

您可以使用浏览器内置的调试工具来查看您的情况是否确实如此。

至于您的实际代码,我发现它有一些问题:

  • 缺乏足够的错误处理。

  • 在评估

    WSAPoll()
    结构的状态之前,您完全忽略了
    WSAPOLLFD
    的返回值。返回值告诉您
    revents
    成员是否实际更新。

  • 将新结构推回您的

    std::vector
    may使您用来循环
    std::vector
    .

    的迭代器无效
  • accept()
    总是返回阻塞套接字,即使监听套接字是非阻塞的。如果你希望客户端是非阻塞的,你需要明确地设置它。

尝试更像这样的东西:

#include <iostream>
#include <vector>
#include <string>

#if _WIN32

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

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

typedef SOCKET socket_t;
typedef WSAPOLLFD pollfd_t;
typedef int socklen_t;

static const SOCKET c_INVALID_SOCKET = INVALID_SOCKET;
static const int c_SOCKET_ERROR = SOCKET_ERROR;
static const int c_EWOULDBLOCK = WSAEWOULDBLOCK;
static const int c_EAGAIN = WSAEWOULDBLOCK;

#define LastErrNo WSAGetLastError()
#define Poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
#define CloseSocket(fd) closesocket(fd)

bool InitSocketLib() {
    WSADATA wsadata;
    int errCode = WSAStartup(MAKEWORD(2,2), &wsadata);
    return (errCode == 0);
}

void CleanupSocketLib() {
    WSACleanup();
}

bool MakeNonBlocking(SOCKET sckt) {
    unsigned long non_blocking = 1;
    return (ioctlsocket(sckt, FIONBIO, &non_blocking) == 0);
}

#else

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <unistd.h>
 
typedef int socket_t;
typedef pollfd pollfd_t;

static const int c_INVALID_SOCKET = -1;
static const int c_SOCKET_ERROR = -1;
static const int c_EWOULDBLOCK = EWOULDBLOCK;
static const int c_EAGAIN = EAGAIN;

#define LastErrNo errno
#define Poll(fds, nfds, timeout) poll(fds, nfds, timeout)
#define CloseSocket(fd) close(fd)

bool InitSocketLib() { return true; }
void CleanupSocketLib() { }

bool MakeNonBlocking(int fd) {
    int non_blocking = 1;
    return (ioctl(fd, FIONBIO, &non_blocking) == 0);

    /* alternatively:
    int flags = fcntl(fd, F_GETFL, 0);
    return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0);
    */
}

#endif

int main()
{
    if (!InitSocketLib()) {
        std::cerr << "Unable to initialize socket library, error: " << LastErrNo << '\n';
        return -1;
    }

    socket_t listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listen_socket == c_INVALID_SOCKET) {
        std::cerr << "Unable to create listening socket, error: " << LastErrNo << '\n';
        return -1;
    }

    if (!MakeNonBlocking(listen_socket)) {
        std::cerr << "Unable to set listening socket to non-blocking, error: " << LastErrNo << '\n';
        return -1;
    }

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(27015);

    if (bind(listen_socket, reinterpret_cast<sockaddr*>(&server_addr), sizeof(server_addr)) < 0) {
        std::cerr << "Unable to bind listening socket, error: " << LastErrNo << '\n';
        return -1;
    }

    if (listen(listen_socket, 4) < 0) {
        std::cerr << "Unable to open listening socket, error: " << LastErrNo << '\n';
        return -1;
    }

    pollfd_t listen_fd {
        .fd = listen_socket,
        .events = POLLRDNORM,
        .revents = 0
    };

    std::vector<pollfd_t> fds;
    fds.push_back(listen_fd);

    uint32_t count = 0;

    for (;;) {
        std::cerr << "Size of fds before poll " << fds.size() << '\n';

        int result = Poll(fds.data(), fds.size(), -1); // no timeout, wait indefinitely
        if (result < 0) {
            std::cerr << "Unable to poll, error: " << LastErrNo << '\n';
            return -1;
        }

        if (result == 0) // timeout
            continue;

        for (auto iter = fds.begin(); iter != fds.end(); ) {
            if (iter->revents & POLLRDNORM) {
                if (iter->fd == listen_socket) {
                    // accept new client connection

                    sockaddr_in client_addr{};
                    socklen_t client_addr_size = sizeof(client_addr);

                    socket_t client_socket = accept(listen_socket, reinterpret_cast<sockaddr*>(&client_addr), &client_addr_size);
                    if (client_socket == c_INVALID_SOCKET) {
                        std::cerr << "Unable to accept a client, error: " << LastErrNo << '\n';
                    }
                    else if (!MakeNonBlocking(client_socket)) {
                        std::cerr << "Unable to set client socket to non-blocking, error: " << LastErrNo << '\n';
                        CloseSocket(client_socket);
                    }
                    else {
                        char client_ip[INET_ADDRSTRLEN] = {};
                        inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
                        std::cerr << "\n\tClient IP addr: " << client_ip << ", port: " << ntohs(client_addr.sin_port) << '\n';

                        pollfd_t client_fd {
                            .fd = client_socket,
                            .events = POLLRDNORM,
                            .revents = 0
                        };

                        //auto offset = std::distance(fds.begin(), iter);
                        fds.push_back(client_fd);
                        iter = fds.begin() /* + offset */;
                    
                        std::cerr << "\tSize of fds after insert: " << fds.size() << ", client: " << client_socket << "\n\n";
                    }
                }
                else {
                    // read from client connection

                    std::cerr << "Some activity from client: " << iter->fd << "\n\n";
                    char buffer[1024];

                    int bytes_read = recv(iter->fd, buffer, sizeof(buffer), 0);
                    if (bytes_read > 0) {
                        std::cerr << "Received data from client: " << iter->fd << ": ";
                        std::cerr.write(buffer, bytes_read);
                        std::cerr << "\n\n";

                        // TODO: parse the request, don't send a reply until the end of the request is actually received...
                        std::string msg = "Count = " + std::to_string(count++);
                        std::string response = "HTTP/1.1 200 OK\r\n";
                        response += "Content-Type: text/plain\r\n";
                        response += "Access-Control-Allow-Origin: *\r\n";
                        response += "Content-Length: " + std::to_string(msg.size()) + "\r\n\r\n";
                        response += msg;

                        // TODO: if send() fails with EWOULDBLOCK/EGAIN,
                        // cache the unsent data and wait for poll() to
                        // tell you when it is OK to keep sending it ...
                        send(iter->fd, response.c_str(), response.size(), 0);
                    }
                    else if (bytes_read == 0) {
                        iter->revents |= POLLHUP;
                    }
                    else if ((LastErrNo != c_EWOULDBLOCK) && (LastErrNo != c_EAGAIN)) {
                        iter->revents |= POLLERR;
                    }
                }
            }

            if (iter->revents & (POLLERR | POLLHUP)) {
                if (iter->fd != listen_socket) {
                    if (iter->revents & POLLHUP) {
                        std::cerr << "Client disconnected: " << iter->fd << '\n';
                    } else {
                        std::cerr << "Unable to read from client: " << iter->fd << ", error: " << LastErrNo << '\n';
                    }
                    socket_t client_socket = iter->fd;
                    CloseSocket(client_socket);
                    iter = fds.erase(iter);
                    std::cerr << "\tSize of fds after remove: " << fds.size() << ", client: " << client_socket << "\n\n";
                    continue;
                }
            }

            ++iter;
        }
    }

    CloseSocket(listen_socket);

    CleanupSocketLib();

    return 0;
}

话虽如此,请注意WSAPoll 已损坏 开始。考虑 Windows 上的替代设计。您可以只使用

select()
代替,它在 Windows 上没有与在其他平台上相同的限制。或者,您可以改用重叠 I/O 或 I/O 完成端口。

© www.soinside.com 2019 - 2024. All rights reserved.