Загалом виявилось, що те про що я думав спочатку - неблокувальні сокети - це туфта. Що дійсно варто уваги, так це OVERLAPPED I/O. Тобто перетинний В/В, коли запити відбуваються асинхронно і час їх виконання перетинає один одного. Тут Microsoft дає можливість використовувати синхронізацію за допомогою WaitForSingle/MultipleObject(s). Що дуже зручно і я це й зробив із WSASend та WSARecv, але WSAConnect наразі не підтримує таку можливість, хоча і має зарезервований параметр для цього. Тому в наведеному коді використовується звичайний синхронний connect.
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#include <string>
#include <functional>
#include <memory>
#include <iostream>
using namespace std;
namespace
{
int ResolveHostName(const char* hostname, struct in_addr* addr)
{
struct addrinfo *res;
int result = getaddrinfo(hostname, NULL, NULL, &res);
if (result == 0) {
memcpy(addr, &((struct sockaddr_in *) res->ai_addr)->sin_addr,
sizeof(struct in_addr));
freeaddrinfo(res);
}
return result;
}
string GetWSALastErrorString(int errCode)
{
LPSTR errString;
int size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
0,
errCode,
0,
(LPSTR)&errString,
0,
0);
if (size != 0)
{
unique_ptr<CHAR, function<void(LPSTR)>> p(errString, [](LPSTR p) { LocalFree(p); });
return p.get();
}
return "";
}
}
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
const string server = "ocw.mit.edu";
// const string server = "en.wikipedia.org";
int port = 80;
string httpGet = "GET http://ocw.mit.edu/courses/mechanical-engineering/2-003sc-engineering-dynamics-fall-2011/newton2019s-laws-vectors-and-reference-frames/MIT2_003SCF11_Pset1_sol.pdf HTTP/1.0\r\n"
// string httpGet = "GET http://en.wikipedia.org HTTP/1.0\r\n"
"From: me@bigboss.com\r\n"
"User-Agent: HTTPTool/1.0\r\n"
"\r\n";
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
if (ResolveHostName(server.c_str(), &(address.sin_addr)) != 0) {
inet_pton(PF_INET, server.c_str(), &(address.sin_addr));
}
SOCKET sd = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
WSAOVERLAPPED overlapped;
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
int wsResult;
if (SOCKET_ERROR == ::connect(sd, (struct sockaddr*)&address, sizeof(address)))
{
cout << "Can't establish connection: " << GetWSALastErrorString(WSAGetLastError());
return 1;
}
DWORD wsFlags = 0;
int wsError = 0;
WSABUF wsaBufSend{ httpGet.length(), NULL };
DWORD bytesSend = -1;
{
wsaBufSend.buf = new char[httpGet.length() + 1];
strcpy_s(wsaBufSend.buf, httpGet.length() + 1, httpGet.c_str());
wsResult = WSASend(sd, &wsaBufSend, 1, &bytesSend, 0, &overlapped, NULL);
// TO BE CHECKED: We don't need to preserve this buffer till end of asynchronous I/O
delete[] wsaBufSend.buf;
}
if (wsResult != 0)
{
if ((wsResult == SOCKET_ERROR) && (WSA_IO_PENDING != (wsError = WSAGetLastError())))
{
cout << "Send failed with error: " << GetWSALastErrorString(wsError) << endl;
return 1;
}
if (WAIT_FAILED == WSAWaitForMultipleEvents(1, &overlapped.hEvent, TRUE, INFINITE, FALSE))
{
cout << "Wait failed with error: " << GetWSALastErrorString(WSAGetLastError()) << endl;
return 1;
}
if (FALSE == WSAGetOverlappedResult(sd, &overlapped, &bytesSend, FALSE, &wsFlags))
{
cout << "Recv failed with error: " << GetWSALastErrorString(WSAGetLastError()) << endl;
return 1;
}
}
const int BUF_SIZE = 1024*1024;
WSABUF wsaBufRecv{ BUF_SIZE, new char[BUF_SIZE] };
DWORD bytesRead = -1;
while (bytesRead != 0)
{
wsResult = WSARecv(sd, &wsaBufRecv, 1, &bytesRead, &wsFlags, &overlapped, NULL);
if (wsResult != 0)
{
if ((wsResult == SOCKET_ERROR) && (WSA_IO_PENDING != (wsError = WSAGetLastError())))
{
cout << "Recv failed with error: " << GetWSALastErrorString(wsError) << endl;
break;
}
if (WAIT_FAILED == WSAWaitForMultipleEvents(1, &overlapped.hEvent, TRUE, INFINITE, FALSE))
{
cout << "Wait failed with error: " << GetWSALastErrorString(WSAGetLastError()) << endl;
break;
}
if (FALSE == WSAGetOverlappedResult(sd, &overlapped, &bytesRead, FALSE, &wsFlags))
{
cout << "Recv failed with error: " << GetWSALastErrorString(WSAGetLastError()) << endl;
break;
}
}
wsaBufRecv.buf[bytesRead] = 0;
cout << wsaBufRecv.buf;
}
return 0;
}
Так обробка помилок після цих асинхронних викликів досить громіздка.