Question

when I want to use the same socket connected to the host to send() and recv() for the second time, recv() will return 0 without anything in buffer

basically Im doing:

  1. connect to the website
  2. send packets
  3. receive packets
  4. send packets again (I think this one is working since it's not giving me SOCKET_ERROR)
  5. receive packets again (this one is returning 0 so as "connection closed")

source code: http://pastebin.com/sm5k5GAe

as you can see, I have sock2, which when I use for second send/recv it's working fine, however I will do more communication, and having sockets for all of that and connecting them would be kinda stupid I guess

Was it helpful?

Solution

The only 2 conditions that cause recv() to return 0 are:

  1. if you provided it with a 0-length buffer.

  2. if the other party has gracefully closed the connection on its end. You can use a packet sniffer, such as Wireshark, to verify this.

You are sending HTTP 1.1 requests that include a Connection: close header in them. That tells an HTTP server to close its end of the connection after sending the response. That is perfectly normal behavior. Simply read the response, close your end of the connection, and reconnect before sending the next request.

If you want to keep the connection open so you can send multiple requests over a single connection, send a Connection: keep-alive header instead, or just omit the Connection header altogether since you are sending HTTP 1.1 requests and keep-alive is the default behavior for HTTP 1.1.

Either way, you must look at the server's actual response Connection header to know if it is going to close its end of the connection or not. For an HTTP 0.9 or 1.0 response, if there is no Connection: keep-alive header present then you must assume the connection is going to be closed. For an HTTP 1.1 response, if there is no Connection: close header then you must assume the connection is being left open. But you do have to be prepared to handle the possibility that the connection may still be closed, such as by a intermediate router/firewall, so simply reconnect if the next request fails with a connection error.

With that said, you are better off not implementing HTTP manually, but instead using a pre-made library that handles these details for you, such as libcurl.

Update:

Try something more like this instead:

#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);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top