【问题标题】:Winsock2.h can't send http requestsWinsock2.h 无法发送 http 请求
【发布时间】:2018-05-23 11:00:43
【问题描述】:

向各位程序员致敬,

我正在尝试编写一个程序,允许您输入您想要的内容,该程序会将您的输入发送到服务器。

目前,我的目标是向网页发送 HTTP 请求。它连接良好。但是当 while 循环运行时立即通过 cin.getline 过程发送一些东西,而我没有输入任何东西。我认为这很奇怪,但它似乎仍然有效。

每次我发送类似“GET / HTTP/1.1\r\n\r\n”的内容时,它都会返回正确的内容,但我输入的任何其他内容,例如“OPTIONS”都会返回源代码 +“应用程序被阻止” “(我在学校,所以这是有道理的)。

所以,我连接到热点屏蔽 VPN 并测试了应用程序,但令我惊恐的是,当我输入要发送的内容时,它什么也没返回。

我通过堆栈溢出和谷歌搜索,但到目前为止我还没有找到任何东西;可能是因为我正在寻找问题的错误解决方案。 无论如何,如果您有时间,请扫描代码发送一些帮助。这可能只是 VPN 和学校的问题,如果代码似乎对你有用,我可以在家尝试,所以请告诉我。

问题的具体概述: 当我在学校网络之外使用它时,没有返回任何内容,并且 while 循环似乎没有执行。我可以连接,但程序似乎无限超时或什么的。

    cout << "Connected to " << hostName << endl;



    while (true) {
        cout << ">";


        cin.getline(sendBuf, sizeof(sendBuf));
        string s(sendBuf);


        cout << s.c_str() << endl;


            send(connectSocket, s.c_str(), sizeof(s.c_str()), 0);

            int rec = recv(connectSocket, recvBuf, sizeof(recvBuf), 0);
            if (rec > 0) {
                cout << recvBuf << endl;
            }
            else if (rec <= 0) {
                cout << "nothing" << endl;
            }
        }

    system("pause");

}
system("pause");
}

【问题讨论】:

    标签: c++ http networking winsock2


    【解决方案1】:

    我的目标是向网页发送 HTTP 请求

    您展示的代码并没有尝试实现HTTP protocol 的任何相似之处,甚至没有接近。

    一方面,如果您更仔细地查看自己的示例,您会发现 GET 请求(顺便说一句,由于您使用 HTTP 1.1,缺少必需的 Host 标头)包含 2 行休息,但cin.getline()(为什么不std::getline()?)一次只读取1行。所以,你读了一行,发送它,然后等待一个没有到达的响应,因为你还没有完成发送一个完整的请求。这可以解释为什么您的 while 循环挂起。

    如果您希望用户输入完整的 HTTP 请求,然后按原样发送,则必须读取用户的整个请求,然后将其完全发送到服务器,然后才能尝试接收服务器的响应。这意味着您必须处理各个消息头之间的换行符,处理将消息头与消息正文分开的终止换行符,并检测正文数据的结尾。

    我建议不要依赖用户按原样输入完整的 HTTP 请求。我建议您提示用户输入相关内容并让用户键入普通文本,然后您的代码可以根据需要将该文本格式化为适当的 HTTP 请求。

    当您读取服务器的响应时,您不能只是盲目地读取任意数据块。您必须根据 HTTP 协议的规则处理您阅读的内容。这对于确定您何时到达响应结束并需要停止阅读尤其重要。如RFC 2616 Section 4.4 Message Length 中所述,可以通过多种不同方式之一发出响应结束的信号。

    一般来说,您在处理 TCP 时也会犯一些常见的新手错误。 TCP 是一种流传输,您没有考虑到 send()recv() 可以发送/接收比请求更少的字节。或者 recv() 不返回以 null 结尾的数据。

    话虽如此,尝试这样的事情:

    void sendAll(SOCKET sckt, const void *buf, int buflen)
    {
        // send all bytes until buflen has been sent,
        // or an error occurs...
    
        const char *pbuf = static_cast<const char*>(buf);
    
        while (buflen > 0)
        {
            int numSent = send(sckt, pbuf, buflen, 0);
            if (numSent < 0) {
                std::ostringstream errMsg;
                errMsg << "Error sending to socket: " << WSAGetLastError();
                throw std::runtime_error(errMsg.str());
            }
            pbuf += numSent;
            buflen -= numSent;
        }
    }
    
    int readSome(SOCKET sckt, void *buf, int buflen)
    {
        // read as many bytes as possible until buflen has been received,
        // the socket is disconnected, or an error occurs...
    
        char *pbuf = static_cast<char*>(buf);
        int total = 0;
    
        while (buflen > 0)
        {
            int numRecvd = recv(sckt, pbuf, buflen, 0);
            if (numRecvd < 0) {
                std::ostringstream errMsg;
                errMsg << "Error receiving from socket: " << WSAGetLastError();
                throw std::runtime_error(errMsg.str());
            }
            if (numRecvd == 0) break;
            pbuf += numRecvd;
            buflen -= numRecvd;
            total += numRecvd;
        }
    
        return total;
    }
    
    void readAll(SOCKET sckt, void *buf, int buflen)
    {
        // read all bytes until buflen has been received,
        // or an error occurs...
    
        if (readSome(sckt, buf, buflen) != buflen)
            throw std::runtime_error("Socket disconnected unexpectedly");
    }
    
    std::string readLine(SOCKET sckt)
    {
        // read a line of characters until a line break is received...
    
        std::string line;
        char c;
    
        do
        {
            readAll(sckt, &c, 1);
            if (c == '\r')
            {
                readAll(sckt, &c, 1);
                if (c == '\n') break;
                line.push_back('\r');
            }
            else if (c == '\n') {
                break;
            }
            line.push_back(c);
        }
        while (true);
    
        return line;
    }
    
    ...
    
    inline void ltrim(std::string &s) {
        // erase whitespace on the left side...
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
            return !std::isspace(ch);
        }));
    }
    
    inline void rtrim(std::string &s) {
        // erase whitespace on the right side...
        s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
            return !std::isspace(ch);
        }).base(), s.end());
    }
    
    inline void trim(std::string &s) {
        // erase whitespace on both sides...
        ltrim(s);
        rtrim(s);
    }
    
    inline void upperCase(std::string &s)
    {
        // translate all characters to upper-case...
        std::transform(s.begin(), s.end(), s.begin(), ::toupper);
    }
    
    ...
    
    std::string makeRequest(const std::string &host, const std::string &method, const std::string &resource, const std::vector<std::string> &extraHeaders, const void *body, int bodyLength)
    {
        std::ostringstream oss;
        oss << method << " " << resource << " HTTP/1.1\r\n";
        oss << "Host: " << host << "\r\n";
        oss << "Content-Length: " << bodyLength << "\r\n";
        for(auto &hdr : extraHeaders)
        {
            // TODO: ignore Host and Content-Length...
            oss << hdr << "\r\n";
        }
        oss << "\r\n";
        oss.write(static_cast<const char*>(body), bodyLength);
        return oss.str();
    }
    
    bool getHeaderValue(const std::vector<std::string> &headers, const std::string &headerName, std::string &value)
    {
        value.clear();
    
        std::string toFind = headerName;
        upperCase(toFind);
    
        // find the requested header by name...
        for(auto &s : headers)
        {
            std::string::size_type pos = s.find(':');
            if (pos != std::string::npos)
            {
                std::string name = s.substr(0, pos-1);
                trim(name);
                upperCase(name);
                if (name == toFind)
                {
                    // now return its value...
                    value = s.substr(pos+1);
                    trim(value);
                    return true;
                }
            }
        }
    
        // name not found
        return false;
    }
    
    ...
    
    std::cout << "Connected to " << hostName << std::endl;
    
    try
    {
        std::string method, resource, hdr, data;
        std::string status, version, reason;
        std::vector<std::string> headers;
        int statusCode, rec;
    
        do
        {
            headers.clear();
            data.clear();
    
            // get user input
        
            std::cout << "Method > " << std::flush;
            if (!std::getline(std::cin, method))
                throw std::runtime_error("Error reading from stdin");
            upperCase(method);
    
            std::cout << "Resource > " << std::flush;
            if (!std::getline(std::cin, resource))
                throw std::runtime_error("Error reading from stdin");
    
            std::cout << "Extra Headers > " << std::flush;
            while (std::getline(std::cin, hdr) && !hdr.empty())
                headers.push_back(hdr);
            if (!std::cin)
                throw std::runtime_error("Error reading from stdin");
    
            std::cout << "Data > " << std::flush;
            // use Ctrl-Z or Ctrl-D to end the data, depending on platform...
            std::ios_base::fmtflags flags = std::cin.flags();
            std::cin >> std::noskipws;
            std::copy(std::istream_iterator<char>(std::cin), std::istream_iterator<char>(), std::back_inserter(data));
            if (!std::cin)
                throw std::runtime_error("Error reading from stdin");
            std::cin.flags(flags);
            std::cin.clear();
    
            // send request
    
            std::string request = makeRequest(hostName, method, resource, headers, data.c_str(), data.length());
            std::cout << "Sending request: << std::endl << request << std::endl;
    
            // TODO: reconnect to hostName if previous request disconnected...
            sendAll(connectSocket, request.c_str(), request.length());
    
            // receive response
        
            headers.clear();
            data.clear();
    
            // read the status line and parse it...
            status = readLine(connectSocket);
            std::cout << status << std::endl;    
            std::getline(std::istringstream(status) >> version >> statusCode, reason);
            upperCase(version);
    
            // read the headers...
            do
            {
                hdr = readLine(connectSocket);
                std::cout << hdr << std::endl;
                if (hdr.empty()) break;
                headers.push_back(hdr);
            }
            while (true);
    
            // The transfer-length of a message is the length of the message-body as
            // it appears in the message; that is, after any transfer-codings have
            // been applied. When a message-body is included with a message, the
            // transfer-length of that body is determined by one of the following
            // (in order of precedence):
    
            // 1. Any response message which "MUST NOT" include a message-body (such
            // as the 1xx, 204, and 304 responses and any response to a HEAD
            // request) is always terminated by the first empty line after the
            // header fields, regardless of the entity-header fields present in
            // the message.
            if (((statusCode / 100) != 1) &&
                (statusCode != 204) &&
                (statusCode != 304) &&
                (method != "HEAD"))
            {
                // 2. If a Transfer-Encoding header field (section 14.41) is present and
                // has any value other than "identity", then the transfer-length is
                // defined by use of the "chunked" transfer-coding (section 3.6),
                // unless the message is terminated by closing the connection.
                if (getHeaderValue(headers, "Transfer-Encoding", hdr))
                    upperCase(hdr);
                if (!hdr.empty() && (hdr != "IDENTITY"))
                {
                    std::string chunk;
                    std::string::size_type oldSize, size;
    
                    do
                    {
                        chunk = readLine(connectSocket);
    
                        std::istringstream(chunk) >> std::hex >> size;
                        if (size == 0) break;
    
                        oldSize = data.size();
                        chunkData.resize(oldSize + size);
                        readAll(connectSocket, &data[oldSize], size);
                        std::cout.write(&data[oldSize], size);
    
                        readLine(connectSocket);
                    }
                    while (true);
    
                    std::cout << std::endl;
    
                    do
                    {
                        hdr = readLine(connectSocket);
                        std::cout << hdr << std::endl;
                        if (hdr.empty()) break;
                        headers.push_back(hdr);
                    }
                    while (true);
                }
    
                // 3. If a Content-Length header field (section 14.13) is present, its
                // decimal value in OCTETs represents both the entity-length and the
                // transfer-length. The Content-Length header field MUST NOT be sent
                // if these two lengths are different (i.e., if a Transfer-Encoding
                // header field is present). If a message is received with both a
                // Transfer-Encoding header field and a Content-Length header field,
                // the latter MUST be ignored.
                else if (getHeaderValue(headers, "Content-Length", hdr))
                {
                    std::string::size_type size;
                    if ((std::istringstream(hdr) >> size) && (size > 0))
                    {
                        data.resize(size);
                        readAll(connectSock, &data[0], size);
                        std::cout << data;
                    }
                }
    
                // 4. If the message uses the media type "multipart/byteranges", and the
                // transfer-length is not otherwise specified, then this self-
                // delimiting media type defines the transfer-length. This media type
                // MUST NOT be used unless the sender knows that the recipient can parse
                // it; the presence in a request of a Range header with multiple byte-
                // range specifiers from a 1.1 client implies that the client can parse
                // multipart/byteranges responses.
                else if (getHeaderValue(headers, "Content-Type", hdr) &&
                        (hdr.compare(0, 10, "multipart/") == 0))
                {
                    // TODO: extract 'boundary' attribute and read from
                    // socket until the terminating boundary is reached...
                }
    
                // 5. By the server closing the connection.
                else
                {
                    do
                    {
                        rec = readSome(connectSocket, recvBuf, sizeof(recvBuf));
                        if (rec == 0) break;
                        data.append(recvBuf, rec);
                        std::cout.write(recvBuf, rec);
                    }
                    while (rec == sizeof(recvBuf));
                }
            }
    
            std::cout << std::endl;
    
            // use status, headers, and data as needed ...
    
            getHeaderValue(headers, "Connection", hdr);
            upperCase(hdr);
    
            if (version == "HTTP/1.0")
            {
                if (hdr != "KEEP-ALIVE")
                    break;
            }
            else
            {
                if (hdr == "CLOSE")
                    break;
            }
        }
        while (true);
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << std::endl;
    }
    
    closesocket(connectSocket);
    std::cout << "Disconnected from " << hostName << std::endl;
    
    std::system("pause");
    

    HTTP 不是很有趣吗? :-) 到目前为止,这还不是一个完整的 HTTP 实现,但它应该可以帮助您入门。但是,如您所见,从头开始实施 HTTP 可能非常复杂,并且您必须遵守许多规则和限制。你最好不要手动实现 HTTP。有大量可用于 C++ 的 3rd 方 HTTP 库。改用其中一个,让他们为您处理繁重的工作,这样您就可以专注于自己的业务逻辑。

    【讨论】:

    • 感激不尽。我理解你告诉我的内容,并且也感觉到我太快地潜入了 C++ 的深处,所以我不得不再次点击 Bjarne Strousoup 的 C++ 圣经……;(。是的,HTTP 是有趣,但我认为我在阅读您的代码后还太新手,不能这样做,我也会查看 std 库并更好地掌握它。谢谢,
    猜你喜欢
    • 1970-01-01
    • 2022-01-15
    • 2016-05-12
    • 2021-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-09
    • 1970-01-01
    相关资源
    最近更新 更多