-1

当我想第二次使用连接到主机的同一个套接字来 send() 和 recv() 时,recv() 将返回 0 而缓冲区中没有任何内容

基本上我在做:

  1. 连接到网站
  2. 发送数据包
  3. 接收数据包
  4. 再次发送数据包(我认为这个正在工作,因为它没有给我 SOCKET_ERROR)
  5. 再次接收数据包(这个返回 0 表示“连接关闭”)

源代码: http: //pastebin.com/sm5k5GAe

如您所见,我有 sock2,当我将其用于第二次发送/接收时,它工作正常,但是我会进行更多的通信,并且为所有这些设置套接字并连接它们会有点愚蠢我猜

4

1 回答 1

1

recv()导致返回 0的唯一 2 个条件是:

  1. 如果您为它提供了一个长度为 0 的缓冲区。

  2. 如果对方已经优雅地关闭了连接。您可以使用数据包嗅探器(例如 Wireshark)来验证这一点。

您正在发送包含Connection: close标头的 HTTP 1.1 请求。这告诉 HTTP 服务器在发送响应后关闭其连接端。这是完全正常的行为。只需阅读响应,关闭连接,然后在发送下一个请求之前重新连接。

如果您想保持连接打开以便可以通过单个连接发送多个请求,请Connection: keep-alive改为发送标头,或者完全省略Connection标头,因为您正在发送 HTTP 1.1 请求并且keep-alive是 HTTP 1.1 的默认行为。

无论哪种方式,您都必须查看服务器的实际响应Connection标头以了解它是否要关闭其连接端。对于 HTTP 0.9 或 1.0 响应,如果不存在Connection: keep-alive标头,那么您必须假定连接将被关闭。对于 HTTP 1.1 响应,如果没有Connection: close标头,那么您必须假定连接处于打开状态。但是您必须准备好处理连接可能仍然关闭的可能性,例如中间路由器/防火墙,因此如果下一个请求因连接错误而失败,只需重新连接即可。

话虽如此,您最好不要手动实现 HTTP,而是使用为您处理这些细节的预制库,例如libcurl

更新:

尝试更多类似的东西:

#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <fstream>

using namespace std;

SOCKET sock;
string currentHost;

bool checkConnection(string Hostname);
int readFromSock(char *data, int datalen, bool disconnectOK = false);
int readData(string &workBuffer, char *data, int datalen, bool disconnectOK = false);
bool readLine(string &workBuffer, string &line);
bool doRequest(string Hostname, string Request, string &Reply);

bool GetReply(string Host, string &Reply);
bool GetLogin(string Reply, string MyStr, string &Login);
bool GetSession(string Reply, string MyStr, string &Session);
bool GetLoginReply(string Host, string Username, string Password, string Login, string Session, string &Reply);

bool Login(string Host, string Resource, string Username, string Password);
bool IsConnected(string Reply, string MyStr);

#pragma comment (lib, "ws2_32.lib")
#pragma warning(disable:4996)

int main()
{
    string sLoginSite, sUsername, sPassword;
    char buffer[32];

    //Initialize Winsock
    WSADATA WsaData;
    if(WSAStartup(MAKEWORD(2,2), &WsaData) != 0)
    {
        cout << "WinSock Startup Failed" << endl;
        system("pause");
        return 1;
    }

    sock = INVALID_SOCKET;

    //Get Login Site
    cout << "Travian login site e.g. ts1.travian.com: ";
    cin >> buffer;
    sLoginSite = buffer;
    cout << endl;

    //Get Username
    cout << "Username: ";
    cin >> buffer;
    sUsername = buffer;

    //Get Password
    cout << "Password: ";
    cin >> buffer;
    sPassword = buffer;
    cout << endl;

    // Perform Login
    if (!Login(sLoginSite, sUsername, sPassword))
    {
        cout << "Error while Logging in" << endl;
        system("pause");
        return 1;
    }

    cout << "Successfully connected to the account \"" << sUsername << "\" at " << sLoginSite << endl;
    system("pause");

    if (sock != INVALID_SOCKET)
        closesocket(sock);

    WSACleanup();

    return 0;
}

// ensure connection to Hostname
bool checkConnection(string Hostname)
{
    // Switching to a different hostname?  If so, disconnect...
    if (currentHost != Hostname)
    {
        if (sock != INVALID_SOCKET)
        {
            closesocket(sock);
            sock = INVALID_SOCKET;
        }

        currentHost = Host;
    }

    // TODO: make sure the socket is actually still connected. If not, disconnect...
    if (sock != INVALID_SOCKET)
    {
        /*
        if (no longer connected)
        {
            closesocket(sock);
            sock = INVALID_SOCKET;
        }
        */
    }

    // Create a new connection?
    if (sock == INVALID_SOCKET)
    {
        // resolve the Hostname...

        struct hostent *host = gethostbyname(Hostname.c_str());
        if(!host)
        {
            cout << "Can't Resolve Hostname" << endl;
            return false;
        }

        // Connect to the Hostname...

        SOCKET newSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (newSock == INVALID_SOCKET)
        {
            cout << "Error creating Socket" << endl;
            return false;
        }

        SOCKADDR_IN SockAddr;
        ZeroMemory(&SockAddr, sizeof(SockAddr)));
        SockAddr.sin_port = htons(80);
        SockAddr.sin_family = AF_INET;
        SockAddr.sin_addr.s_addr = *((unsigned long*)host->h_addr);

        cout << "Connecting" << endl;
        if(connect(newSock, (SOCKADDR*) &SockAddr, sizeof(SockAddr)) != 0)
        {
            closesocket(newSock);
            cout << "Can't Connect to Hostname" << endl;
            return false;
        }

        sock = newSock;
        cout << "Successfully connected to " << Hostname << "!" << endl;
    }

    // Ready
    return true;
}

// read raw data from the socket directly
// returns how many bytes were actually read, -1 on error, or 0 on disconnect
int readFromSock(char *data, int datalen, bool disconnectOK)
{
    int read = 0;

    while (datalen > 0)
    {
        // more data is expected...

        int ret = recv(sock, data, datalen, 0);
        if (ret == SOCKET_ERROR)
        {
            cout << "recv failed: " << WSAGetLastError() << endl;
            closesocket(sock);
            sock = INVALID_SOCKET;
            return -1;
        }

        if (ret == 0)
        {
            cout << "server disconnected" << endl;
            closesocket(sock);
            sock = INVALID_SOCKET;

            // if the caller is OK with a disconnect occurring, exit without error...
            if (disconnectOK)
                break;

            return -1;
        }

        // move forward in the output buffer
        data += ret;
        datalen -= ret;

        // increment the result value
        read += ret;
    }

    // done
    return read;
}

// read raw data from an in-memory buffer, reading from the socket directly only when the buffer is empty.
// returns how many bytes were actually read, -1 on error, or 0 on disconnect
int readData(string &workBuffer, char *data, int datalen, bool disconnectOK)
{
    int read = 0;

    int len;
    char buffer[512];

    while (datalen > 0)
    {
        // more data is expected...

        len = workBuffer.length();
        if (len > 0)
        {
            // the work buffer has cached data, move to the output buffer...

            if (len > datalen) len = datalen;
            workBuffer.copy(data, len);
            workBuffer.erase(len);

            // move forward in the output buffer
            data += len;
            datalen -= len;

            // increment the return value
            read += len;
        }
        else
        {
            // the work buffer is empty, read from the socket and cache it...

            len = readFromSock(buffer, sizeof(buffer), disconnectOK);
            if (ret == -1)
                return -1;

            // disconnected?
            if (ret == 0)
                break;

            // append new data to the work buffer...
            workBuffer += string(buffer, ret);
        }
    }

    // done
    return read;
}

// reads a LF-delimited line of text from an in-memory buffer, reading from the socket directly only when the buffer is empty.
// returns whether a full line was actually read.
bool readLine(string &workBuffer, string &line)
{
    // clear the output...
    line = "";

    int found, len, start = 0;
    char buffer[512];

    do
    {
        // check if a LF is already cached. Ignore previously searched text..
        found = workBuffer.find("\n", start);
        if (found != string::npos)
        {
            len = found;

            // is the LF preceded by a CR? If so, do include it in the output...
            if (len > 0)
            {
                if (workBuffer[len-1] == '\r')
                    --len;
            }

            // output the line, up to but not including the CR/LF...
            line = workBuffer.substr(0, len);
            workBuffer.erase(found);

            break;
        }

        // the work buffer does not contain a LF, read from the socket and cache it...

        len = readFromSock(buffer, sizeof(buffer));
        if (len <= 0)
        {
            closesocket(sock);
            sock = INVALID_SOCKET;
            return false;
        }

        // append new data to the work buffer and search again...
        start = workBuffer.length();
        workBuffer += string(buffer, len);
    }
    while (true);

    // done
    return true;
}

// perform an HTTP request and read the reply
// returns whether the reply was actually read
bool doRequest(string Hostname, string Request, string &Reply)
{
    // clear the output
    Reply = "";

    char buffer[512];
    string str, workBuffer;
    string sContentLength, sTransferEncoding, sConnection;
    bool doClose;

    // make sure there is a connection, reconnecting if needed...
    if (!checkConnection(Hostname))
        return false;

    // send the request...

    char *data = Request.c_str();
    int len = Request.length();
    int ret;

    do
    {
        ret = send(sock, data, len, 0);
        if (ret == SOCKET_ERROR)
        {
            cout << "Send Error" << endl;
            closesocket(sock);
            sock = INVALID_SOCKET;
            return false;
        }

        // move forward in the input buffer...
        data += ret;
        len -= ret;
    }
    while (len > 0);

    // read the response's status line...
    if (!readLine(workBuffer, str))
        return false;

    // TODO: parse out the line's values, ie: "200 OK HTTP/1.1"
    int ResponseNum = ...;
    int VersionMajor = ...;
    int VersionMinor = ...;

    // only HTTP 1.0 responses have headers...
    if (VersionMajor >= 1)
    {    
        // read the headers until a blank line is reached...

        do
        {
            // read a header
            if (!readLine(workBuffer, str))
                return false;

            // headers finished?
            if (str.length() == 0)
                break;

            // TODO: do case-insensitive comparisons
            if (str.compare(0, 15, "Content-Length:") == 0)
                sContentLength = str.substr(15);
            else if (str.compare(0, 18, "Transfer-Encoding:") == 0)
                sTransferEncoding = str.substr(18);
            else if (str.compare(0, 18, "Connection:") == 0)
                sConnection = str.substr(11);
        }
        while (true);

        // If HTTP 1.0, the connection must closed if the "Connection" header is not "keep-alive"
        // If HTTP 1.1+, the connection must be left open the "Connection" header is not "close"

        // TODO: do case-insensitive comparisons
        if ((VersionMajor == 1) && (VersionMinor == 0))
            doClose = (sConnection.compare"keep-alive") != 0);
        else
            doClose = (sConnection.compare("close") == 0);
    }
    else
    {
        // HTTP 0.9 or earlier, no support for headers or keep-alives
        doClose = true;
    }

    // TODO: do case-insensitive comparisons
    if (sTransferEncoding.compare(sTransferEncoding.length()-7, 7, "chunked") == 0)
    {
        // the response is chunked, read the chunks until a blank chunk is reached...

        do
        {
            // read the chunk header...
            if (!readLine(workBuffer, str))
                return false;

            // ignore any extensions for now...
            int found = str.find(";");
            if (found != string::npos)
                str.resize(found);

            // convert the chunk size from hex to decimal...
            size = strtol(str.c_str(), NULL, 16);

            // chunks finished?
            if (size == 0)
                break;

            // read the chunk's data...
            do
            {
                len = size;
                if (len > sizeof(buffer)) len = sizeof(buffer);

                if (!readData(workBuffer, buffer, len))
                    return false;

                // copy the data to the output
                Reply += string(buffer, len);
                size -= len;
            }
            while (size > 0);

            // the data is followed by a CRLF, skip it...
            if (!readLine(workBuffer, str))
                return false;
        }
        while (true);

        // read trailing headers...

        do
        {
            // read a header...
            if (!readLine(workBuffer, str))
                return false;

            // headers finished?
            if (str.length() == 0)
                break;

            // process header as needed, overwriting HTTP header if needed...
        }
        while (true);
    }
    else if (sContentLength.length() != 0)
    {
        // the response has a length, read only as many bytes as are specified...

        // convert the length to decimal...
        len = strtol(sContentLength.c_str(), NULL, 10);

        if (len > 0)
        {
            // read the data...

            do
            {
                ret = len;
                if (ret > sizeof(buffer)) ret = sizeof(buffer);

                ret = readData(workBuffer, buffer, ret);
                if (ret <= 0)
                    return false;

                // copy the data to the output
                Reply += string(buffer, ret);
                len -= ret;
            }
            while (len > 0);
        }
    }
    else
    {
        // response is terminated by a disconnect...

        do
        {
            len = readData(workBuffer, buffer, sizeof(buffer), true);
            if (len == -1)
                return false;

            // disconnected?
            if (len == 0)
                break;

            // copy the data to the output
            Reply += string(buffer, len);
        }
        while (true);

        doClose = true;
    }

    // close the socket now?
    if (doClose)
    {
        closesocket(sock);
        sock = INVALID_SOCKET;
    }

    // TODO: handle other responses, like 3xx redirects...

    return ((ResponseNum / 100) == 2);
}

// Login to Hostname with Username and Password
// returns whether login was successful or not
bool Login(string Hostname, string Username, string Password)
{
    string sLoginForm, sReply, sSessionID, sLoginID, sLoginReply;

    //Get Login Form HTML
    if (!GetReply(Hostname, sReply))
    {
        cout << "Reply Error" << endl;
        return false;
    }

    //Save Reply
    ofstream Data;
    Data.open("Reply.txt");
    Data << sReply;
    Data.close();

    //Get Session ID from HTML
    if (!GetSession(sReply, "sess_id", sSessionID))
    {
        cout << "Session ID Error" << endl;
        return false;
    }

    //Get Login ID from HTML
    if (!GetLogin(sReply, "<input type=\"hidden\" name=\"login\" value=", sLoginID))
    {
        cout << "Login ID Error" << endl;
        return false;
    }

    // perform final Login
    if (!GetLoginReply(Hostname, Username, Password, sLoginID, sSessionID, sLoginReply))
    {
        cout << "Login Reply Error" << endl;
        return false;
    }

    /*
    if(!IsConnected(sLoginReply, "HTTP/1.1 303 See Other"))
    {
        cout << "Invalid Username or Password" << endl;
        return false;
    }
    */

    //Save Reply
    Data.open("login.txt");
    Data << sLoginReply;
    Data.close();

    // done
    return true;
}

bool GetReply(string Hostname, string &Reply)
{
    string str;

    str = "GET / HTTP/1.1\r\n";
    str += "Host: " + Hostname + "\r\n"
    str += "Connection: keep-alive\r\n"
    str += "\r\n";

    return doRequest(Hostname, str, Reply);
}

bool GetSession(string Reply, string MyStr, string &Session)
{
    int found = Reply.find(MyStr);
    if(found == string::npos)
        return false;

    Session = Reply.substr(found+MyStr.Length(), 32);
    return true;
}

bool GetLogin(string Reply, string MyStr, string &Login)
{
    int found = Reply.find(MyStr);
    if(found == string::npos)
        return false;

    Login = Reply.substr(found+MyStr.length(), 10)
    return true;
}

bool GetLoginReply(string Hostname, string Username, string Password, string Login, string Session, string &Reply)
{
    string str, special;

    special = "name=" + Username + "&password=" + Password + "&s1=Login&w=1600%3A900&login=" + Login;

    string temp;
    stringstream ss;
    ss << special.length();
    ss >> temp;

    str = "POST /dorf1.php HTTP/1.1\r\n";
    str += "Host: " + Hostname + "\r\n";
    str += "Cookie: sess_id=" + Session + "; highlightsToggle=false; WMBlueprints=%5B%5D\r\n";
    str += "Content-Type: application/x-www-form-urlencoded\r\n";
    str += "Content-Length: " + temp + "\r\n"
    str += "\r\n";
    str += special;

    return doRequest(Hostname, str, Reply);
}

bool IsConnected(string Reply, string MyStr)
{
    return (Reply.find(MyStr) != string::npos);
}
于 2013-07-30T18:03:22.900 回答