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

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

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

Author24 — интернет-сервис помощи студентам
Добрый день уважаемые форумчане. В общем, возникла проблема связанная с передаче данных через сокеты. Написал я касс, в одном из методов которого создается 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
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
26.01.2016, 12:02
Ответы с готовыми решениями:

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

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

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

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

4
Ушел с форума
Эксперт С++
16473 / 7436 / 1187
Регистрация: 02.05.2013
Сообщений: 11,617
Записей в блоге: 1
26.01.2016, 12:10 2
Весь код не анализировал, но достаточно одного лишь фрагмента:
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  [ТС] 3
С этим согласен, но все же мне непонятно почему recv не реагирует на второй вызов send, ведь данные отправляются и что-то считаться должно, но такое ощущение что данные в канал не попадают.
Я даже пробовал отсылать сообщение по 1 байту, однако же результат прежний))
(recv не считывает ни 1 байта)
Бьюсь над этим уже солидный кусок времени)))
Хотя какое-то время назад я реализовывал похожую задачу на сокетах UNIX, так вот там таких проблем не возникало.
0
Ушел с форума
Эксперт С++
16473 / 7436 / 1187
Регистрация: 02.05.2013
Сообщений: 11,617
Записей в блоге: 1
26.01.2016, 13:04 4
Цитата Сообщение от Survial Посмотреть сообщение
непонятно почему recv не реагирует на второй вызов send
Сократи код до минимума и попробуй воспроизвести эту ситуацию.
То есть, без классов, без потоков и т.п., просто "голые" сокеты и
больше ничего.
1
0 / 0 / 0
Регистрация: 25.11.2015
Сообщений: 4
26.01.2016, 14:31  [ТС] 5
Сделал упрощенную программку без классов. И действительно проблемы исчезли и все сообщения корректно передаются.
Следовательно, моя организация классов как-то влияет на сокеты.
Осталось разобраться что именно может повлиять на передачу сообщений в классе.
Спасибо за то что, указали направление куда копать))

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

У меня в классе было всего 2 сокета и получилось так, что второй и третий сокеты представляли из себя 1 и тот же дескриптор))
А так как обмен данными происходит локально (клиент и сервер на 1 ПК в разных потоках), то данный сокет был "рабочим" для клиента.
Для сервера же собственного "рабочего" сокета не было (он "перезатирался" клиентом).
В результате чего данный сокет использовался и сервером и клиентом, что и приводило к зависанию вызова recv() с оной из сторон.
Имхо это очень глупая ошибка с моей стороны, постарайтесь избегать таких ситуаций.
0
26.01.2016, 14:31
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
26.01.2016, 14:31
Помогаю со студенческими работами здесь

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
5
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru