KooKooK (Update 3): Umbauten und weitere Informationen

Ich habe mich doch entschieden, einige Umbauten durchzuführen. Zunächst wollte ich den Server eigentlich pro Verbindung mit einem Thread laufen lassen. Das hat direkt, für ein Hobbyprojekt mit wenig Zeit, viele Probleme aufgeworfen.

Von Threads

Vor allem sind die Dateideskriptoren auf unterschiedlichen Betriebssystemen unterschiedlich konfiguriert, aber doch teils sehr limitiert. Ich wollte auf meinem Mac mal eben 500 Verbindungen aufbauen, bei 80 oder so war Schluss. Man kann das Limit zwar höher schrauben, aber man ist natürlich durchaus limitiert.

Dann gab es noch Probleme mit dem Verschieben von Objekten innerhalb von Qt zu Threads. Neue erstellte Objekte liefen nicht im Thread, sondern Hauptthread. Das kann man alles recht problemlos fixen, indem man mit Workerprozessen arbeitet, aber ich wollte da nicht weiter herumbauen (zeitliche Einschränkung), da ich vorwärts kommen wollte. Aber interessant: Ich habe mal 500 Konnektierungen gleichzeitig startet (so gleichzeitig, wie es nur geht). Das hat auf meinem Mac 200ms (etwas darunter) benötigt, um auf 500 Verbindungen ein „Hallo Welt“ über das Loopback-Device zu senden. War zwar nur Loopback, aber ich war doch erstaunt, wie schnell es geht, so viele Threads zu öffnen, was zu schicken und wieder zu schließen.

Keine Threads mehr

Dann habe ich einfach mit generellen nonblocking Sockets herumgespielt (im Thread waren sie auch nonblocking!) und festgestellt, dass es von der Geschwindigkeit her für einige hundert Verbindungen ausreichen sollte, zumal man nicht sonderlich viele Informationen senden muss und diese ja auch noch komprimieren kann. Also habe ich alles umgebaut.

Benutzerauthentifizierung

Ich habe das tatsächlich so ähnlich, wie im vorherigen Blogeintrag, implementiert. Letztlich ist es so: Der Client macht eine Authentifizierungsanforderung an den Server. Der Server kennt den Benutzernamen und schickt eine UUID. Dann werden die Benutzerinformationen mit der UUID zusammengefasst und gehasht, zurückgeschickt, der Server macht das selbe und vergleicht beide Ergebnisse. Sind sie gleich, gilt der Benutzer als eingeloggt.

Weiteres

Weiterhin habe ich noch Datenbankabstraktionen implementiert für MySQL, PostgreSQL und SQLite, damit man Benutzer speichern kann. Ein Logging habe ich implementiert und dem Server kann man Signale schicken, um ihn zu beenden oder sonst was zu tun. Es gibt einen Testclient unter resources/sslclient, mit dem man mit dem Server ein wenig spielen kann.

Hier geht es übrigens zum GitHub-Repo.

KooKooK (Update 2): Überlegungen zur Benutzerauthentifizierung

Benutzer sollen sich am System anmelden können. Ich dachte mir, die speichern wir einfach in einer relationalen Datenbank, da Qt dafür schon Bibliotheken mitbringen (MySQL, PostgreSQL, SQLite). Welches DBMS wir nehmen, weiß ich noch nicht.

Dennoch muss die Benutzerauthentifizierung ja implementiert werden. Naiv könnte man das so machen:

  1. Client schickt Benutzernamen und Kennwort an Server
  2. Server prüft per Datenbank Benutzernamen und Kennwort
  3. Server gibt OK oder NK zurück

Jetzt will ich aber, wenn auch verschlüsselt, keinen Benutzernamen und kein Kennwort einfach durch die Gegend schicken. Meine Idee war folgende:

  1. Auf dem Server sind Benutzername und Kennwort in der Datenbank hinterlegt, wobei das Kennwort gehasht ist
  2. Client meldet sich bei Server, dass er sich authentifizieren will
  3. Server schickt einen einfache String (z.B. eine UUID) an den Client
  4. Der Client hasht sein Kennwort in der selben Weise, wie der Server, und fügt im String Benutzernamen und Kennwort zusammen (thorstenAdsfjlksudSDFAalsj)
  5. Mit dem zusammengefügten Benutzernamen und gehashten Kennwort verschlüsseln wir den vom Server geschickten String (z.B. die UUID)
  6. Den verschlüsselten String schicken wir an den Server zurück
  7. Der Server prüft den verschlüsselten String mit seinem eigenen (er macht das selbe online)
  8. Der Server gibt OK oder NK zurück

Was haltet ihr davon? Eine gute Idee? Hat jemand eine bessere?

KooKooK (Update 1): Ein paar Implementierungen ohne Video

Ich habe überlegt: ich kann leider nicht alles zu dem Projekt aufnehmen. Ich habe noch ein paar Erweiterungen gemacht. Was alles, seht ihr im GitHub-Projekt.

Hier eine Liste:

  • Über die Server.ini kann man den Server jetzt teils konfigurieren
  • Einfaches Logging wurde eingebaut
  • Threads können beendet werden
  • Kompilierungsscript wurde erweitert
  • Unix-Signal-Handling wurde implementiert (TERM und HUP)

Schreibt mir gerne bei Fragen und Anregungen.

FreeBSD-Grundkurs 031: Virtualisierung mit bhyve und vm-bhyve

Wer mit FreeBSD Virtualisierung durchführen möchte, hat mehrere Möglichkeiten: Jails, VirtualBox und QEmu. Mit bhyve bietet FreeBSD aber ein eigenes Virtualisierungsprogramm an, welches sehr einfach mit vm-byhve zu benutzen ist. Wie das geht, zeigt dieses Video.

Virtualisierung mit bhyve und vm-bhyve
Virtualisierung mit bhyve und vm-bhyve

In diesem Beispiel zeige ich, wie man vm-byhve und grub2-bhyve installiert, konfiguriert und ein Debian darin installiert.

Virtualisiertes Debian unter bhyve
Virtualisiertes Debian unter bhyve

Hier einige Kommandos:

# SSH-Verbindung, falls nötig, zum Host aufbauen und darauf achten, dass Escape-Sequenzen von SSH ignoriert werden, da diese sonst nicht an die Console von bhyve geschickt werden können (cu)
ssh -e none $servername

# Installation der benötigten Pakete
pkg install vm-bhyve grub2-bhyve

# ZFS-Dataset erstellen
zfs create storage/bhyve

# /etc/rc.conf bearbeiten
sysrc vm_enable="YES"
sysrc vm_dir="zfs:server/bhyve"

# vm-byhve initialisieren
vm init

# Template kopieren und bearbeiten
cp /usr/local/share/examples/vm-bhyve/debian.conf /server/bhyve/.templates/mydebian.conf

# Switch erstellen und Netzwerk-Interface hinzufügen
vm switch create public
vm switch add public bge0

# ISO-Datei für die Installation von Debian herunterladen
vm iso https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-11.5.0-amd64-netinst.iso

# Virtuelle Maschine erstellen, Festplattengröße auf 16GB setzen
vm create -t mydebian -s 16g mydebian

# Virtuelle Maschine installieren
vm install -f mydebian debian-11.5.0-amd64-netinst.iso

# Virtuelle Maschinen auflisten (inkl. Status)
vm list

# Vorhandene ISO-Dateien ansehen
vm iso

# Automatischer Start der virtuellen Maschinen
vm_list="vm1 vm2"
vm_delay="5"

Hier geht es zum Video.

wxWidgets-Tutorial 008 – Das erste Programm: Netto-Brutto-Rechner

Um uns zu motivieren, schreiben wir in diesem Video unser erstes eigenes kleines Programm. Ihr könnt mal schauen, ob wxWidgets das Richtige für euch ist und schon, nach dem ersten Erfolgserlebnis, damit ein wenig herumspielen.

Das erste Programm: Netto-Brutto-Rechner
Das erste Programm: Netto-Brutto-Rechner

Und so sieht das Programm aus:

Beispielprogramm
Beispielprogramm

Hier noch der Quelltext:

#include <wx/wx.h>

class BruttoNettoApp : public wxApp {

	public:
		bool OnInit();

};

class BruttoNettoFrame : public wxFrame {

	public:
		BruttoNettoFrame();
	
	private:
		wxPanel *mainPanel;
		wxBoxSizer *mainBoxSizer;
		wxTextCtrl *priceTextCtrl;
		wxStaticText *multiplicatorStaticText;
		wxComboBox *taxComboBox;
		wxButton *calculateButton;
		wxTextCtrl *resultTextCtrl;

};

IMPLEMENT_APP(BruttoNettoApp)

bool BruttoNettoApp::OnInit() {
	BruttoNettoFrame *bruttoNettoFrame = new BruttoNettoFrame;
	bruttoNettoFrame->Show();
	
	SetTopWindow(bruttoNettoFrame);

	return true;
}

BruttoNettoFrame::BruttoNettoFrame() : wxFrame(nullptr, wxID_ANY, "Brutto-Netto-Rechner") {
	mainPanel = new wxPanel(this, wxID_ANY);
	mainBoxSizer = new wxBoxSizer(wxHORIZONTAL);

	priceTextCtrl = new wxTextCtrl(mainPanel, wxID_ANY);
	mainBoxSizer->Add(priceTextCtrl, 1);

	multiplicatorStaticText = new wxStaticText(mainPanel, wxID_ANY, "x");
	mainBoxSizer->Add(multiplicatorStaticText);

	taxComboBox = new wxComboBox(mainPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY);
	taxComboBox->Append("7%");
	taxComboBox->Append("19%");
	taxComboBox->SetSelection(0);
	mainBoxSizer->Add(taxComboBox);

	calculateButton = new wxButton(mainPanel, wxID_ANY, "=");
	calculateButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent &event) {
		double price = 0.f;
		if(priceTextCtrl->GetValue().ToDouble(&price)) {
			price *= taxComboBox->GetSelection() == 0 ? 1.07 : 1.19;
			resultTextCtrl->SetValue(wxString::Format(_("%lf"), price));
		} else {
			wxMessageBox("Wrong price", "Error");
		}
	});
	mainBoxSizer->Add(calculateButton);
	
	resultTextCtrl = new wxTextCtrl(mainPanel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
	mainBoxSizer->Add(resultTextCtrl, 1);

	mainPanel->SetSizer(mainBoxSizer);
	mainBoxSizer->SetSizeHints(this);

	SetSize(500, -1);

	priceTextCtrl->SetFocus();
	calculateButton->SetDefault();
}

Wer Windows und VisualStudio benutzt, kann auf den Play-Button klicken. Unter anderen Systemen könnte die Kompilierung so aussehen:

c++ NettoBruttoRechner.cpp -o NettoBruttoRechner -std=c++11 `wx-config --libs --cppflags`

Auf die einzelnen Komponenten gehe ich dann Stück für Stück in den nächsten Videos ein. Also keine Angst, falls Ihr hier etwas nicht verstehen solltet, das kommt bald.

Hier geht es zum Video.

FreeBSD-Grundkurs 029: RAID1 mit GMIRROR

Datenredundanz ist mitunter ein sehr wichtiges Theman. Wie man ein (Software-) RAID1 mit FreeBSD und GMIRROR aufbaut, zeigt dieses Video.

RAID1 mit GMIRROR
RAID1 mit GMIRROR

Die Erstellung eines einfach RAID1 funktioniert so:

# Erste Festplatte labeln
gmirror label -b round-robin $name $device1

# gmirror laden (für den Boot 'geom_mirror_load="YES"' in /boot/loader.conf eintragen
gmirror load

# Zweite Festplatte einbinden
gmirror insert $name $device2

# Status ansehen
gmirror status

Alle wichtigen Dinge und auch Beispiele stehen in der Manpage. Bitte immer lesen!

Hier geht es zum Video.

Qt-Tutorial 041: Settings (Konfigurationsdateien speichern und laden)

Dieses Video zeigt, wie einfach es ist, mit Qt Konfigurationsdateien zu speichern und zu laden. Dazu benutze ich QSettings.

Settings (Konfigurationen)
Settings (Konfigurationen)

Hier der Beispielcode:

#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QSettings>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
	: QMainWindow(parent)
	, ui(new Ui::MainWindow)
{
	ui->setupUi(this);

	QCoreApplication::setApplicationName("YouTube-Tutorial");
	QCoreApplication::setOrganizationName("DynSoft");
	QCoreApplication::setOrganizationDomain("dynsoft.com");
}

MainWindow::~MainWindow()
{
	delete ui;
}

void MainWindow::log()
{
	ui->logPlainTextEdit->appendPlainText("");
}

void MainWindow::log(const QString &message)
{
	ui->logPlainTextEdit->appendPlainText(message);
}

void MainWindow::log(const QString &prefix, const QString &message)
{
	ui->logPlainTextEdit->appendPlainText(prefix + ": " + message);
}


void MainWindow::on_savePushButton_clicked()
{
	//QSettings settings("C:\\Users\\thorsten\\Desktop\\Tutorial.ini", QSettings::IniFormat);
	//QSettings settings("DynSoft", "QtTutorialSettings");
	QSettings settings;

	settings.setValue("Window/PositionX", pos().x());
	settings.setValue("Window/PositionY", pos().y());
	settings.setValue("Window/Width", size().width());
	settings.setValue("Window/Height", size().height());
	settings.setValue("Window/Size", size());

	settings.beginGroup("UserSettings");
	settings.setValue("Username", "Thorsten");
	settings.endGroup();
}


void MainWindow::on_loadPushButton_clicked()
{
	//QSettings settings("C:\\Users\\thorsten\\Desktop\\Tutorial.ini", QSettings::IniFormat);
	//QSettings settings("DynSoft", "QtTutorialSettings");
	QSettings settings;

	log("Window");
	log("PositionX", settings.value("Window/PositionX").toString());
	log("PositionY", settings.value("Window/PositionY").toString());
	log("Width", settings.value("Window/Width").toString());
	log("Height", settings.value("Window/Height").toString());
	qDebug() << settings.value("Window/Size").toSize();

	log();

	log("UserSettings");
	settings.beginGroup("UserSettings");
	log("Username", settings.value("Username").toString());
	settings.endGroup();
}

Hier geht es zum Video.