miércoles, 10 de julio de 2013

Desarrollo de un servidor TCP/IP con el Framework Qt.

clip_image002

En esta entrada mostraremos un ejemplo de cómo crear un servidor para un servicio de red simple, a través del protocolo TCP/IP. En próximas entradas mostraremos un cliente TCP/IP que se conectara al servidor que a continuación explicaremos.

clip_image004

El servidor usa la clase QtcpServer para aceptar las conexiones TCP entrantes, y un protocolo de trasferencia de datos sencillo basado en la clase QdataStream, para escribir un texto aleatorio al cliente conectado, antes de cerrar la conexión.

class Server : public QDialog
{
Q_OBJECT

public:
Server(QWidget *parent = 0);

private slots:
void sendFortune();

private:
QLabel *statusLabel;
QPushButton *quitButton;
QTcpServer *tcpServer;
QStringList fortunes;
};

Este servidor es implementado usando una clase simple con solo un slot, para manejar las conexiones entrantes.

tcpServer = new QTcpServer(this);
if (!tcpServer->listen()) {
QMessageBox::critical(this, tr("Fortune Server"),
tr("Unable to start the server: %1.")
.arg(tcpServer->errorString()));
close();
return;
}
QString ipAddress;
QList<QHostAddress> ipAddressesList = QNetworkInterface::allAddresses();
// use the first non-localhost IPv4 address
for (int i = 0; i < ipAddressesList.size(); ++i) {
if (ipAddressesList.at(i) != QHostAddress::LocalHost &&
ipAddressesList.at(i).toIPv4Address()) {
ipAddress = ipAddressesList.at(i).toString();
break;
}
}
// if we did not find one, use IPv4 localhost
if (ipAddress.isEmpty())
ipAddress = QHostAddress(QHostAddress::LocalHost).toString();
statusLabel->setText(tr("The server is running on\n\nIP: %1\nport: %2\n\n"
"Run the Fortune Client example now.")
.arg(ipAddress).arg(tcpServer->serverPort()));

En este constructor, nuestro objeto Server llama a QtcpServer::listen() para indicar a la clase QtcpServer que escuche todas las direcciones, por un puerto arbitrario. En la pantalla se muestra una etiqueta con el puerto que fue escogido para que QtcpServer escuche, de esta forma los usuarios conocen a que puerto se debe conectar la aplicación cliente, para recibir mensajes aleatorios de “fortuna”.

fortunes << tr("You've been leading a dog's life. Stay off the furniture.")

<< tr("You've got to think about tomorrow.")

<< tr("You will be surprised by a loud noise.")

<< tr("You will feel hungry again in another hour.")

<< tr("You might have mail.")

<< tr("You cannot kill time without injuring eternity.")

<< tr("Computers are not intelligent. They only think they are.");

Nuestro servidor genera una lista aleatoria de textos de “fortuna”, que pueden ser enviados a los clientes TCP conectados.

connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendFortune()));

Cuando un cliente se conecta a nuestro servidor, el objeto de la clase QtcpServer emitirá la señal QtcpServer::newConnection(). En consecuencia, se invocara nuestro slot sendFortune().

void Server::sendFortune()
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);

El propósito de este slot es seleccionar una línea aleatoria de nuestra lista de textos de “fortuna”, codificarla dentro de un QByteArray usando la clase QdataStream, y entonces escribirlo hacia el socket conectado. Esta es una vía común de transmitir datos binarios usando QtcpSocket. Primero crearemos un QbyteArray y un objeto QdataStream, pasando el bytearray al constructor de la clase QdataStream. Entonces especificamos explícitamente la versión del protocolo de QdataStream a QdataStream::Qt_4_0 para asegurar que nos podamos comunicar con clientes de futuras versiones de Qt.

     out << (quint16)0;
out << fortunes.at(qrand() % fortunes.size());
out.device()->seek(0);
out << (quint16)(block.size() - sizeof(quint16));

Al iniciar nuestro QbyteArray, reservamos espacio para un entero de 16 bits que contendrá el tamaño total de los bloques de datos que serán enviados. Luego colocamos en el QbyteArray un texto aleatorio de “fortuna”. Entonces saltamos hacia el inicio del QbyteArray, y sobrescribimos el valor del entero de 16 bits que habíamos reservado, colocando con el tamaño total del arreglo. Haciendo esto proveemos una vía a los clientes para verificar la cantidad de datos que ellos deben esperar antes de leer el paquete completo.

QTcpSocket *clientConnection = tcpServer->nextPendingConnection();

connect(clientConnection, SIGNAL(disconnected()),

clientConnection, SLOT(deleteLater()));


Entonces llamamos al método QtcpServer::newPendingConnection(), el cual retorna el objeto QtcpSocket representando la conexión del lado del servidor. Conectando QTcpSocket::disconnected() al método Qobject::deleteLater(), nos aseguramos que el socket será eliminado luego de que nos desconectemos.

clientConnection->write(block);

clientConnection->disconnectFromHost();

}


El texto de “fortuna” codificado es escrito usando el método QtcpSocket::write(), y finalmente llamamos al método QtcpSocket::disconnectFromHost(), el cual cerrará la conexión después de que QtcpSocket haya terminado de escribir el texto de “fortuna” a través de la red. Debido a que QtcpSocket trabaja de forma asíncrona, los datos serán escritos después de que la función retorne y el control regrese al lazo de eventos de Qt. El socket entonces será cerrado, lo cual causará una llamada al método Qobject::deleteLater() para eliminarlo.

El código fuente total de esta aplicación pueden descargarlo desde aquí


¿Te ha gustado este Post? Compártelo con tus amigos.

No hay comentarios:

Publicar un comentario

IconIconIcon