Форум программистов, компьютерный форум, киберфорум
C++: Сети
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.88/8: Рейтинг темы: голосов - 8, средняя оценка - 4.88
0 / 0 / 0
Регистрация: 25.11.2015
Сообщений: 4

Функция recv не разблокируется после отправки сообщения в канал

26.01.2016, 12:02. Показов 1839. Ответов 4
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день уважаемые форумчане. В общем, возникла проблема связанная с передаче данных через сокеты. Написал я касс, в одном из методов которого создается 2 потока и затем в этих потоках создаются 2сокета, клиентский и серверный. Затем я реализовал 2 метода для приема и отправки сообщений между сокетами (функции recieveCMD и sendCMD). Вот как раз таки на данном этапе возникла проблема. Суть функция заключается в том, чтобы (передать/принять) через канал сначала длину сообщения, а затем уже само сообщение.
Например при удачном соединении клиента с сервером. сервер отправляет клиенту сообщение (sendCMD("HELLO")),
клиент, соответственно, ждет сообщения от сервера (recieveCMD()). Сервер отправляет длину сообщения (4 байта), а клиент соответственно эти 4 байта считывает. Затем сервер отправляет уже само сообщение, но вот клиентский вызов recv() продолжает ожидать поступления в канал сообщения, хотя оно уже было отправлено. Как следствие поток клиента блокируется вызовом recv.
Буду очень признателен, если вы поможете разобраться почему так происходит.
Исходники:
Заголовочный файл класса.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef list<player *> PL;
class gameplay
{
private:
    int gType;
    PL players;
    SOCKET clientSocket;
    SOCKET serverSocket;
    consoleLine *message;
 
    HANDLE ev1;
    bool initWSA();
    void createServer();
    void createClient();
public:
    virtual void playGamePvP() = 0;
    virtual void playGamePvC() = 0;
    gameplay(player *, player *);
    PL *getPlayers();
    int getGType();
    virtual ~gameplay();
    void startServer(player *);
    void startClient(player *);
    sockType createSockType(player *);
    consoleLine *myMessage();
    SOCKET getServSock();
    SOCKET getClieSock();
    void setServSock(const SOCKET &);
    void setClieSock(const SOCKET &);
    HANDLE getEv1() { return ev1; }
    void setEv1() { SetEvent(ev1); }
 
    string recieveCMD();
 
    int sendCMD(const string &);
};
Метод создание сокета клиента (уже в потоке):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void gameplay::createClient() {
    
    SOCKET cliSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    if (cliSock == SOCKET_ERROR)
    {
        myMessage()->printLine("Unable to create socket\n");
        WSACleanup();
        Sleep(2000);
        exit(-1);
    }
 
    SOCKADDR_IN destAddr;
    destAddr.sin_family = AF_INET;
    destAddr.sin_port = htons(PORT);
    destAddr.sin_addr.S_un.S_addr = inet_addr(SERVERADDR);
 
    if (connect(cliSock, (SOCKADDR *)&destAddr, sizeof(destAddr)) == 0) {
        cout << "Connect to " << SERVERADDR << " a connection SUCCESS" << endl;
    } else {
        cout << "Connect to " << SERVERADDR << " connection LOST" << endl;
    }
 
    setClieSock(cliSock);
    setServSock(NULL);
}
Метод создание сокета сервера (уже в потоке):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void gameplay::createServer() {
    SOCKET servSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == SOCKET_ERROR)
    {
        cout << "Unable to create socket" << endl;
        WSACleanup();
        Sleep(2000);
        exit(-1);
    }
    
    SOCKADDR_IN servInfo;
    servInfo.sin_family = AF_INET;
    servInfo.sin_port = htons(PORT);
    servInfo.sin_addr.s_addr = inet_addr(SERVERADDR);
 
    if (::bind(servSock, (LPSOCKADDR)&servInfo, sizeof(servInfo)) != 0) {
        cout << "Unable to bind socket" << endl;
        WSACleanup();
        Sleep(2000);
        exit(-1);
    }
 
    if (listen(servSock, MAXPLAYERSCONN) != 0) {
        cout << "Unable to connection" << endl;
        WSACleanup();
        Sleep(2000);
        exit(-1);
    }
 
    SOCKET cliSock;
    SOCKADDR_IN cliInfo;
    int len = sizeof(cliInfo);
    cliSock = accept(servSock, (SOCKADDR *)&cliInfo, &len);
    setServSock(servSock);
    setClieSock(cliSock);
}
Методы отправки и приема сообщений:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
string gameplay::recieveCMD() {
    char data_size[4];
    int sz;
    if ((sz = recv(clientSocket, data_size, 4, 0)) <= 0) {
        return "ERR";
    }
    int size = 0;
    size |= (unsigned char)data_size[0] << 24;
    size |= (unsigned char)data_size[1] << 16;
    size |= (unsigned char)data_size[2] << 8;
    size |= (unsigned char)data_size[3] << 0;
    size = ntohl(size);
    char *buf = new char[size + 1];
    int result = recv(clientSocket, buf, size, 0);
    string str;
    if (result > 0) {
        str = string(buf, result);
    }
    delete buf;
    return str;
}
 
int gameplay::sendCMD(const string &str) {
    int size = htonl(str.size());
    char data_size[4];
    data_size[0] = (size & 0xff000000) >> 24;
    data_size[1] = (size & 0x00ff0000) >> 16;
    data_size[2] = (size & 0x0000ff00) >> 8;
    data_size[3] = (size & 0x000000ff) >> 0;
    int sz;
    if ((sz = send(clientSocket, data_size, 4, 0)) <= 0) {
        return -1;
    }
    sz = send(clientSocket, str.c_str(), str.size(), 0);
    return sz;
}
Ну и, собственно, функции потоков, где и происходит отправка:
C++
1
2
3
4
5
6
7
8
9
10
11
12
void gameplay::startClient(player *pl) {
    WaitForSingleObject(getEv1(), INFINITE); //ожидает когда будет создан сервер
    cout << recieveCMD() << endl;
    while (1);
}
 
void gameplay::startServer(player *pl) {
    createSockType(pl);
    setEv1(); //сервер создан, разблокировать поток клиента
    sendCMD("HELLO");
    while (1);
}
Все стопорится именно на 2 вызове recv() в функции recieveCMD().
До этого момента все передается верно, проверил пошагово.
И да, сокет используется блокирующий.
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
26.01.2016, 12:02
Ответы с готовыми решениями:

Функция отправки сообщения работает неправильно
Здраствуйте.Решил сделать контактную форму с php обработчиком. Вродебы код правильный, но при прочтении сообщения я вижу только дату и...

Не закрывается окно после отправки сообщения
Доброго времени суток в этой статье http://xozblog.ru/2012/11/modal-windows/ сделал себе модальное окно, также добавил форму отправки...

Переадресация после отправки сообщения, PHP
Здравствуйте форумчане. В PHP новичек. Отправляю сообщение с прикрепленными файлами. После отправки страница не перезагружена,...

4
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
26.01.2016, 12:10
Весь код не анализировал, но достаточно одного лишь фрагмента:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string gameplay::recieveCMD() {
    char data_size[4];
    int sz;
    if ((sz = recv(clientSocket, data_size, 4, 0)) <= 0) {
        return "ERR";
    }
    int size = 0;
    size |= (unsigned char)data_size[0] << 24;
    size |= (unsigned char)data_size[1] << 16;
    size |= (unsigned char)data_size[2] << 8;
    size |= (unsigned char)data_size[3] << 0;
    size = ntohl(size);
    char *buf = new char[size + 1];
    int result = recv(clientSocket, buf, size, 0);
    string str;
    if (result > 0) {
        str = string(buf, result);
    }
    delete buf;
    return str;
}
Это идеологически неверно. Дело в том, что recv гарантирует лишь то,
что в случае успеха будет прочитано от 1 до sizeof(buffer) байт.
В данном случае размер буфера для recv - 4 байта, но прочесть она
может за один вызов и три байта, и два, и один. Так что начать нужно с этого.

Кстати говоря, это вообще наитипичнейшая ошибка всех, кто учит работу в сетях.
0
0 / 0 / 0
Регистрация: 25.11.2015
Сообщений: 4
26.01.2016, 12:29  [ТС]
С этим согласен, но все же мне непонятно почему recv не реагирует на второй вызов send, ведь данные отправляются и что-то считаться должно, но такое ощущение что данные в канал не попадают.
Я даже пробовал отсылать сообщение по 1 байту, однако же результат прежний))
(recv не считывает ни 1 байта)
Бьюсь над этим уже солидный кусок времени)))
Хотя какое-то время назад я реализовывал похожую задачу на сокетах UNIX, так вот там таких проблем не возникало.
0
Ушел с форума
Эксперт С++
 Аватар для Убежденный
16481 / 7444 / 1187
Регистрация: 02.05.2013
Сообщений: 11,616
Записей в блоге: 1
26.01.2016, 13:04
Цитата Сообщение от Survial Посмотреть сообщение
непонятно почему recv не реагирует на второй вызов send
Сократи код до минимума и попробуй воспроизвести эту ситуацию.
То есть, без классов, без потоков и т.п., просто "голые" сокеты и
больше ничего.
1
0 / 0 / 0
Регистрация: 25.11.2015
Сообщений: 4
26.01.2016, 14:31  [ТС]
Сделал упрощенную программку без классов. И действительно проблемы исчезли и все сообщения корректно передаются.
Следовательно, моя организация классов как-то влияет на сокеты.
Осталось разобраться что именно может повлиять на передачу сообщений в классе.
Спасибо за то что, указали направление куда копать))

Добавлено через 59 минут
Ну и конечно же решение в студию:
Проблема оказалась простак как яйцо))
Для нормального обмена данными необходимо 3 сокета:
первый - сокет сервера, который является "слушающим", он ждет пока к нему присоединится какой-нибудь клиент;
второй - сокет сервера, на который он отсылает и с которого принимает данные от соединившегося с ним клиента ("рабочий" сокет);
третий - сокет клиента, через который он обменивается данными с сервером.

У меня в классе было всего 2 сокета и получилось так, что второй и третий сокеты представляли из себя 1 и тот же дескриптор))
А так как обмен данными происходит локально (клиент и сервер на 1 ПК в разных потоках), то данный сокет был "рабочим" для клиента.
Для сервера же собственного "рабочего" сокета не было (он "перезатирался" клиентом).
В результате чего данный сокет использовался и сервером и клиентом, что и приводило к зависанию вызова recv() с оной из сторон.
Имхо это очень глупая ошибка с моей стороны, постарайтесь избегать таких ситуаций.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
26.01.2016, 14:31
Помогаю со студенческими работами здесь

Переадресация на страницу после отправки сообщения
Отправляю сообщение на PHP все норм. Но после(через секунд 5) мне нужно сделать переадресацию на другую страницу(главную напр.). Вот...

Вывод уведомления после отправки сообщения
Всем привет! Имеется форма для отправки сообщений на эл. почту. Она работает. Но после отправки мне надо сделать, чтобы на странице...

Вызвать метод после отправки сообщения клиенту
public class ConnectionService : ConnectionInterface { public string GetResult(string message) { ...

Proforms Basik, пропадает капча после отправки сообщения
На странице формы Proforms Basik, есть капча. После первого письма, она пропадает и далее сообщения отправляются без нее. После...

Автоматический возврат на страницу после отправки сообщения с сайта
Здравствуйте. Пожалуйста, подскажите как дописать код, чтобы после успешной отправки сообщения с сайта посетитель автоматически, без...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
5
Ответ Создать тему
Новые блоги и статьи
Автоматическое создание документа при проведении другого документа
Maks 29.03.2026
Реализация из решения ниже выполнена на нетиповых документах, разработанных в конфигурации КА2. Есть нетиповой документ "ЗаявкаНаРемонтСпецтехники" и нетиповой документ "ПланированиеСпецтехники". В. . .
Настройка движения справочника по регистру сведений
Maks 29.03.2026
Решение ниже реализовано на примере нетипового справочника "ТарифыМобильнойСвязи" разработанного в конфигурации КА2, с целью учета корпоративной мобильной связи в коммерческом предприятии. . . .
Автозаполнение реквизита при выборе элемента справочника
Maks 27.03.2026
Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. При выборе "Спецтехники" (Тип Справочник. Спецтехника), заполняется. . .
Сумматор с применением элементов трёх состояний.
Hrethgir 26.03.2026
Тут. https:/ / fips. ru/ EGD/ ab3c85c8-836d-4866-871b-c2f0c5d77fbc Первый документ красиво выглядит, но без схемы. Это конечно не даёт никаких плюсов автору, но тем не менее. . . всё может быть. . .
Автозаполнение реквизитов при создании документа
Maks 26.03.2026
Программный код из решения ниже размещается в модуле объекта документа, в процедуре "ПриСозданииНаСервере". Алгоритм проверки заполнения реализован для исключения перезаписи значения реквизита,. . .
Команды формы и диалоговое окно
Maks 26.03.2026
1. Команда формы "ЗаполнитьЗапчасти". Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. В качестве источника данных. . .
Кому нужен AOT?
DevAlt 26.03.2026
Решил сделать простой ланчер Написал заготовку: dotnet new console --aot -o UrlHandler var items = args. Split(":"); var tag = items; var id = items; var executable = args;. . .
Отправка уведомления на почту при создании или изменении элементов справочника
Maks 24.03.2026
Программная отправка письма электронной почты на примере типового справочника "Склады" в конфигурации БП3. Перед реализацией необходимо выполнить настройку системной учетной записи электронной. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru