LinuxForum.Fans
Тема

[РЕШЕНО] (С++) BSD Sockets, pthreads и куча всего...

Автор: V0idExp 12 сообщений 3705 просмотров 🔒 закрыта
#31956

Привет всем гуру программирования сетевых приложений! Пишу простенький telnet клиент в качестве упражнения, и решил воспользоваться такой фишкой как многопоточность. Есть два потока: один занимается приемом от пользователя команд с клавиатуры ( cin.getline() ) и отсыланием их на сервер, другой же принимает данные с сервера и печатает на консоль. Все общение client-server происходит через объект класса NVT (Network Virtual Terminal), который является простой оберткой над BSD Sockets API. По идее, когда один поток ждет от пользователя команд, другой каждую секунду опрашивает сервер и печатает данные, если они были им посланы. В теории, эти два потока выполняются параллельно и благополучно общаются, синхронизируясь через mutex-ы. Вот фрагмент кода:

text
boost::mutex    nvtMutex;
boost::mutex    quitMutex;
bool            quit = false;

ПОТОК1: void *processServerOutput(void *arg) { NVT *nvt = (NVT*)arg; while(1) { // Lock the quitMutex before trying to access to quit variable quitMutex.lock(); if(quit) { quitMutex.unlock(); pthread_exit(NULL); } else quitMutex.unlock(); // Receive output from server nvtMutex.lock(); nvt->receive(); cout << Util::Instance()->iconv("koi8-r", "utf-8", nvt->getOutBuffer()); // convert the output from KOI8-R to UTF-8 nvtMutex.unlock(); // Delay sleep(1); } }

ПОТОК2: void *processUserInput(void *arg) { NVT *nvt = (NVT*)arg; while(1) { // Get user's input cin.getline(str, 1023); // If we type 'quit', exit from thread if(strcmp(str, "quit") == 0) { // Lock quit variable before trying to modify it quitMutex.lock(); quit = true; quitMutex.unlock(); // Exit from thread pthread_exit(NULL); } // Send the input to server nvtMutex.lock(); nvt->writeUserCommand(Util::Instance()->iconv("utf-8", "koi8-r", str)); // Convert the output from UTF-8 to KOI8-R nvt->send(); nvtMutex.unlock(); } }

NVT->receive() и NVT->send() являются обертками над операциями с сокетами send() и recv(). Проблема в том, что когда сервер в ожидании и не отправляет никаких данных, ОБА потока зависают! То есть, выполнение приостанавливается на NVT->receive()! В чем же тогда суть многопоточности? Потестировав, заметил что ПОТОК1 выполняется бесконечно если закомментировать строку NVT->receive(), когда ПОТОК2 в ожидании ввода. Но почему-же ПОТОК2 зависает когда ПОТОК1 ожидает данных с сервера?

#32060

Во-первых, неизвестно, как работает ваша receive(), а сокет может быть
в блокирующем режиме или нет,
во-вторых, попробуйте на период отладки имитировать работу getline().
Но сам я в этом не специалист, своих примеров у меня нет.

Пётр.

#32083

receive() является просто оберткой над recv(), с фиксированным размером буфера (4096 байт). Сокет работает в блокирующем режиме, и мне как раз интересно почему эта блокировка останавливает оба потока, вместо одного processServerOutput().

#32086

Так сделайте в неблокирующем, раз у вас там sleep(1).
И у вас же там захватывается mutex перед receive().
И ещё раз: попробуйте вместо getline() её имитировать
и не вызывать send(), если ничего не принято.

Пётр.

#32098

Как работает boost с блокировкой большой вопрос. Подождите, может, кто-то появится, кто с ним работал...

#32151

Нет, причина явно не в Boost, в этом я уверен. Тем более что из Boost я использовал только класс mutex. В общем, где-то в окрестностях MSDN прочитал что в их реализации сокетов "внутренности" находятся в глобальной видимости, то есть на уровне процесса... Может быть что recv() подвешивает весь процесс, в ожидании данных с сервера? Не знаю насколько это логично и возможно-ли вообще...

#32171
V0idExp написал:
Но почему-же ПОТОК2 зависает когда ПОТОК1 ожидает данных с сервера?

А по каким признакам Вы делаете вывод, что он зависыет? Я так полагаю, что Вы просто блочите первый поток на receive, после чего он не может попасть в начало цикла и среагировать на quit.

А вообще, это проще сделать без мьютексов, используя poll.

#32181
Aesthetus Animus написал:
[quote=V0idExp]Но почему-же ПОТОК2 зависает когда ПОТОК1 ожидает данных с сервера?

А по каким признакам Вы делаете вывод, что он зависыет? Я так полагаю, что Вы просто блочите первый поток на receive, после чего он не может попасть в начало цикла и среагировать на quit.

А вообще, это проще сделать без мьютексов, используя poll.[/quote]
Я тупо после каждой блокировки mutex-а и после каждой разблокировки поставил банальное

text
cout << "OUTPUT THREAD: 'nvt' locked" << endl;

или

text
cout << "INPUT THREAD: 'quit' unlocked" << endl;

и т.п. Все работает нормально пока есть ответ с сервера или я за 1 секунду успеваю отправить запрос на сервер, заставив его сгенерировать ответ. Таким образом каждую секунду происходят lock-и и unlock-и в ПОТОКЕ 1, что в принципе и правильно. Но как только входных данных с сервера больше нет, то все зависает на "OUTPUT THREAD: 'nvt' locked", а значит на receive().

#32184

Ну, вообще-то recv() не закончит работу пока данные не придут. Так и будет висеть и ждать.

#32193

Ну я не возражаю чтобы он висел и ждал :) Пусть себе висит сколько хочет, вот только не понятно почему вместо того чтобы блокировать только тот поток в котором он был вызван, он блокирует ВЕСЬ процесс?

Кстати, попробовал симулировать getline() этим кодом:

text

sleep(3);
strcpy(str, "qwerty");

Проблема остается. Как и следовало ожидать, если указать sleep(1), то все работает нормально... Отсюда вывод, что проблема именно в recv()



Какая глупость... на сайте stackoverflow.com умный дядька подсказал что на самом деле все работает правильно, вот только на первый взгляд не увидеть логическую ошибку. Когда ПОТОК1 вызывает receive(), он строкой раньше заблокировал nvtMutex, а т.к ПОТОКУ 2 необходим свободный терминал nvt чтобы отправить ответ, он его никогда не отправит... :rolleyes:

#32195

V0idExp,
Тему закрываем?

#32196

Да, спасибо.

Войдите или зарегистрируйтесь, чтобы ответить.