crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

 
Тема закрытаНачать новую тему
> (clinet) socket + threads, один поток - чтение, другой - запись
alexei
  опции профиля:
сообщение 11.4.2010, 0:05
Сообщение #1


Студент
*

Группа: Новичок
Сообщений: 11
Регистрация: 26.3.2010
Пользователь №: 1570

Спасибо сказали: 0 раз(а)




Репутация:   0  


Рассматриваемая проблема уж очень похожа на sockets + threads.

Имеется рабочий сервер, который общается с клиентом. В данном случае рассматривается именно работа клиента. Какие компоненты хочется иметь
1) Поток для того чтобы читать данные из сокета и не блокировать при этом работу графического приложения.
2) Поток для того чтобы отправлять данные по ЭТОМУ же сокету.

Поскольку данные (команды управления задачами на сервере) от клиента имеют большее значение по сравнению с получением данных, опишу решения которые функционируют, но не удовлетворяют нужным требованиям. Благодаря описанию (на момент написания было доступно только из кеша google) цель moveToThread было решено сделать отдельный event loop для работы с сокетом, чтобы не тормозить работу основного (графического) приложения. Варианты

(а) Делаем два потока, один для чтения, другой для записи. Насколько я понял из документации, чтобы была возможность пользоваться signals и slots в отдельном потоке, необходимо перенести объект в этот поток (в моем случае - это QTcpSocket). В потоке "А": создается QTcpSocket, регистрируются сигналы и слоты, устанавливается соединение, запускается event loop ( exec() ), переносится socket и сам thread в этот thread (уж извините за тавтологию: this->moveToThread( this ) - в функции run() ). Из потока "А" с помощью сигналов, слотов или блокировок и shared memory можно получить socketDescriptor. Если в потоке "Б" (предназначенного для отправки сообщений серверу) мы создадим сокет (QTcpSocket) и зададим файловый дескриптор посредством setSocketDescriptor (не важно какие openMode мы используем). Во время выполнения приложения мы увидим ругательства от Qt по поводу того, что нельзя использовать объект с одним и тем же дескриптором в разных потоках (точное ругательство можно воспроизвести при необходимости).

(б) Отчаявшись способом (а) решил попробовать сделать писать сообщения серверу в том же потоке, в котором читаю данные. Первое что я попробовал QTcpSocket::Write. В силу необходимости складывать операции отправки сообщений приоритетнее их чтения, попробовал из графического приложения сделать следующее:
/* recvTId - поток для работы непосредственно с сокетом*/
connect( this, SIGNAL( appSendToServer() ), recvTId , SLOT( sendToServer() ), Qt::DirectConnection );

На что при запуске было получено сообщение следующего содержания:
QSocketNotifier: socket notifiers cannot be enabled from another thread


В документации по Qt (4) написано следующее
Раскрывающийся текст

Notes for Windows Users

The socket passed to QSocketNotifier will become non-blocking, even if it was created as a blocking socket. The activated() signal is sometimes triggered by high general activity on the host, even if there is nothing to read. A subsequent read from the socket can then fail, the error indicating that there is no data available (e.g., WSAEWOULDBLOCK). This is an operating system limitation, and not a bug in QSocketNotifier.

To ensure that the socket notifier handles read notifications correctly, follow these steps when you receive a notification:

Disable the notifier.
Read data from the socket.
Re-enable the notifier if you are interested in more data (such as after having written a new command to a remote server).
To ensure that the socket notifier handles write notifications correctly, follow these steps when you receive a notification:

Disable the notifier.
Write as much data as you can (before EWOULDBLOCK is returned).
Re-enable notifier if you have more data to write.
Further information: On Windows, Qt always disables the notifier after getting a notification, and only re-enables it if more data is expected. For example, if data is read from the socket and it can be used to read more, or if reading or writing is not possible because the socket would block, in which case it is necessary to wait before attempting to read or write again.


Из примера в "Further information" я понимаю, что мне не получится сделать отправку сообщений на сервер моментально - т.е.пробиться в event loop через directConnection из другого потока. socketNotifier в windows работает не так как требуется (написано что из-за спецификации ОС, тот же код в linux работал без проблем (надеюсь я не забыл все перекомпилировать, когда проверял) ). Тогда я попробовал воспользоваться непосредственной записью в файловый дескриптор посредством write или _write (в windows определены в io.h). К сожалению на стороне сервера я никакого запроса не получил (возможно я ошибся с аналогией write-linux для windows). По крайней мере меня смущает не кроссплатформенность такого подхода, данные функции не работают для имеющегося socketDescriptor (QTcpSocket.write - отрабатывает нормально). Отказавшись от Qt::directConnection у меня получается рабочее приложение, которые по функциональности опаздывает на действия пользователя.


Подводя итог:
(а) Не получилось зарегистрировать новый QTcpSocket для записи в другом потоке с указанным файловым дескриптором.
(б) Нет моментальной отправки команд пользователя на сервер.

Кроссплатформенный write по указанному дескриптору решает обе проблемы. Сделать объект socket для записи решит проблему (а), как правильно определить directConnection решить проблему (б). Буду благодарен всем, кто поможет разрешить любую из этих ситуаций или подскажет другое решений.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Lenymo
  опции профиля:
сообщение 16.6.2010, 15:37
Сообщение #2


Студент
*

Группа: Новичок
Сообщений: 12
Регистрация: 16.6.2010
Пользователь №: 1812

Спасибо сказали: 1 раз(а)




Репутация:   0  


Имею аналогичную проблему. Ни у кого нет никаких идей по сабжу? :huh:
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
inviZ
  опции профиля:
сообщение 17.6.2010, 6:30
Сообщение #3


Студент
*

Группа: Новичок
Сообщений: 16
Регистрация: 5.6.2010
Пользователь №: 1781

Спасибо сказали: 4 раз(а)




Репутация:   1  


Способ а) не реализуем, я думаю. А в способе б) у тебя проблема вот в чем.
Во-первых, читаем документацию:
Цитата
"Direct Connection The slot is invoked immediately, when the signal is emitted. The slot is executed in the emitter's thread, which is not necessarily the receiver's thread"

Кроме того, не стоит думать, что "this->moveToThread(this)" вообще работает, как ты этого хочешь. Можешь протрассировать в отладчике и посмотреть, что ничего не происходит. Или ворнинги почитать, которые при этом выводятся. Поэтому даже если ты не будешь использовать DirectConnection, то все равно твой слот sendToServer() будет выполняться в главном потоке. В общем, ты должен создать какой-нибудь объект, принадлежащий твоему сетевому потоку (например, можно в теле метода run(), чтоб не использовать всякие там moveToThread), и его уже использовать для отправки сообщений.

Хм, извиняюсь, был не прав, "this->moveToThread(this)" все же работает. Но все равно, лучше так не делать, потому что в общем-то, это криво и есть более корректные способы (например, как я написал). Криво как минимум с точки зрения ОО-дизайна. Сущность "поток" - это все же системная единица диспетчеризации. А для сетевого взаимодействия лучше создать отдельный класс, и назвать его, например, "ClientConnection".
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
alexei
  опции профиля:
сообщение 19.6.2010, 20:15
Сообщение #4


Студент
*

Группа: Новичок
Сообщений: 11
Регистрация: 26.3.2010
Пользователь №: 1570

Спасибо сказали: 0 раз(а)




Репутация:   0  


Цитата(inviZ @ 17.6.2010, 7:30) *
Криво как минимум с точки зрения ОО-дизайна. Сущность "поток" - это все же системная единица диспетчеризации. А для сетевого взаимодействия лучше создать отдельный класс, и назвать его, например, "ClientConnection".


Спасибо за ответ.
Но к чему здесь создание отдельного класса? Мне нужно чтобы этот объект жил не в одном потоке, т.е. чтобы из одного потока протолкнуть событие в другой, причем в начало обработки событий EventLoop. Вопрос стоит об использование файлового дескриптора (сокета). Если я неправильно понял Ваше сообщение, прошу пояснить.

С уважением,
Алексей.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

Тема закрытаНачать новую тему
Теги
Нет тегов для показа


1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 28.4.2024, 7:11