Lidi dělají divné věci. Například spouští Linux v Excelu. Využít je emulátor RISC-V mini-rv32ima sestavený jako knihovna DLL, která je volaná z makra VBA (Visual Basic for Applications).
Revolut nabídne neomezený mobilní tarif za 12,50 eur (312 Kč). Aktuálně startuje ve Velké Británii a Německu.
Společnost Amazon miliardáře Jeffa Bezose vypustila na oběžnou dráhu první várku družic svého projektu Kuiper, který má z vesmíru poskytovat vysokorychlostní internetové připojení po celém světě a snažit se konkurovat nyní dominantnímu Starlinku nejbohatšího muže planety Elona Muska.
Poslední aktualizací začal model GPT-4o uživatelům příliš podlézat. OpenAI jej tak vrátila k předchozí verzi.
Google Chrome 136 byl prohlášen za stabilní. Nejnovější stabilní verze 136.0.7103.59 přináší řadu novinek z hlediska uživatelů i vývojářů. Podrobný přehled v poznámkách k vydání. Opraveno bylo 8 bezpečnostních chyb. Vylepšeny byly také nástroje pro vývojáře.
Homebrew (Wikipedie), správce balíčků pro macOS a od verze 2.0.0 také pro Linux, byl vydán ve verzi 4.5.0. Na stránce Homebrew Formulae lze procházet seznamem balíčků. K dispozici jsou také různé statistiky.
Byl vydán Mozilla Firefox 138.0. Přehled novinek v poznámkách k vydání a poznámkách k vydání pro vývojáře. Řešeny jsou rovněž bezpečnostní chyby. Nový Firefox 138 je již k dispozici také na Flathubu a Snapcraftu.
Šestnáctý ročník ne-konference jOpenSpace se koná 3. – 5. října 2025 v Hotelu Antoň v Telči. Pro účast je potřeba vyplnit registrační formulář. Ne-konference neznamená, že se organizátorům nechce připravovat program, ale naopak dává prostor všem pozvaným, aby si program sami složili z toho nejzajímavějšího, čím se v poslední době zabývají nebo co je oslovilo. Obsah, který vytvářejí všichni účastníci, se skládá z desetiminutových
… více »Richard Stallman přednáší ve středu 7. května od 16:30 na Technické univerzitě v Liberci o vlivu technologií na svobodu. Přednáška je určená jak odborné tak laické veřejnosti.
Tento díl seriálu úzce souvisí s článkem Grafické programy v Qt 4 – 8 (TCP klient), jelikož je o server, jehož klienta jsem napsal s grafickým rozhraním. Základní informace o TCP síťování, socketech v Qt a ukázkovém programu se dočtete tam, zatímco tento článek popisuje čistě serverovou část, takže doporučuji začít čtení odkazovaným článkem.
Teď, když chápeme komunikaci přes QTcpSocket
, můžeme se vrhnout na serverovou část. Základem je třída QTcpServer
. Hned po vytvoření instance je vhodné nastavit nějaké základní parametry serveru, například takto:
QTcpServer* server = new QTcpServer(this); // explicitně zakážeme použití proxy server->setProxy(QNetworkProxy::NoProxy); // nastavíme max. počet spojení server->setMaxPendingConnections(50);
Ve většině případů je dobré zpracovat signál newConnection()
, kterým nás server informuje o novém příchozím spojení. Slot, který na tento signál napojíme, bude chtít nejspíš získat socket tohoto nového spojení. K tomu využijeme metodu nextPendingConnection()
, a to následovně:
// získáme socket QTcpSocket* socket = server->nextPendingConnection(); // dobré je hned ověřit, zda ještě existuje (tj. zda metoda nevrátila 0) if(!socket) return;
Existují dvě alternativy k tomuto přístupu zpracování nových příchozích spojení a jejich použití záleží na tom, čeho přesně chcete dosáhnout. Pokud potřebujete provádět něco velmi specifického a vyžadujete vysokou flexibilitu, potom si celou událost můžete zpracovat sami, a to tak, že si vytvoříte vlastní třídu založenou na QTcpServer
a reimplementujete její chráněnou virtuální metodu incomingConnection(int socketDescriptor)
. Integer, který dostanete jako argument, je popisovač (descriptor) socketu a pokud nepoužíváte QNetworkProxy
, lze s ním pracovat pomocí nativních socketových funkcí.
Druhý alternativní způsob je podstatně jednodušší. Nejdřív je třeba spustit server (viz níže) a potom lze použít blokující metodu waitForNewConnection()
, které lze zadat čas v milisekundách (jak dlouho má čekat na příchozí připojení) a ukazatel na bool
, do kterého se uloží informace o tom, zda došlo k vypršení času (tedy false
znamená, že máte nové spojení).
Když máme připravené zpracování nových připojení, můžeme spustit server, tedy naslouchání na určité adrese (nebo více adresách) a portu. Třeba takto:
QString ip("127.0.0.1"); QHostAddress addr; if(!addr.setAddress(ip)) { // zadaná IP adresa není ve správném formátu return 1; } // port=0 zajistí automatický výběr portu server->listen(addr, 0);
Ještě než server spustíte, je třeba vytvořit mu konfigurační soubor, kam zapíšete platná uživatelská jména (bez mezer) a přiřadíte jim hesla (resp. jejich SHA1 hash). Chcete-li vytvořit uživatele user s heslem pass, tak si nejdřív zjistěte SHA1 hash řetězce „pass“:
$ echo -n "pass" | sha1sum 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684
Když máte hash, vytvořte si konfigurační soubor v textovém editoru (na UNIXech ~/.config/watzke.cz/todoserver.conf
) a přidejte do něj sekci [logins]
a vaším uživatelem.
[logins] user=9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684
Server komunikuje jednoduchým protokolem, který je zdokumentovaný ve výše odkazovaném článku. Obsahuje jednoduchou obranu před bruteforce přihlašováním – povolí maximálně 10 chybných přihlášení za minutu. Jde spíše jen o ukázku implementace takové věci, spíš než cokoliv jiného, nicméně crackera by to mělo zpomalit :-)
Samozřejmě teď lze namítnout, že zmiňovat crackery je výsměch, když nepoužívám SSL sockety (nebo jiný způsob šifrování), ale o to mi v této ukázce nejde. Nicméně, když najdete bezpečnostní chybu, která s tímto nesouvisí, určitě se ozvěte v diskusi. Ještě dodám, že o chybějícím limitu délky jednotlivých záznamů vím, může tam být (teoreticky) libovolně dlouhý řetězec, i když můj klient umožní max. 32767 (2^15-1) znaků. Sám jsem několik bezpečnostních chyb odhalil a opravil při testování. Zvlášť při psaní síťového serveru, kde se řeší přihlašování, je třeba korektně ošetřovat nejrůznější výjimky. Nikdy nespoléhejte pouze na kontroly ve vašem klientovi.
tcpserver.h
: API.
#ifndef TCPSERVER_H #define TCPSERVER_H #include <QObject> #include <QTextStream> #include <QList> #include <QPair> #include <QHostAddress> #include <QHash> #include <QSettings> class QTcpServer; class QTcpSocket; class TcpServer : public QObject { Q_OBJECT public: TcpServer(QHostAddress addr = QHostAddress(QHostAddress::Any), int port = 2266); private: QTcpServer* server; QTextStream cerr; QHash<QTcpSocket*, QString> logins; QHash<QString, int> log; QSettings settings; void login(QTcpSocket* socket, const QString& user, const QString& pass, bool force = false); QString getKey(QString user, int index); void sortKeys(QString user); void listItems(QTcpSocket* socket); void addItem(QTcpSocket* socket, QString item); void removeItems(QTcpSocket* socket, QList<QByteArray> keys); void socketWrite(QTcpSocket* socket, QByteArray data); private slots: void purgeLog(); void handleNewConnection(); void reply(); void gotDisconnected(); }; #endif // TCPSERVER_H
tcpserver.cpp
:
#include "tcpserver.h" #include <QtNetwork> #include <cstdio> TcpServer::TcpServer(QHostAddress addr, int port) : cerr(stderr, QIODevice::WriteOnly) // poslouží pro výpis na std. chybový výstup, // podobně jako std::cerr v C++ { // vytvoříme TCP server server = new QTcpServer(this); // zajistíme zpracování požadavků vlastním slotem connect(server, SIGNAL(newConnection()), this, SLOT(handleNewConnection())); // každou minutu pročistíme záznam přihlašování QTimer::singleShot(60*1000, this, SLOT(purgeLog())); // explicitně zakážeme použití proxy server->setProxy(QNetworkProxy::NoProxy); // nastavíme max. počet spojení server->setMaxPendingConnections(50); // nastavíme IP a port pro naslouchání bool listening = server->listen(addr, port); // uložíme si IP adresu s portem do řetězce ve tvaru IP:port (jen pro výpis) QString ipport = addr.toString() + ":" + QString::number(port); // pokud se TCP server nepodařilo spustit na dané IP a portu if(not listening) { // vypíšeme chybovou hlášku cerr << tr("couldn't bind to %1, quitting…").arg(ipport) + "\n"; // zajistíme okamžitý zápis na stderr cerr.flush(); // tímto zajistíme ukončení programu po provedení konstruktoru QTimer::singleShot(0, qApp, SLOT(quit())); } else { cerr << tr("listening on %1").arg(ipport) + "\n"; cerr.flush(); } } // (ihned) zapíše data do socketu void TcpServer::socketWrite(QTcpSocket* socket, QByteArray data) { // zapíše data socket->write(data); // vynutí okamžitý zápis čekajících dat socket->flush(); } // vyprázdní záznam o přihlašování void TcpServer::purgeLog() { log.clear(); } // zpracuje nové příchozí spojení void TcpServer::handleNewConnection() { // získáme socket QTcpSocket* socket = server->nextPendingConnection(); if(!socket) return; cerr << tr("incoming connection from ") << socket->peerAddress().toString() << "\n"; cerr.flush(); // komunikace s klientem connect(socket, SIGNAL(readyRead()), SLOT(reply())); // šetříme paměť a zajistíme smazání socketu po jeho odpojení connect(socket, SIGNAL(disconnected()), SLOT(gotDisconnected())); // požádáme klienta, aby se přihlásil this->socketWrite(socket, "LOGIN"); } // vyřizuje odpověď na příchozí požadavky void TcpServer::reply() { // získáme socket (skrz objekt, který vyslal signál) QTcpSocket* socket = qobject_cast<QTcpSocket*>(this->sender()); if(log.value(socket->peerAddress().toString(), 0) >= 10) this->socketWrite(socket, "TOOMANYWRONGLOGINS"); // požadavek QByteArray rawdata = socket->readAll(); // argumenty požadavku QList<QByteArray> args = rawdata.split(' '); // samotný příkaz QString command = args.takeFirst(); // přihlašovací/uživatelské jméno QString user = logins.value(socket); qDebug() << rawdata; if(command == "LOGIN" || command == "FORCELOGIN") { // klient se chce přihlásit // klient je již přihlášen (na tomto socketu) if(!user.isEmpty()) { this->socketWrite(socket, "ALREADYLOGGED"); return; } // vynucené přihlášení? bool force = (command == "FORCELOGIN"); // heslo QString pass; // špatný počet argumentů nebudeme tolerovat if(args.size() == 2) { user = args.at(0); pass = args.at(1); } // zahájíme přihlašování this->login(socket, user, pass, force); return; } if(user.isEmpty()) { socket->disconnectFromHost(); QString ip(socket->peerAddress().toString()); cerr << tr("a client (IP=%1) has requested something without being logged in,").arg(ip) << "\n" << tr("which is suspicious – closing the connection.") << "\n"; cerr.flush(); } if(command == "LIST") { // klient žádá o seznam svých TODO položek this->listItems(socket); } else if(command == "ADD") { // klient si přeje přidat novou TODO položku this->addItem(socket, rawdata.right( rawdata.size()-command.size()-1 )); } else if(command == "REMOVE") { // klient si přeje odstranit položky this->removeItems(socket, rawdata.right( rawdata.size()-command.size()-1 ) .split(' ')); } else { // klient poslal neplatný příkaz this->socketWrite(socket, "INVALIDCMD"); cerr << "invalid command: " << command << "\n"; cerr.flush(); return; } } // přihlásí uživatele void TcpServer::login(QTcpSocket* socket, const QString& user, const QString& pass, bool force) { //qDebug() << "user" << user << "is trying to log in with pass" << pass; // načteme správné heslo QString realpass = settings.value(QString("logins/%1").arg(user)).toString(); // a porovnáme jej se zadaným (navíc se ujistíme, zda uživatel vůbec existuje, // abychom ošetřili přihlášení bez hesla) if(!user.isEmpty() && !realpass.isEmpty() && pass == realpass) { // ok // zjistíme, zda je uživatel již přihlášen odjinud bool alreadyLogged = logins.values().contains(user); // pokud ano a nejde o vynucený login… if(alreadyLogged and not force) { // informujeme o tom klienta this->socketWrite(socket, "ALREADYLOGGED"); return; } // pokud ano a jde o vynucený login… if(alreadyLogged) { // získáme seznam všech socketů QList<QTcpSocket*> keys = logins.keys(); // a odpojíme ten, který je přihlášený pod daným jménem for(int i=0; i<keys.size(); i++) if(logins.value(keys.at(i)) == user) { QTcpSocket* oldSocket = keys.at(i); oldSocket->disconnectFromHost(); logins.remove(oldSocket); break; } } // pošleme odpověď this->socketWrite(socket, "LOGGED"); // přiřadíme si socket k uživatelskému jménu do seznamu přihlášení logins.insert(socket, user); cerr << tr("user %1 successfully logged in").arg(user) << "\n"; cerr.flush(); } else { // špatný login this->socketWrite(socket, "WRONGLOGIN"); // odpojíme klienta socket->disconnectFromHost(); // zaznamenáme chybný login QString ip(socket->peerAddress().toString()); log.insert(ip, log.value(ip, 0)+1); cerr << tr("user %1 tried to log in with a wrong password, " "closing the connection").arg(user) << "\n"; cerr.flush(); } } // opraví klíče (indexy) položek v nastavení tak, aby šly po sobě (000 až 999) void TcpServer::sortKeys(QString user) { // získáme seznam klíčů QStringList keys = settings.allKeys(); // seznam je skutečně plný, nedá se nic dělat if(keys.size() > 999) return; cerr << tr("re-sorting %1's todo list").arg(user) + "\n"; cerr.flush(); // získáme seznam položek QStringList items; foreach(QString key, keys) items << settings.value(key).toString(); //qDebug() << "items:" << items; // odstraníme všechny položky settings.remove(""); // a přidáme je zpátky se správnými klíči for(int key = 0; key < keys.size(); key++) settings.setValue(this->getKey(user, key), items.at(key)); settings.sync(); } // vrátí klíč (index), pod kterým se položka uloží QString TcpServer::getKey(QString user, int index) { QString key = QString::number(index); if(index < 10) // pokud je index menší než 10 key.prepend("00"); // přidáme dvě nuly (5 -> 005), atd. else if(index < 100) key.prepend("0"); else if(index < 1000) Q_UNUSED(key) else { // index je vyšší než 999, zkusíme to ještě zachránit // zkusíme opravit indexy this->sortKeys(user); // načteme poslední index a přičteme k němu 1 int nIndex = settings.allKeys().last().toInt() + 1; // pokud je menší než 1000, povedlo se… if(nIndex < 1000) key = this->getKey(user, nIndex); else // prázdný klíč = plný seznam key.clear(); } return key; } // odešle klientovi seznam jeho položek void TcpServer::listItems(QTcpSocket* socket) { // načteme si uživatelské jméno QString user = logins.value(socket); // odpověď QByteArray reply; settings.beginGroup("todolist_" + user); QStringList keys = settings.allKeys(); // přidáme do odpovědi seznam všech položek foreach(QString key, keys) reply += settings.value(key).toString() + '\n'; settings.endGroup(); // ořízneme poslední \n reply.chop(1); // přidáme odpovídající příkaz if(reply.isEmpty()) reply = "LISTEMPTY"; else reply.prepend("LISTING "); //qDebug() << reply; // a odpovíme this->socketWrite(socket, reply); } // přidá položku do seznamu uživatele void TcpServer::addItem(QTcpSocket* socket, QString item) { QString user = logins.value(socket); settings.beginGroup("todolist_" + user); // zjistíme, pod kterým klíčem/indexem položku uložit int index; if(settings.allKeys().size() > 0) // k poslednímu uloženému indexu přičteme 1 index = settings.allKeys().last().toInt() + 1; else // první index bude 0 index = 0; //qDebug() << "index:" << index; // získáme klíč ve správném formátu QString key = this->getKey(user, index); // prázdný klíč = plný seznam if(key.isEmpty()) { // informujeme klienta, že má plný seznam TODO this->socketWrite(socket, "LISTFULL"); // nezapomeneme ukončit skupinu! settings.endGroup(); cerr << tr("user %1 has got a full todo list").arg(user) << "\n"; cerr.flush(); return; } // uložíme položku settings.setValue(key, item); // sestrojíme odpověď QByteArray reply = "ADDED "; reply += settings.value(key).toString(); settings.endGroup(); // odešleme odpověď this->socketWrite(socket, reply); // zapíšeme změny v TODO settings.sync(); } // odstraní vybrané položky ze seznamu void TcpServer::removeItems(QTcpSocket* socket, QList<QByteArray> keys) { QString user = logins.value(socket); QByteArray reply = "REMOVED"; settings.beginGroup("todolist_" + user); QStringList savedKeys = settings.allKeys(); foreach(QByteArray key, keys) { int index = key.toInt(); settings.remove(savedKeys.at(index)); reply += " " + key; } settings.endGroup(); settings.sync(); this->socketWrite(socket, reply); } // klient se odpojil void TcpServer::gotDisconnected() { // získáme ukazatel na objekt (socket), který vyslal signál QTcpSocket* socket = qobject_cast<QTcpSocket*>( this->sender() ); // získáme přihlašovací jméno QString user = logins.value(socket); if(user.isEmpty()) user = "*not logged*"; else // odstraníme ze seznamu aktivních připojení logins.remove(socket); // smažeme socket socket->deleteLater(); cerr << tr("a client (%1) has disconnected").arg(user) << "\n"; cerr.flush(); }
main.cpp
:
#include <QCoreApplication> #include <QDebug> #include <QHostAddress> #include <QStringList> #include <QTextCodec> #include "tcpserver.h" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // výchozí port int port = 2266; // výchozí adresa pro naslouchání QHostAddress addr(QHostAddress::Any); // je vše v pořádku pro spuštění programu? bool ok = true; // kontrola argumentů na příkazovém řádku if(argc > 3) { ok = false; } else { if(argc == 3) port = a.arguments().at(2).toInt(&ok); if(argc > 1) ok = addr.setAddress(a.arguments().at(1)); } if(not ok) { qDebug() << "usage:" << argv[0] << "[IP(0.0.0.0)] [port(2266)]"; return 1; } a.setApplicationName("todoserver"); a.setOrganizationDomain("watzke.cz"); // nastavíme kódování C řetězců QTextCodec::setCodecForCStrings( QTextCodec::codecForName("UTF-8") ); TcpServer s(addr, port); return a.exec(); }
Zdrojáky si můžete stáhnout v archívu tcpserver.tar.bz2.
Nástroje: Tisk bez diskuse
Tiskni
Sdílej:
Odkaz v uvodu clanku odkazuje asi jinam nez by melJo, díky. To je tím, že ten článek měl původně č. 9, ale nakonec předběhl původní osmičku
Ale jinak skvely pocteni DavideTo jsem rád, díky.
a potom lze použít blokovací metodu waitForNewConnection()spíš bych napsal blokující. Blokovací vyznívá tak, že účelem té metody je blokovat.