
Содержание
1. Сетевое программирование в qt 1
1.1. Обзор классов и примеры реализаций 1
1.2. Клиент-серверная архитектура 1
1.3. Реализация сервера 1
1.4. Реализация клиента 5
1.5. Заключение 8
2. Проведение тестов в qt 9
Литература 10
1. Сетевое программирование в qt
1.1. Обзор классов и примеры реализаций
Сокет – это устройство пересылки данных с одного конца линии связи на другой. Другой конец может принадлежать процессу, работающему на локальном компьютере, а может располагаться и на удаленном компьютере, подключенном к интернету и расположенному в другом полушарии Земли.
Сокетное соединение – это соединение точка-точка, которое производится между двумя процессами.
Сокеты разделяются на дейтаграммные и поточные. Дейтаграммные осуществляют обмен данными пакетами. Поточные сокеты устанавливают связь и производят потоковый обмен данными через установленную ими линию связи.
Для дейтаграммных сокетов существует класс QUdpSocket, а для поточных – QTcpSocket. Далее мы будем рассматривать только поточные сокеты.
1.2. Клиент-Серверная архитектура(модель Клиент-Сервер)
Сервер предлагает услуги, а клиент ими пользуется. Программа, использующая сокеты, может выполнять роль сервера, либо роль клиента. Для того, чтобы клиент мог взаимодействовать с сервером, ему необходимо знать IP-адрес сервера и номер порта, через который клиент должен сообщить о себе. Когда клиент начинает соединение с сервером, его система назначает данному соединению отдельный сокет, а когда сервер принимает соединение, сокет назначается со стороны сервера. После этого устанавливается связь между этими двумя сокетами, по которой высылаются данные запроса к серверу. А сервер высылает по этому же соединению готовые результаты клиенту согласно его запросу.
1.3. Реализация Сервера
Для реализации сервера Qt предоставляет удобный класс QTcpServer, который предназначен для управления входящими TCP-соединениями.
Файл main.cpp
#include <QtGui>
#include "MyServer.h"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyServer server(2323);
server.show();
return app.exec();
}
В данном листинге создается объект сервера. Чтобы запустить сервер, мы создаем объект, передавая в конструктор номер порта, по которому должен работать нужный нам сервис.
Примечание: необходимо, чтобы данный порт был не занят какими-либо другими сервисами и был открыт файрволом(при его наличии в системе).
|
Файл MyServer.h
#include <QWidget>
class QTcpServer;
class QTextEdit;
class QTcpSocket;
class MyServer : public QWidget {
Q_OBJECT
private:
QTcpServer* m_ptcpServer;
QTextEdit* m_ptxt;
quint16 m_nNextBlockSize;
private:
void sendToClient(QTcpSocket* pSocket, const QString& str);
public:
MyServer(int nPort, QWidget* pwgt = 0);
public slots:
virtual void slotNewConnection();
void slotReadClient ();
};
В классе MyServer мы объявляем атрибут m_ptcpserver, который и является основой управления нашим сервером. Атрибут m_NextBlockSizе служит для хранения длины следующего полученного от сокета блока. Многострочное текстовое поле m_ptxt предназначено для информирования о происходящих соединениях.
Файл MyServer.cpp
#include <QtNetwork>
#include <QtGui>
#include "MyServer.h"
// ----------------------------------------------------------------------
MyServer::MyServer(int nPort, QWidget* pwgt /*=0*/) : QWidget(pwgt)
, m_nNextBlockSize(0)
{
m_ptcpServer = new QTcpServer(this); //создаётся объект, адрес заносим в указатель
if (!m_ptcpServer->listen(QHostAddress::Any, nPort)) //слушаем наличие соединений по любому ip-адресу данной машины, с конкретного порта, сидим и слушаем до упора, если listen вернул 0, то это признак ошибки, попадаем сюда
//->
{
QMessageBox::critical(0, "Server Error", "Unable to start the server:" + m_ptcpServer->errorString());
m_ptcpServer->close(); //закрываем соединение
return;
}
//если происходит новое соединение, вызываем соответствующий обработчик
connect(m_ptcpServer, SIGNAL(newConnection()),
this, SLOT(slotNewConnection())
);
m_ptxt = new QTextEdit;
m_ptxt->setReadOnly(true);
//Layout-настройки
QVBoxLayout* pvbxLayout = new QVBoxLayout;
pvbxLayout->addWidget(new QLabel("<H1>Server</H1>"));
pvbxLayout->addWidget(m_ptxt);
setLayout(pvbxLayout);
}
Для запуска сервера нам необходимо вызвать в конструкторе метод listen(). В этот метод необходимо передать номер порта, который мы получили в конструкторе. При возникновении ошибочных ситуаций(невозможность захвата порта), этот метод возвратит false, на который мы отреагируем показом окна с сообщением об ошибке. Если ошибки не произошло, мы соединяем определенный нами слот slotNewConnection() с сигналом newConnection(), который высылается при каждом присоединении нового клиента.
// ----------------------------------------------------------------------
void MyServer::slotNewConnection() //обработчик нового соединения
{
// получили адрес сокета (корзинки) от метода nextPC, в которую будут / помещаться байты
QTcpSocket* pClientSocket = m_ptcpServer->nextPendingConnection();
//связали
connect(pClientSocket, SIGNAL(disconnected()),
pClientSocket, SLOT(deleteLater())
);
// если прошло соединение, мы готовы читать данные от клиента, вызываем метод //по чтению данных
connect(pClientSocket, SIGNAL(readyRead()),
this, SLOT(slotReadClient())
);
//вызов НАШЕГО метода, который шлёт клиенту ответ сервера
sendToClient(pClientSocket, "Server Response: Connected!");
}
Данный метод вызывается каждый раз при соединении с новым клиентом. Для подтверждения соединения с клиентом необходимо вызвать метод nextPendingConnection(), который возвратит сокет, посредством которого можно осуществлять дальнейшую связь с клиентом. Дальнейшая работа сокета обеспечивается сигнально-слотовыми связями. Мы соединяем сигнал disconnected(), высылаемый сокетом при отсоединении клиента, со стандартным слотом QObject::deleteLater(), предназначенного для последующего его уничтожения. При поступлении запросов от клиентов высылается сигнал readyToRead(), который мы соединяем со слотом slotReadClient().
// ----------------------------------------------------------------------
void MyServer::slotReadClient()
{
// приводим указатель, возвращённый сендером к нужному типу и заносим в свою // переменную-указатель
QTcpSocket* pClientSocket = (QTcpSocket*)sender();
//создаём поток входной на основе сокета, см. конструктор датастрима
QDataStream in(pClientSocket);
//бесконечный цикл, с выходом по брейку
for (;;)
{
//m_nNextBlockSize хранит размер блока, переданного от клиента
if (!m_nNextBlockSize) //в ветку попадаем, если блоксайз=0
{
//если доступное число байт меньше размера этой переменной (16 //бит или 2 байта), то выходим из цикла for
if (pClientSocket->bytesAvailable() < sizeof(quint16))
{ break; }
//сюда попадаем, если не вышли из цикла благодаря пред. условию. Здесь из //потока заносим значение в нашу переменную, хранящую количество байт в //посылке.
in >> m_nNextBlockSize;
}
//ждём, пока в сокете не накопится нужное количество байт, если байт в //сокете меньше, чем размер блока, то выходим, поскольку данных там //недостаточно. Если мало - выход из for’a.
if (pClientSocket->bytesAvailable() < m_nNextBlockSize)
{
break;
}
//если все проверки пройдены, то создаем объект, хранящий время и объект-//строку.
QTime time;
QString str;
//из потока направляем набор байт в ВРЕМЯ (отправки клиентом) и в СТРОКУ //(переданную клиентом)
in >> time >> str;
QString strMessage =
time.toString() + " " + "Client has sended - " + str;
//добавляем сформированную строку к уже отображённым строкам в редакторе.
m_ptxt->append(strMessage);
//когда всё получили, обнулили размер блока, чтобы ждать следующий посыл
m_nNextBlockSize = 0;
//клиенту отправили подтверждение, что мы от него получили
sendToClient(pClientSocket,
"Server Response: Received \"" + str + "\""
); }}
Каждый переданный сокетом блок начинается полем полем размера блока. Размер блока считывается при условии, что размер полученных данных не меньше двух байт(тип QString, Unicode). Вызовом метода sendToClient() мы сообщаем клиенту о том, что нас успешно удалось прочитать высланные им данные.
// ----------------------------------------------------------------------
void MyServer::sendToClient(QTcpSocket* pSocket, const QString& str)
{
//создаём массив из байтов (последовательность, с которой мы работаем)
QByteArray arrBlock;
//связываем поток с массивом байтов (передаём адрес массива)
QDataStream out(&arrBlock, QIODevice::WriteOnly);
//в поток помещаем двухбайтное слово, которое будет хранить размер, потом //туда заносим объект-ВРЕМЯ, созданный методом currentTime(), и саму строку-//ответ
out << quint16(0) << QTime::currentTime() << str;
//ищем в потоке значение 0, чтобы...
out.device()->seek(0);
//...записать туда общее количество байт, записанное в поток, минус 2 байта, //отведённые под хранение собственно количества
out << quint16(arrBlock.size() - sizeof(quint16));
pSocket->write(arrBlock); //заносим последовательность байт в сокет
}
В этом методе мы формируем данные, которые будут отосланы клиенту.