1

Тема: Багатопотоковий мережевий павук

Потрібно написати такого павука, пишу під Windows на VC++.

Питання в тому як організувати багатопотоковість. Є два види сокетів, блокувальні і неблокувальні. Якщо я використовую блокувальні - все зрозуміло. кожне завантаження в окремому потоці. А якщо ні, то коли я очікую на на дані, чи завантажена система зчитуванням із сокета, чи це відбувається десь на controller'і і процесор в цей час гуляє.

Тобто я думаю використати неблокувальні сокети і по мірі надходження пакетів записувати їх куди треба і звідти для подальшої обробки їх вибиратимуть потоки призначені для цього, а сама робота з сокетами відбуватиметься в одному потоці.

Дякую.

2

Re: Багатопотоковий мережевий павук

Я не великий фахівець з цього питання, проте чув що подіє-орієнтований підхід краще за все - select() з FD_ZERO, FD_SET

Подякували: Yola1

3 Востаннє редагувалося 0x9111A (10.09.2015 21:00:21)

Re: Багатопотоковий мережевий павук

Десь читав що Epoll швидко працює, проте сам з ним діла не мав.

4

Re: Багатопотоковий мережевий павук

0x9111A написав:

Десь читав що Epoll швидко працює, проте сам з ним діла не мав.

Та ми розуміємо англійську, трохи :)

epoll is a Linux kernel system call, a scalable I/O event notification mechanism.

Але ж я пишу під Windows.

Подякували: 0xDADA11C71

5 Востаннє редагувалося 0x9111A (11.09.2015 09:23:16)

Re: Багатопотоковий мережевий павук

Тхю точно, для реабілітації можна було б ляпнути щось про boost::asio (рандомний асинхронний код) але ну його

Подякували: 0xDADA11C7, Yola2

6

Re: Багатопотоковий мережевий павук

Загалом виявилось, що те про що я думав спочатку - неблокувальні сокети - це туфта. Що дійсно варто уваги, так це 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;
}

Так обробка помилок після цих асинхронних викликів досить громіздка.

Подякували: 0xDADA11C71

7

Re: Багатопотоковий мережевий павук

Ну, власне, ось - https://github.com/Harmyder/HttpSpider

Подякували: 0xDADA11C71