сокеты вопрос

у меня есть классы сервера и клиента, но проблема в том, что когда я делаю бесконечный цикл для приема входящего соединения, я не могу получить все данные, полученные от клиента, принимая соединения, потому что accept блокируется до тех пор, пока соединение не будет принято, мой код:

    for (;;) 
    {
       boost::thread thread(boost::bind(&Irc::Server::startAccept, &s));
       thread.join();
       for (ClientsMap::const_iterator it = s.begin(); it != s.end(); ++it) 
       {
          std::string msg = getData(it->second->recv());
          std::clog << "Msg: " << msg << std::endl;
       }
    }

person Community    schedule 13.11.2010    source источник
comment
Какой смысл начинать тему и тут же присоединяться к ней?   -  person Armen Tsirunyan    schedule 13.11.2010
comment
я думал, что join ждет завершения потока?   -  person    schedule 13.11.2010
comment
Это означает, что одновременно работает только один поток, что полностью противоречит цели.   -  person Ben Voigt    schedule 13.11.2010
comment
Вместо того, чтобы использовать Boost::thread вместе с (очевидно) каким-то другим кодом сокета, рассматривали ли вы использование ASIO (или это то, что уже использует ваш IRC::Server::startAccept)?   -  person Jerry Coffin    schedule 13.11.2010
comment
я использую обычные сокеты, но на самом деле у меня есть эта проблема и с asio   -  person    schedule 13.11.2010


Ответы (3)


Вам нужно либо несколько потоков, либо вызов select/poll, чтобы узнать, какие соединения имеют необработанные данные. У IBM есть хороший пример, который будет работать на любой разновидности Unix, Linux, BSD и т. д. (вам могут понадобиться разные заголовочные файлы в зависимости от ОС).

Прямо сейчас вы запускаете поток, а затем немедленно ожидаете его, что приводит к последовательному выполнению и полностью противоречит цели потоков.

person Ben Voigt    schedule 13.11.2010
comment
как мне это сделать, можете привести пример? - person ; 13.11.2010
comment
Вот пример использования select, я все еще ищу хороший вариант с опросом, который является более новым вариантом: gnu.org/s/libc/manual/html_node/ - person Ben Voigt; 14.11.2010
comment
Вот пример с poll, обработка ошибок, все работает: publib.boulder.ibm.com/infocenter/iseries/v6r1m0/ - person Ben Voigt; 14.11.2010
comment
Тем не менее, я обычно заканчиваю тем, что не использую ни сырой poll API, ни кросс-платформенную boost::asio оболочку, потому что они позволяют выполнять ввод-вывод только на множестве сокетов (на некоторых платформах также и в файлах). Обычно я заканчиваю тем, что использую функции сокетов, управляемые событиями, из моего инструментария пользовательского интерфейса (в Windows, WSAAsyncSelect, в Unix, XtAppAddInput), и таким образом один поток может отвечать на сетевые запросы и взаимодействие с пользователем чистым управляемым событиями способом. Немного больше работы (на самом деле немного) для настройки обратных вызовов событий, но это предотвращает условия гонки по дизайну. - person Ben Voigt; 14.11.2010
comment
Но это только мое предпочтение, так как я предпочитаю, чтобы на моих серверах был небольшой пользовательский интерфейс, показывающий, сколько подключений, кнопка выхода и тому подобное. Для системных служб, работающих в фоновом режиме, poll API сам по себе работает очень хорошо. - person Ben Voigt; 14.11.2010

Посмотрите здесь: http://www.boost.org/doc/libs/1_38_0/doc/html/boost_asio/examples.html

особенно пример HTTP Server 3, это именно то, что вы ищете, все, что вам нужно сделать, это немного изменить этот код для ваших нужд :) и все готово

person Marek Szanyi    schedule 13.11.2010
comment
да, конечно, каждый конец каждой асинхронной операции является неблокирующей операцией. В большинстве случаев, когда вы используете асинхронные операции (функции), вам даже не нужно использовать потоки вообще, это может упростить ваш дизайн + устраняет дополнительные накладные расходы на некоторое переключение контекста, вызванное добавленными потоками. - person Marek Szanyi; 13.11.2010
comment
HTTP-сервер, где соединения обслуживают запросы почти полностью независимо друг от друга, не является хорошей основой для использования чат-сервера. Но на этой boost::asio странице также есть пример чата. - person Ben Voigt; 14.11.2010

Хорошим подходом было бы создание одного потока, который принимает только новые соединения. Вот где у вас есть сокет слушателя. Затем для каждого принятого соединения у вас есть новый подключенный сокет, поэтому вы можете создать еще один поток, передав ему подключенный сокет в качестве параметра. Таким образом, ваш поток, который принимает соединения, не блокируется и может очень быстро подключаться ко многим клиентам. Потоки обработки имеют дело с клиентами, а затем завершаются.

Я даже не знаю, зачем их ждать, но если вы это сделаете, вы можете справиться с этим каким-то другим способом, в зависимости от используемой ОС и/или библиотек (могут использоваться сообщения, сигналы и т.д.).

Если вы не хотите создавать новый поток для каждого подключенного клиента, то, как предложил Бен Фойгт, вы можете использовать select. Это еще один хороший подход, если вы хотите сделать его однопоточным. По сути, все ваши сокеты будут в массиве дескрипторов сокетов, и с помощью select вы узнаете, что произошло (кто-то подключился, сокет готов для чтения/записи, сокет отключился и т. д.), и будете действовать соответственно.

Вот один пример Частичный, но работает. вы просто принимаете соединения в acceptConnections(), которые затем создают отдельный поток для каждого клиента. Вот где вы общаетесь с клиентами. Это из кода Windows, который у меня лежит, но его очень легко переделать для любой платформы.

typedef struct SOCKET_DATA_ {
  SOCKET sd;
  /* other parameters that you may want to pass to the clientProc */
} SOCKET_DATA;

/* In this function you communicate with the clients */
DWORD WINAPI clientProc(void * param)
{
    SOCKET_DATA * pSocketData = (SOCKET_DATA *)param;

    /* Communicate with the new client, and at the end deallocate the memory for
       SOCKET_DATA and return.
    */

    delete pSocketData;
    return 0;
}

int acceptConnections(const char * pcAddress, int nPort)
{
    sockaddr_in sinRemote;
    int nAddrSize;
    SOCKET sd_client;
    SOCKET sd_listener;
    sockaddr_in sinInterface;
    SOCKET_DATA * pSocketData;
    HANDLE hThread;

    sd_listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (INVALID_SOCKET == sd_listener) {
        fprintf(stderr, "Could not get a listener socket!\n");
        return 1;
    }

    sinInterface.sin_family = AF_INET;
    sinInterface.sin_port = nPort;
    sinInterface.sin_addr.S_un.S_addr = INADDR_ANY;

    if (SOCKET_ERROR != bind(sd_listener, (sockaddr*)&sinInterface, sizeof(sockaddr_in))) {
        listen(sd_listener, SOMAXCONN);
    } else {
        fprintf(stderr, "Could not bind the listening socket!\n");
        return 1;
    }

    while (1)
    {
        nAddrSize = sizeof(sinRemote);
        sd_client = accept(sd_listener, (sockaddr*)&sinRemote, &nAddrSize);

        if (INVALID_SOCKET == sd_client) {
            fprintf(stdout, "Accept failed!\n");
            closesocket(sd_listener);
            return 1;
        }

        fprintf(stdout, "Accepted connection from %s:%u.\n", inet_ntoa(sinRemote.sin_addr), ntohs(sinRemote.sin_port));
        pSocketData = (SOCKET_DATA *)malloc(sizeof(SOCKET_DATA));

        if (!pSocketData) {
            fprintf(stderr, "Could not allocate memory for SOCKET_DATA!\n");
            return 1;
        }

        pSocketData->sd = sd_client;
        hThread = CreateThread(0, 0, clientProc, pSocketData, 0, &nThreadID);

        if (hThread == INVALID_HANDLE_VALUE) {
            fprintf(stderr, "An error occured while trying to create a thread!\n");
            delete pSocketData;
            return 1;
        }
    }

    closesocket(sd_listener);
    return 0;
}
person Amy    schedule 13.11.2010
comment
Преимущество одного потока для каждого клиента заключается в том, что легко отслеживать независимое состояние для каждого клиента. Однако планирование всех потоков не так эффективно, как poll, и если у вас есть общие данные между клиентами, очень легко ввести условия гонки (а добавление мьютексов или другой синхронизации для исправления условий гонки еще больше ухудшит производительность). - person Ben Voigt; 14.11.2010