Architektury systemów rozproszonych LABORATORIUM Ćwiczenie 1 Temat: Aplikacja klient-serwer - implementacja w środowisku QT Creator. Przykładowy projekt aplikacji typu klient - serwer został udostępniony na szkolnym serwerze. Przed przystąpieniem do realizacji ćwiczenia należy pobrać 2 archiwa: bin.zip - programy wykonywalne src.zip - źródła programów Projekt w aktualnej wersji umożliwia komunikację między komputerami podłączonymi w obrębie tej samej sieci lokalnej. Jako parametr wejściowy serwera podajemy port, na którym mają zdalnie łączyć się maszyny klienckie (rys. 1). Rys. 1. Aplikacja serwera. W aplikacji klienta podajemy 3 parametry: nick użytkownika, adres IP serwera oraz port na którym on nasłuchuje (rys. 2). Rys. 2. Aplikacja klienta. 1
W celu nawiązania komunikacji między użytkownikami, jeden z nich musi uruchomić program "Serwer" natomiast pozostali uruchamiają aplikację "Klient". Komputer pełniący funkcję serwera może być też klientem (rys. 3). Rys. 2. Funkcje komputerów w sieci podczas użytkowania aplikacji klient-serwer. SERWER Do obsługi połączenia od strony serwera w środowisku QT służy funkcja generująca sygnał newconnection(). Za pomocą funkcji connect() sygnał ten łączymy z własnym slotem o nazwie newclient(), który automatycznie zostanie utworzony podczas nowego połączenia. server = new QTcpServer(this); connect(server, SIGNAL(newConnection()), this, SLOT(newClient())); Aplikacja serwer sprawdza dostępność portu podanego przez użytkownika i jeśli jest on wolny to uruchamia serwer i rozpoczyna nasłuchiwanie. void MainWindow::on_pushButton_clicked() if(isrunning) isrunning = false; server->close(); ui->label_2->settext(tr("rozłączony")); ui->pushbutton->settext(tr("uruchom")); else if (!server->listen(qhostaddress::any, ui->lineedit->text().toint())) ui->label_2->settext(tr("błąd: serwer nie może być uruchomiony")); else QString ipaddress; QList<QHostAddress> ipaddresseslist = QNetworkInterface::allAddresses(); for (int i = 0; i < ipaddresseslist.size(); ++i) if (ipaddresseslist.at(i)!= QHostAddress::LocalHost && ipaddresseslist.at(i).toipv4address()) ipaddress = ipaddresseslist.at(i).tostring(); break; 2
if (ipaddress.isempty()) ipaddress = QHostAddress(QHostAddress::LocalHost).toString(); isrunning = true; ui->label_2->settext(tr("serwer słucha na IP: %1\nna porcie: %2").arg(ipAddress).arg(server->serverPort())); ui->pushbutton->settext(tr("rozłącz")); Dodanie klienta polega na stworzeniu dla niego gniazda (socket) i dodanie go na listę klientów. Następnie do slotów podłączane są sygnały: disconnected() - który automatycznie usuwa klienta w momencie utraty połączenia readyread() - do obsługi danych przychodzących od klienta QTcpSocket *socket = server->nextpendingconnection(); socketslist.push_back(socket); sizeslist.push_back(0); connect(socket, SIGNAL(disconnected()), this, SLOT(removeClient())); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); connect(socket, SIGNAL(readyRead()), this, SLOT(sendToEveryone())); Usunięcie klienta następuje poprzez odczytanie jego ID. Jeśli klient nie odpowie to jest usuwany z listy. void MainWindow::removeClient() QTcpSocket *socket = (QTcpSocket*)sender(); int id = socketslist.indexof(socket); socketslist.removeat(id); sizeslist.removeat(id); W środowisku QT transmisja danych między komputerami można zrealizować wykorzystując klasy QDataStream oraz QByteArray. Poniżej została przedstawiona funkcja, która przesyła komunikat odebrany przez serwer od jednego z klientów do wszystkich występujących na liście serwera. 3
void MainWindow::sendToEveryone() QTcpSocket *socket = (QTcpSocket*) sender(); int id = socketslist.indexof(socket); QDataStream in(socket); in.setversion(qdatastream::qt_5_2); if (sizeslist.at(id) == 0) if (socket->bytesavailable() < (int)sizeof(quint16)) quint16 sizeofblock; in >> sizeofblock; sizeslist[id] = sizeofblock; if (socket->bytesavailable() < sizeslist.at(id)) QString nick; in >> nick; QString message; in >> message; QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); out.setversion(qdatastream::qt_5_2); out << sizeslist.at(id); out << nick; out << message; for (int i = 0; i < socketslist.size(); ++i) if (i!= id) socketslist.at(i)->write(data); sizeslist[id] = (quint16)0; KLIENT W aplikacji klienta do odpowiednich slotów podłączamy sygnały do obsługi połączenia ze zdalnym serwerem. socket = new QTcpSocket(this); connect(socket, SIGNAL(readyRead()), this, SLOT(readData())); connect(socket, SIGNAL(connected()), this, SLOT(connectedToServer())); connect(socket,signal(disconnected()),this,slot(disconnect())); 4
Połączenie klienta z serwerem następuje przy użyciu funkcji ConnectToServer(). void MainWindow::connectToServer(QString ip, int port, QString nick) socket->abort(); blocksize = 0; socket->connecttohost(ip, port); this->nick = nick; Po nawiązaniu połączenia wywołana jest funkcja ConnectedToServer(). void MainWindow::connectedToServer() ui->textbrowser->clear(); ui->textbrowser->append(tr("połączono z serwerem")); isconnected = true; ui->pushbutton->settext(tr("rozłącz")); ui->lineedit->setenabled(false); ui->lineedit_2->setenabled(false); ui->lineedit_4->setenabled(false); ui->lineedit_3->setenabled(true); ui->pushbutton_2->setenabled(true); Odbiór danych przez klienta jest realizowany przez funkcję readdata(). void MainWindow::readData() QDataStream in(socket); in.setversion(qdatastream::qt_4_6); if (blocksize == 0) if (socket->bytesavailable() < (int)sizeof(quint16)) in >> blocksize; if (socket->bytesavailable() < blocksize) QString nick; in >> nick; QString message; in >> message; ui->textbrowser->append(nick + ": " + message); blocksize = 0; Funkcja disconnected() określa co zrobić w momencie utraty połączenia z serwerem. 5
void MainWindow::disconnect() isconnected = false; ui->pushbutton->settext(tr("połącz")); ui->lineedit->setenabled(true); ui->lineedit_2->setenabled(true); ui->lineedit_4->setenabled(true); ui->lineedit_3->setenabled(false); ui->pushbutton_2->setenabled(false); socket->close(); nick.clear(); ui->textbrowser->append(tr("połączenie zerwane")); Do wysyłania danych przez port TCP/IP służy funkcja sendmessage(). void MainWindow::sendMessage() ui->textbrowser->append(nick + ": " + ui->lineedit_3->text()); QByteArray data; QDataStream out(&data, QIODevice::WriteOnly); out.setversion(qdatastream::qt_5_2); out << (quint16) 0; out << nick; out << ui->lineedit_3->text(); out.device()->seek(0); out << (quint16) (data.size() - sizeof(quint16)); socket->write(data); ui->lineedit_3->clear(); ZADANIE: Należy zmodyfikować kody źródłowe aplikacji w ten sposób, aby stworzyć prosty generator liczb pseudolosowych. Klient wysyła komunikat do serwera o treści "random", a serwer zwraca liczbę wylosowaną z zakresu 0-1000. 6