The only 2 conditions that cause recv()
to return 0 are:
if you provided it with a 0-length buffer.
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);
}