Организация сетевого взаимодействия на Qt

от автора

В данной статье я бы хотел рассказать об одном из вариантов организации сетевого взаимодействия в программах, написанных на Qt, используя библиотеку QexRemint. Эта библиотека позволяет сериализовать/десериализовать сигналы и слоты. Дописав к ней сетевую часть, можно получить отличный и удобный механизм для удаленного вызова процедур (Remote Procedure Call).

Основные классы

QexMethodTranslator – класс для вызова методов.
QexPropertyTranslator – класс для передачи свойств.
QexSignalSlotTranslator – класс для передачи сигналов.
QexMetaTranslator – агрегатор основных классов.
QexRemintReader – вспомогательный класс для ожидания прихода пакета целиком.

Передаваемые по сети данные

Во время вызова, на вызываемую сторону предается:
— идентификатор ответа;
— имя вызываемого объекта;
— сигнатура вызываемого метода;
— тип возвращаемого значения;
— список типов вызываемых аргументов;
— список значений вызываемых аргументов.
Аргументы сохраняются и восстанавливаются в QByteArray, используя QMetaType::save/ QMetaType::load.

При возвращении ответа передается:
— идентификатор ответа;
— имя объекта, который был вызван;
— имя метода, который был вызван;
— тип ответа;
— значение ответа.

Методы у объектов вызываются через QMetaObject::invokeMethod, поэтому они выполняются в потоке, в котором он обрабатывает слоты.

Пример

Для примера был реализован простенький чат. Что бы выделить важный код, были удалены почти все проверки и очистки. Этот код является очень урезанной версией того, что используется в боевом режиме. Как уже было сказано выше, сетевое взаимодействие не входит в саму библиотеку, поэтому, в начале, устанавливается связь.

Взаимодействие на стороне сервера

Сервер начинает слушать входящие соединения:

m_tcp_server.listen( QHostAddress::Any, 43567 ); 

При подключении клиента настраиваем его:

// получаем ожидающего клиента QTcpSocket *client = m_tcp_server.nextPendingConnection( ); // мониторим отключение клиента connect( client, SIGNAL( disconnected( ) ), SLOT( disconnect_client( ) ) ); // мониторим когда поступят данные от клиента connect( client, SIGNAL( readyRead( ) ), SLOT( ready_read( ) ) ); // пара отвечает за обработку сигналов/слотов от одного клиента typedef QPair< QexRemintReader*, QexMetaTranslator* > reader_translator; // создаем классы, для обработки данных именного этого клиента reader_translator rt = qMakePair( new QexRemintReader( client ), new QexMetaTranslator( client ) ); // подключаем обработчик данных, которые приходят из сети (у нас это принятые вызовы методов) connect( rt.first, SIGNAL( dataFormed( const QByteArray& ) ), rt.second, SLOT( inputData( const QByteArray& ) ) ); // подключаем обработчик данных, который надо послать в сеть (у нас это сгенеренные сигналы) connect( rt.second, SIGNAL( dataOutputted ( const QByteArray& ) ), SLOT( send_data( const QByteArray& ) ) ); // подключаем список методов, которые можно вызвать rt.second->methodTranslator().connectMethod( "chat_server", this, SLOT( send_message( const QString&, const QString& ) ) ); // подключаем список сигналов, которые будут передаваться клиентам rt.second->signalSlotTranslator().connectSignal( "chat_server", this, SIGNAL( message_received( const QString&, const QString& ) ) ); 

Когда мы делаем какой-то вызов или генерируем сигнал, нам необходимо отправить данные об этом клиенту. Обработчик всех подобных сигналов реализован один в одном объекте, поэтому необходимо найти какому клиенту предназначены данные. Целевого клиента ищем по отправителю сигнала, т.к. данные отправляет QexMetaTranslator, который отвечает за данного клиента:

QexMetaTranslator * mt = qobject_cast< QexMetaTranslator* >( sender() ); for ( QMap< QTcpSocket*, reader_translator >::iterator it = m_clients.begin(); it != m_clients.end(); ++it ) { 	if ( it.value().second == mt ) 	{ 		QDataStream stream( it.key() ); 		stream << data; 	} } 

Необходимо обернуть отправляемые данные в QDataStream, т.к. при чтении данных, в начале вычитывается размер данных, который записывает QDataStream. При приеме нового сообщения нужно только разослать его всем остальным пользователям:

Q_EMIT message_received( nickname, text ); 

Взаимодействие на стороне клиента

При создание клиента необходимо настроить прием/передачу сигналов и слотов по аналогии с серверной частью:

connect( m_reader, SIGNAL( dataFormed( const QByteArray& ) ), m_translator, SLOT( inputData( const QByteArray& ) ) ); connect( m_translator, SIGNAL( dataOutputted( const QByteArray& ) ), SLOT( send_data( const QByteArray& ) ) ); 

Далее настраиваем принимаемые сигналы:

m_translator->signalSlotTranslator().connectSlot( "chat_server", SIGNAL( message_received( const QString&, const QString& ) ), this, SLOT( message_received( const QString&, const QString& ) ) ); 

При получении сигнала, просто добавляем полученный текст сообщения в окно с сообщениями. После создания объекта клиента, необходимо установить связь по сети. Далее, при получении данных из сокета, отправляем их на обработку в парсер:

m_reader->addData( m_socket->readAll() ); 

Отправка данных серверу осуществляется просто записью в сокет через QDataStream:

QDataStream stream( m_socket ); stream << data; 

Отправка сообщение (вызов метода):

m_translator->methodTranslator().callMethod( "chat_server" 	, METHOD( send_message( QString, QString ) ) 	, Q_ARG( QString, m_ui.nick->text() ) 	, Q_ARG( QString, m_ui.text->toPlainText() ) 	); 

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

Исходники чата

ссылка на оригинал статьи http://habrahabr.ru/post/171553/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *