recv()
导致返回 0的唯一 2 个条件是:
如果您为它提供了一个长度为 0 的缓冲区。
如果对方已经优雅地关闭了连接。您可以使用数据包嗅探器(例如 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);
}