PI Guide/ Programmare con GNU/Linux

PI Guide/ Programmare con GNU/Linux

Dritte, consigli ed esempi pratici per iniziare a sviluppare software con la tua distro preferita
Dritte, consigli ed esempi pratici per iniziare a sviluppare software con la tua distro preferita

Grazie alle possibilità offerte, GNU/Linux è il paradiso dei programmatori. Sia i principianti che gli esperti in questo sistema operativo open source trovano qualcosa che fa per loro e i tool necessari per realizzare le proprie idee. Abbiamo così deciso di proporre una serie di programmi, sviluppati in diversi linguaggi, dai più semplici ai più complessi, per invogliare chi ancora non sa programmare ma che vorrebbe iniziare a farlo (e a costo zero). Risolveremo semplici problemi reali, semplificando la vita di tutti giorni: scopriremo come scrivere software che manipolano per noi pagine Web, immagini, musica e tanto altro. Infine ci apriremo alla programmazione Android. Esempi utili, dunque, ma anche necessari per riuscire a comprendere appieno la magia della “programmazione libera” su GNU/Linux.

Partiamo da QtCreator
Prima di addentrarci negli esempi pratici installiamo nella nostra distro l’ambiente di sviluppo e le librerie necessarie che ci serviranno nel corso dell’articolo o per eventuali sperimentazioni “fai da te”.

Per installare QtCreator su un sistema di tipo Debian, basta dare da un terminale il comando sudo apt-get install qtcreator . Questo comando installa automaticamente anche le principali librerie Qt.

Per poter utilizzare tutti i vari componenti delle librerie Qt, e non solo quelli di base, dobbiamo installarli. Possiamo farlo selezionando automaticamente tutti i pacchetti con il comando sudo apt-get install ‘libqt5*’ .

Appena apriamo il programma QtCreator, abbiamo diverse opzioni: possiamo creare un nuovo progetto cliccando sul pulsante New Project oppure possiamo aprirne uno (è possibile importare progetti makefile e cmake). Ma possiamo anche provare degli esempi.

Quando abbiamo caricato un progetto e vogliamo provare ad eseguirlo, possiamo cliccare sul menu Build e scegliere direttamente la voce Run . Se vogliamo soltanto compilare il programma, senza eseguirlo, basta cliccare sulla voce Build all .

Con QtCreator, inoltre, possiamo disegnare interfacce grafiche per i nostri programmi e convertirle in codice Python.
Per farlo, apriamo QtCreator e dal menu File scegliamo Nuovo file . Indichiamo che vogliamo realizzare un nuovo file di tipo Qt , un Qt Designer Form . Basta selezionarlo e cliccare sul pulsante Avanti per cominciare a disegnare.

Dopo avere salvato l’interfaccia grafica disegnata in un file .ui , possiamo facilmente tradurla in codice Python con il comando pyuic4 mainwindow.ui -o mainwindow_ui.py , dove il primo nome è quello della UI e il secondo quello del file da ottenere.

ESEMPIO 1: Link e immagini di una pagina Web
Linguaggio usato : Bash – Difficoltà : Bassa
Scarica da qui il codice completo

Cominciamo con qualcosa di semplice: uno script per il terminale Bash . Lavoreremo con la riga di comando, quindi possiamo creare il nostro script direttamente con un editor di testo da terminale, come Nano :

nano estrai-url.sh

Il codice dello script è abbastanza semplice:

pagina=$1

Prima di tutto costruiamo una variabile in cui memorizzare l’URL della pagina che è stata indicata come argomento dello script (il primo argomento è $1 ).

if
In questo caso, dobbiamo prima di tutto scaricare la pagina Web in questione utilizzando il programma curl . Poi, passiamo il codice HTML della pagina direttamente al programma grep , per estrarre tutte le righe che contengono la dicitura href seguita dal simbolo = e dalle virgolette. Questa formula, infatti, presuppone la presenza di un link. Selezionate le righe che contengono dei link, utilizziamo ancora la pipe (il simbolo | ) per passare queste righe al comando sed . È quest’ultimo ad occuparsi di estrarre tutto il testo contenuto tra href=” e . Significa che se nella riga su cui si sta lavorando è scritto href=http://www.linux-magazine.it/test.png , ciò che sed estrarrà sarà http://www.linux-magazine.it/test.png . Proprio ciò che volevamo.
Dopo avere salvato lo script con Ctrl+O ed avere chiuso Nano con Ctrl+X , possiamo rendere lo script eseguibile con chmod :

chmod +x estrai-url.sh

Lo script può poi essere lanciato indicando la pagina Web che vogliamo leggere:

./estrai-url.sh http://www.linux-magazine.it/

Otterremo il file di testo links.txt con tutti gli URL dei file da scaricare, che poi possiamo dare in pasto a programmi come [JDownloader[http://jdownloader.org/download/index”> ${pagina:0:4} = “http” ]; then

Ci serve un semplice ciclo if per capire se la pagina Web indicata si trova su Internet o se è in locale. Basta controllare le prime 4 lettere (dalla 0 alla 4 non inclusa), e vedere se corrispondono ad http (se le prime lettere iniziano con questa stringa è ovvio che abbiamo a che fare con una pagina presente sul Web).

curl $pagina | grep -o 'href=
.

ESEMPIO 2: Watermark per le immagini
Linguaggio usato : Python - Difficoltà : Bassa
Scarica da qui il codice completo

Passiamo ora ad un programma leggermente più complesso, da sviluppare in Python . Lo realizziamo per applicare automaticamente un watermark, ovvero una firma, su una fotografia.

Prima di iniziare, però, occorre una piccola precisazione sull' indentazione in Python.
L'identazione è l'identificazione di porzioni di codice tramite spazi a inizio riga. Ad esempio, possiamo decidere di aggiungere due spazi all'inizio di ogni riga contenuta in un ciclo if. In questo modo si riconoscono a colpo d'occhio le righe del ciclo if da quelle ad esso esterne, perché le altre non hanno gli spazi iniziali. Su quasi tutti i linguaggi di programmazione l'indentazione è facoltativa, anche se fortemente consigliata per migliorare la leggibilità del codice. In Python, invece, no: è obbligatoria, perché è l'unico modo per distinguere le varie porzioni di codice.
Invitiamo ad analizzare il codice completo dell'esempio per una corretta visualizzazione delle identazioni, in quanto potrebbero non essere correttamente visualizzate qui sul sito.

Cominciamo dunque con il codice del nostro esempio:

import sys
import os
import Image

Le prime righe sono dedicate alle librerie necessarie per il funzionamento del programma. Si tratta di normali librerie Python, presenti di default su tutte le principali distro GNU/Linux.

def modifica(filename, watermarkname):

Ora, definiamo una funzione: gli attributi sono due stringhe di testo, ovvero filename e watermarkname . Si tratta dei nomi dell'immagine su cui applicare il watermark e di quello dell'immagine che costituisce il watermark stesso. Banalmente, incolleremo l'immagine watermark sopra l'altra immagine, posizionandola al centro. Ma per farlo ci servono i nomi delle due immagini.

photo = Image.open(filename)
watermark = Image.open(watermarkname)

Ecco quindi che all'interno della funzione apriamo le due immagini ricevute come argomento: d'ora in poi potremo accedere alle immagini utilizzando i loro oggetti di riferimento, ovvero photo per la fotografia e watermark per il watermark.

Pwidth = photo.size[0]
Pheight = photo.size[1]
Wwidth = watermark.size[0]
Wheight = watermark.size[1]

Memorizziamo in quattro variabili larghezza ( width ) e altezza ( height ) di entrambe le immagini. La dimensione di ogni immagine è fornita dalla sua funzione size , quindi photo.size ci dice le dimensioni dell'immagine photo . Però, la dimensione viene fornita come una "tupla", ovvero una coppia di numeri (per l'appunto larghezza ed altezza).

x = (Pwidth/2)-(Wwidth/2)
y = (Pheight/2)-(Wheight/2)

Ora, calcoliamo la posizione in cui dovremo apporre il watermark. L'idea è semplice: se vogliamo che il watermark sia posto al centro delle foto, basta calcolare il centro della foto. Le cui coordinate sono banalmente la larghezza e l'altezza divise a metà, ovvero Pwidth/2 e Pheight/2 . Però non basta: per tutte le immagini, il conteggio viene fatto dall'angolo in alto a sinistra. Quindi se utilizziamo questo semplice calcolo, il watermark si troverà con il proprio angolo alto a sinistra sul centro della fotografia, e quindi non risulterà allineato. Noi vogliamo, infatti, che il centro del watermark coincida con il centro della foto. Basta sottrarre ai numeri calcolati per il centro della foto, i valori che possiamo calcolare per il centro del watermark (larghezza e altezza del watermark divise a metà).

photo.paste(watermark, (x, y), watermark)

Ora possiamo incollare l'immagine watermark sopra l'immagine photo . La posizione è quella indicata dalla tupla (x,y) , ovvero la coppia di coordinate che abbiamo calcolato.

photo.save(filename.split(".")[0]+"_watermark.jpg")

Dobbiamo poi salvare l'immagine così prodotta, che è contenuta nell'oggetto photo . La funzione save richiede come argomento il nome del file su cui vogliamo salvare l'immagine: possiamo ricavarlo dal nome originale della fotografia. Il nome originale è contenuto nella variabile filename , ma questo nome contiene anche l'estensione del file (per esempio .jpg ). Noi vogliamo rimuoverla, quindi dividiamo il filename con la funzione split prelevando tutto ciò che si trova a sinistra ( 0 ) del punto. Estratto soltanto il nome, aggiungiamo con l' operatore + la scritta "_watermark.jpg" . Quindi, se la fotografia originale si chiamava foto.jpg ora la versione modificata si chiamerà foto_watermark.jpg .

if len(sys.argv) < 3:
print "Esempio d'uso: python watermark.py foto.jpg watermark.png"
sys.exit()

La funzione è terminata: cominciamo a scrivere il codice principale. Dobbiamo prima di tutto verificare che al programma siano stati forniti gli argomenti necessari: ci servono il nome della fotografia e quello del watermark. Abbiamo bisogno di avere almeno 3 argomenti: il primo è sempre il nome stesso del programma, il secondo e il terzo possono essere ciò che vogliamo (i nomi delle due immagini). Gli argomenti sono inseriti in un "array", ovvero un elenco di variabili (stringhe di testo nel nostro caso), chiamato sys.argv . Se i suoi elementi sono meno di 3, il programma è stato lanciato in modo errato e quindi facciamo apparire sul terminale un messaggio con il comando print per spiegare come funziona il nostro programma. Naturalmente, poi il programma deve terminare con sys.exit() perché non è in grado di funzionare correttamente.

ESEMPIO 3: Browser web fai da te
Linguaggio usato : Python - Difficoltà : Media
Scarica da qui il codice completo

È il momento di realizzare il nostro primo programma a interfaccia grafica per GNU/Linux: un browser Web molto semplice e rapido, senza memorizzazione della cronologia e dunque comodo per la navigazione in incognito. Lo realizzeremo con Python utilizzando le librerie grafiche PyQt4 (installabili su un sistema Debian-like con il comando sudo apt-get install python-qt4 pyqt4-dev-tools qt4-designer ). Si tratta della versione Python delle librerie Qt.

import sys
from PyQt4 import QtCore, QtGui, QtWebKit

Come nell'esempio precedente, le prime righe riguardano l'inclusione di librerie utili per il nostro programma.

class Browser(QtGui.QMainWindow):

Dichiariamo una "classe", che rappresenterà la nostra finestra grafica. Una classe è l'insieme del codice necessario per un particolare oggetto (nel nostro caso la finestra del browser), e ci permetterà di utilizzare la finestra in modo comodo. La finestra viene costruita sulla base di un QMainWindow , un componente delle librerie PyQt che per l'appunto propone un modello di finestra grafica.

def __init__(self):

Ogni classe ha una funzione chiamata __init__ . Questa è il "costruttore" della classe, ovvero la funzione che viene automaticamente chiamata ogni volta che qualcuno decide di realizzare un nuovo oggetto con la classe in questione. Nel nostro caso, questa funzione deve disegnare l'interfaccia grafica della finestra, così sarà poi possibile mostrarla.

QtGui.QMainWindow.__init__(self)

Prima di tutto, avviamo la funzione di costruzione di QMainWindow, così il grosso del lavoro verrà svolto dalle librerie Qt.

self.resize(800,600)

Dobbiamo dare una dimensione di partenza alla finestra: poi sarà comunque possibile ridimensionarla, ma cominciamo con una finestra grande 800x600 pixel.

self.centralwidget = QtGui.QWidget(self)
self.mainLayout = QtGui.QHBoxLayout(self.centralwidget)
self.mainLayout.setSpacing(0)
self.mainLayout.setMargin(1)

Ogni finestra deve avere un elemento centrale: lo chiamiamo mainLayout , ed è per l'appunto un "layout orizzontale" ( QHBoxLayout ). Un layout orizzontale è un oggetto grafico che contiene tutti gli altri oggetti e li dispone automaticamente sulla finestra in modo ordinato su una sola linea orizzontale. Questo primo Layout in realtà ci serve solo come contenitore generale, perché poi realizzeremo altri layout per disporre i vari elementi come vogliamo.

self.frame = QtGui.QFrame(self.centralwidget)
self.gridLayout = QtGui.QVBoxLayout(self.frame)
self.gridLayout.setMargin(0)
self.gridLayout.setSpacing(0)

Ci serve un layout verticale, per poter posizionare la barra degli strumenti. In poche parole, ciò che faremo sarà inserire questo layout verticale in una colonna del layout orizzontale generale. È in questo layout verticale che verranno inserite la barra degli strumenti e il visualizzatore HTML, una sopra l'altro.

self.horizontalLayout = QtGui.QHBoxLayout()
self.tb_url = QtGui.QLineEdit(self.frame)
self.bt_back = QtGui.QPushButton(self.frame)
self.bt_ahead = QtGui.QPushButton(self.frame)

La barra degli strumenti è anch'essa un semplice layout orizzontale: ogni colonna del livello verrà occupata da un pulsante ( bt_back o bt_ahead ) o dalla casella dell'indirizzo Web ( tb_url ).

self.bt_back.setIcon(QtGui.QIcon().fromTheme("go-previous"))
self.bt_ahead.setIcon(QtGui.QIcon().fromTheme("go-next"))

Costruiamo i due pulsanti necessari: uno per andare indietro nella cronologia temporanea, l'altro per andare avanti. Potremmo inserire un testo in ciascun pulsante, ma è meglio ricorrere a un'immagine, un'icona. E possiamo prelevarla direttamente dal set di immagini standard fornito dalle librerie Qt: basta indicare i loro nomi, go-previous e go-next .

self.connect(self.tb_url, QtCore.SIGNAL("returnPressed()"), self.browse)
self.connect(self.bt_back, QtCore.SIGNAL("clicked()"), self.html.back)
self.connect(self.bt_ahead, QtCore.SIGNAL("clicked()"), self.html.forward)
self.connect(self.html, QtCore.SIGNAL("urlChanged(const QUrl)"), self.url_changed)

Abbiamo inserito i vari elementi nella finestra. Però devono anche fare qualcosa: dovremo associare a ciascun elemento una specifica funzione. Questo si può fare collegando con la funzione connect il segnale emesso da uno degli elementi della finestra e le funzioni che costruiremo per le varie operazioni. Ad esempio, quando viene premuto il tasto Invio o Return sulla casella di testo dell'URL deve essere lanciata la funzione self.browse , che scriveremo. Quando vengono premuti i due pulsanti vengono lanciate direttamente le funzioni del QWebKit che si occupano di andare avanti o indietro nella cronologia temporanea. Infine, quando la pagina visualizzata dal QwebKit cambia, viene lanciata la funzione self.url_changed . Questo avviene tipicamente quando l'utente ha cliccato su un link.

self.default_url = "http://www.linux-magazine.it/"
self.tb_url.setText(self.default_url)
self.browse()

Per concludere la funzione iniziale della classe che stiamo scrivendo, impostiamo un URL di default, praticamente la nostra home page. Questo URL è impostato nella casella di testo apposita, e poi si può lanciare la funzione browser per ordinare la visualizzazione della pagina Web indicata nella casella di testo.

def browse(self):
url = self.tb_url.text() if self.tb_url.text() else self.default_url
self.html.load(QtCore.QUrl(url))
self.html.show()

if __name__ == "__main__":

La classe browser è ora terminata, quindi possiamo procedere a scrivere il codice principale del programma ( __main__ ).

app = QtGui.Qapplication(sys.argv)

Prima di tutto vogliamo creare una applicazione Qt, cioè un'app grafica tramite le librerie Qt, che gestiranno per noi la visualizzazione sullo schermo e i rapporti con il server grafico.

main = Browser()
main.show()

Poi, possiamo creare un oggetto che rappresenti la classe Browser definita poco fa: basta inventare un nuovo oggetto (che chiamiamo main ) e associarlo alla classe. Da questo momento main sarà la finestra grafica della nostra applicazione, e possiamo farla apparire sullo schermo chiamando la sua funzione show . La funzione show non era stata manualmente definita nel codice che abbiamo presentato: questo perché faceva già parte del set di funzioni predefinite di QMainWindow, la libreria da cui la nostra classe Browser ha ereditato funzioni e attributi.

app.exec_()

Infine, ordiniamo il lancio ( exec_() ) dell'applicazione che abbiamo appena terminato di scrivere. Ci si può chiedere perché si usi la dicitura exec_ invece di exec . Il fatto è che fino alla versione 2 di Python, la parola exec era riservata e non poteva essere usata per le funzioni. È stato quindi necessario aggiungere un underscore per mantenere la compatibilità con Python 2.

ESEMPIO 4: Server TCP da zero
Linguaggio usato : C++ - Difficoltà : Alta
Scarica da qui il codice completo

Far comunicare due computer è abbastanza facile: esistono molti programmi progettati appositamente per questo scopo. Potremmo però avere bisogno di qualcosa che sia costruito specificatamente attorno alle nostre esigenze. Per fare un esempio, potrebbe essere utile un server che rimanga in attesa sul nostro Raspberry Pi e che, quando ci connettiamo ad esso, ci invii un'immagine scattata in tempo reale dalla webcam: un semplice sistema di sorveglianza remota. Per realizzare il nostro server TCP utilizzeremo soltanto librerie standard C++ , ma per semplificare la compilazione possiamo ricorrere a QtCreator, l'ambiente di sviluppo ufficiale delle librerie Qt. Il file del progetto, serverTCP.pro , è il seguente:

QT += core
QT -= gui
TARGET = serverTCP
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
HEADERS = base64.h
SOURCES += serverTCP.cpp \
base64.cpp

Specifichiamo che non ci servono le librerie grafiche, che stiamo realizzando un programma per console, e che il codice sorgente è contenuto nel file serverTCP.cpp . Aggiungiamo anche due file con le funzioni base64 , una libreria che sfrutteremo per codificare i file. Ora possiamo cominciare a scrivere il nostro codice sorgente:

#include <cstdlib>
#include <iostream>
...
#include <fstream>
#include "base64.h"
using namespace std;

Le prime righe del codice, come negli esempi che abbiamo scritto in Python, servono a includere le librerie. Le librerie necessarie sono tante, perché il programma è ovviamente più complesso di quelli che abbiamo visto finora.

int BUFFERSIZE = 4096;

Definiamo in una variabile, come numero intero, la dimensione massima del buffer per i messaggi che possiamo ricevere.

void dostuff (int sock, sockaddr_in cli_addr, pid_t pid);
std::string decToDotIP(long num);
void handleSIGCHLD(int n);
void error(const char *msg)
{
perror(msg);
exit(1);
}

Definiamo tre funzioni che scriveremo tra poco, mentre ne definiamo una semplice immediatamente: serve solo a far apparire un messaggio di errore sullo schermo se qualcosa va storto.

int main(int argc, char *argv[])
{

La funzione principale di un programma C++ è main .

signal(SIGCHLD, handleSIGCHLD);

Il nostro server costruirà dei "figli": praticamente, il programma si clona ogni volta che un nuovo client lo contatta. E ogni clone deve essere terminato quando il client interrompe la comunicazione: in quel momento il clone invia il segnale SIGCHLD , e dobbiamo assegnargli una funzione per gestirlo correttamente.

int sockfd, newsockfd, portno;
socklen_t clilen;
struct sockaddr_in serv_addr, cli_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) error("ERROR opening socket");
std::cout << "This is my server.\n";

Una connessione TCP avviene tramite un socket, che deve essere aperto tramite la funzione socket() . Il socket che realizziamo è basato sulla famiglia di indirizzi IP AF_INET , ovvero gli indirizzi IPv4.

bzero((char *) &serv_addr, sizeof(serv_addr));
if (argc < 2) {
portno = 1612;
}
if (argc > 1) portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);

Il socket deve poi essere configurato: il dato più importante è la porta di comunicazione TCP che vogliamo assegnare al nostro server: ne dobbiamo scegliere una non utilizzata da altri programmi, ad esempio la 1612 . Possiamo lasciare che la porta venga specificata all'avvio del programma server come argomento del programma (e in tal caso sarà l'elemento 1 dell'array argv , come abbiamo visto in Python). E possiamo impostarne una predefinita nel caso non sia stata indicata come argomento. Visto che stiamo realizzando un server, questo programma deve rimanere in ascolto sul socket che abbiamo aperto: possiamo farlo con la funzione listen .

while (1) {

Cominciamo un ciclo infinito (il ciclo while continua finché la condizione espressa tra parentesi è uguale ad 1, ed 1 è sempre uguale ad 1), nel quale il server attenderà l'arrivo delle connessioni.

newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) error("ERROR on accept");

Appena arriva una richiesta di connessione da parte di un client, la accettiamo.

pid_t pid = fork();
if (pid < 0) error("ERROR on fork");

Creiamo un fork, ovvero un clone del programma server da dedicare esclusivamente alla connessione appena accettata.

if (pid != 0) {
std::cout << "Opened new child process with pid " << pid << "." << std::endl;
dostuff(newsockfd, cli_addr, pid);
close(newsockfd);
}
}

Se la creazione del figlio clonato è andata a buon termine, dobbiamo ovviamente dire al clone che cosa deve fare per gestire correttamente la connessione che gli abbiamo assegnato: lo facciamo lanciando la funzione dostuff , che vedremo tra poco.

close(sockfd);
return 0;
}

Quando il ciclo while viene interrotto, perché è successo un imprevisto, possiamo chiudere il socket e terminare il server.

void handleSIGCHLD(int n)
{
int stat;
while(waitpid(-1, &stat, WNOHANG) > 0);
}

La funzione handleSIGCHLD , che abbiamo già menzionato, si occupa proprio di gestire questo segnale emesso dai figli: la funzione rimane in attesa finché il clone non è definitivamente terminato. Se non lo facessimo, il clone potrebbe diventare un processo "zombie", che continua a utilizzare risorse del processore senza però essere più controllato dal processo padre.

void dostuff (int sock, sockaddr_in cli_addr, pid_t pid)
{
int n;
char buffer[BUFFERSIZE];
long empty = 0;
int found = 0;

Questa è la funzione che stabilisce il comportamento del server: tutto ciò che abbiamo visto finora sono le routine standard di un server, ma adesso scriveremo il codice che fa funzionare davvero il nostro server.

while (1) {
bzero(buffer,BUFFERSIZE);
n = read(sock,buffer,BUFFERSIZE-1);

Grazie ad un altro ciclo infinito leggiamo continuamente i messaggi inviati sul socket da parte del client. I messaggi vengono letti dalla funzione read, ed inseriti nella variabile buffer che è un array di caratteri inizializzato con una certa dimensione grazie alla funzione bzero.

if (n < 0) error("ERROR reading from socket");
std::string answ(buffer);
if (answ!="") std::cout << "Message from " << decToDotIP(cli_addr.sin_addr.s_addr) << ": " << answ << std::endl;

Per comodità, trasformiamo il buffer in una stringa, che è molto più gestibile di un array di caratteri.

if (answ=="") empty++;

Se la risposta del client è vuota (quindi non c'è risposta), teniamo il conto sulla variabile empty, la quale incrementa il suo valore di una unità: empty++ è un modo conciso per dire empty+1 .

if (empty > 2000000) {
kill(pid, SIGTERM);
std:cout << "Killed process " << pid << "." << std::endl;
return;
}

Se il client non sta rispondendo da molto tempo, terminiamo (inviando il segnale SIGTERM ) il processo clone per evitare che la connessione con il client possa rimanere aperta: è il concetto di timeout.

if (answ.substr(0,5) == "HELLO")
{
found++;
std::cout << "Sending welcome message." << std::endl;
n = write(sock,"Ìm ready to listen to your commands.",38);
if (n < 0) std::cout << "ERROR writing to socket";
}

In questo momento la stringa answ contiene il completo messaggio inviato dal client al server, quindi possiamo cercare di interpretarla per capire che cosa vuole fare l'utente. Ad esempio, se le prime 5 lettere sono la parola HELLO , probabilmente l'utente vuole solo salutare il server per assicurarsi di essere davvero connesso. Abbiamo appena definito un comando del nostro server, il comando HELLO. Si può rispondere scrivendo sul socket una frase che faccia capire che il server è davvero pronto. La frase che scriviamo è lunga 38 caratteri, quindi possiamo indicare questo numero come terzo parametro della funzione write , che si occupa di scrivere sul socket.

if (answ.substr(0,6) == "WEBCAM")
{
found++;

Ora definiamo un altro comando: WEBCAM . Questo comando deve far eseguire al server uno scatto tramite la propria webcam e poi inviare l'immagine attraverso il socket.

std::string encoded = "WEBCAM";

Prepariamo la stringa da inviare come risposta al client: comincerà con la scritta WEBCAM, così il client saprà che questa risposta contiene l'immagine della webcam.

system("streamer -s 176x144 -f jpeg -o /tmp/image.jpeg");

È ovviamente necessario ottenere l'immagine della webcam: potremmo produrla con le librerie di Video4Linux , ma sarebbe complicato. Molto meglio ricorrere a un programma a parte, che può essere installato su un sistema Debian-like con il comando sudo apt-get install streamer . Streamer produrrà per noi uno scatto dalla webcam, della risoluzione di 176x144 pixel. L'immagine verrà salvata nel file /tmp/image.jpeg .

std::ifstream infile ("/tmp/image.jpeg",std::ifstream::binary);
infile.seekg (0,infile.end);
long size = infile.tellg();
infile.seekg (0);

Possiamo quindi leggere il file immagine con la libreria ifstream . Il file è binario, naturalmente, perché è una immagine e non un semplice file di testo. Sfruttando la funzione seekg ci spostiamo all'ultimo byte dell'immagine, per calcolarne la dimensione e memorizzarla nella variabile size .

n = write(sock,encoded.c_str(),encoded.length());
encoded.clear();
if (n < 0) std::cout << "ERROR writing to socket";
}

Adesso possiamo scrivere la stringa encoded sul socket. La funzione write accetta soltanto array di caratteri, ma per fortuna è facile convertire una stringa in array di caratteri: basta usare la sua funzione c_str . Così, il client che ci aveva scritto "WEBCAM" ora riceve in risposta l'immagine della webcam in formato base64.

Il client, che dobbiamo sviluppare per fare coppia con il nostro server, è molto semplice. Il suo file di progetto, clientTCP.pro , è praticamente identico a quello del server, cambia solo il nome del file sorgente. Il suo codice sorgente, contenuto nel file clientTCP.cpp , comincia con l'inclusione delle stesse librerie che abbiamo utilizzato per il server. Poi procede:

int main(int argc, char *argv[])
{
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
int BUFFERSIZE = 4096;//256;
char buffer[BUFFERSIZE];

La funzione principale ( main ) deve definire il buffer , l'array che utilizzeremo per i messaggi: deve avere la stessa dimensione che abbiamo indicato per il buffer del server.

if (argc < 3) {
portno = 1612;
server = gethostbyname("127.0.0.1");
}
printf("This is the client for my server. Please type HELLO or WEBCAM. \n\n");
if (argc > 2) {
portno = atoi(argv[2]);
server = gethostbyname(argv[1]);
}

Il server aveva bisogno di conoscere la porta TCP su cui lavorare. Il client ha bisogno anche dell'indirizzo IP del server da contattare (nel caso non si specificato tra gli argomenti del programma client, si da per scontato che sia localhost, ovvero 127.0.0.1).

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
error("ERROR connecting");

Specificati i vari parametri, che abbiamo già visto per il server, possiamo connetterci ad esso tramite la funzione connect . Se tutto va bene, otterremo un socket chiamato sockfd per la comunicazione.

n = write(sockfd,buffer,strlen(buffer));
if (n < 0)
error("ERROR writing to socket");

Possiamo scrivere il comando impartito dall'utente sul socket, e dunque inviarlo al server, grazie alla funzione write .

std::string received;
unsigned long long int bytes= 20*1024*1024;
received.resize(bytes);

Il server risponderà qualcosa: dobbiamo preparare una stringa per memorizzarlo. Ne impostiamo la dimensione massima a 20 MB.

int bytes_received = read(sockfd, &received[0], bytes-1);
if (bytes_received <0) {
std::cout << "Failed to read data from socket.\n";
}

Utilizzando la funzione read possiamo leggere la risposta del server e memorizzarla nella stringa received . Alla funzione read non passiamo l'intera stringa, ma solo il puntatore al suo primo carattere (con il simbolo & ). Questo significa che la funzione read comincerà a copiare la risposta del server in tutte le celle della RAM che il sistema operativo ha assegnato alla variabile received , a partire dalla prima.

std::cout << received.c_str() << "\n";

Adesso possiamo far apparire sullo schermo il testo della risposta ricevuta dal server.

if (received.substr(0,6) == "WEBCAM"){

Quando per un qualsiasi motivo il ciclo infinito che permette l'invio dei comandi al server termina, significa che il client non ha più motivo di essere attivo e quindi possiamo chiudere il socket e terminare il programma client con l'istruzione return 0 (che restituisce il valore 0, indicante una corretta chiusura del programma). Server e client sono ora pronti: basta compilarle i due progetti con qmake , avviamo poi entrambe i programmi (prima il server, poi il client) per provarli.

ESEMPIO 5: Player musicale per Android
Linguaggio usato : C++ - Difficoltà : Alta
Scarica da qui il codice completo

Su Android non ci sono molti programmi che riproducano in loop delle scalette musicali, cosa che potrebbe risultare utile durante feste o sessioni di allenamento, ad esempio. Ma possiamo realizzarne una noi con C++ e le librerie Qt: il programma che realizzeremo funziona sia su un sistema GNU/Linux desktop che su Android. Affinché il nostro programma funzioni, dobbiamo avere installato i pacchetti di QtMultimedia: sudo apt-get install libqt5multimedia5-plugins qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5 su un sistema Debian-like. Il file di intestazione della nostra finestra, chiamato mainwindow.h , contiene le definizioni di tutti gli oggetti più importanti. Il codice vero e proprio del programma è contenuto nel file mainwindow.cpp :

#include "mainwindow.h"
#include "ui_mainwindow.h"

Le prime righe includono il file di intestazione e il codice dell'interfaccia grafica: il file ui_mainwindow.h viene infatti prodotto automaticamente da QtCreator sulla base dell'interfaccia che abbiamo disegnato nell'apposito designer (l'interfaccia è memorizzata nel file mainwindow.ui ).

void MainWindow::on_openFolder_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), "/home", QFileDialog::ShowDirsOnly);
ui->lineEdit->setText(dir);
}

Quando il pulsante Open viene premuto, viene eseguita la funzione on_openFolder_clicked() . Questa funzione deve permettere all'utente di selezionare una cartella: possiamo farlo grazie alla libreria QFileDialog::getExistingDirectory , specificando tra i suoi argomenti che debbano essere visualizzate solo le cartelle. Il percorso della cartella scelta può poi essere impostato come testo all'interno della casella (chiamata lineEdit ) presente nell'interfaccia grafica.

QStringList dirList(dir.entryList());
for(int i = 0; i < dirList.count(); i++)
{

Costruiamo una lista dei file della cartella che chiamiamo dirList . Si tratta di una QStringList , ovvero un array di stringhe QString . Possiamo scorrere questo array con un semplice ciclo for che parte dall'elemento 0 e arriva all'ultimo elemento dell'array stesso.

QListWidgetItem *dirItem=new QListWidgetItem();
dirItem->setText(dirList.at(i));
ui->listWidget->insertItem(i, dirItem);

Siamo all'intero del ciclo for che scorre tutti gli elementi della array di stringhe: la stringa attuale si trova chiamando la funzione at della dirList . Dobbiamo trasformarla in un elemento ( QListWidgetItem ) della lista dei file, ovvero l'oggetto QListWidget .

QString filename = ui->lineEdit->text() + "/" + dirList.at(i);
playlist->addMedia(QUrl::fromLocalFile(filename));
}

Il percorso del file viene ottenuto unendo il percorso della cartella e il nome del file attuale, e può essere aggiunto alla playlist con la funzione addMedia . Il ciclo for si può quindi concludere.

playlist->setPlaybackMode(QMediaPlaylist::Loop);
connect(playlist, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int)));
player->setPlaylist(playlist);
}
}

Ora la playlist contiene tutti i file musicali della cartella indicata dall'utente. Possiamo quindi importarla come playlist del nostro player. Prima però dobbiamo indicare che vogliamo che questa playlist vada in loop continuo, ovvero che venga ripetuta più volte ricominciando dall'inizio quando termina la riproduzione dell'ultimo file. E colleghiamo anche un segnale della playlist a una funzione: è il segnale lanciato ogni volta che comincia la riproduzione di un nuovo file musicale.

void MainWindow::on_play_clicked()
{
if (ui->listWidget->count()>0) {

Per avviare la riproduzione, l'utente può cliccare il pulsante play . Quando ciò avviene, un'apposita funzione viene lanciata automaticamente: questa deve prima di tutto verificare che ci sia almeno un file all'interno della lista dei file, altrimenti è ovvio che non si possa riprodurre nulla.

int inizio = 0;
if (ui->listWidget->currentRow()>0) inizio = ui→listWidget→currentRow();

È importante capire da quale elemento dobbiamo cominciare la riproduzione della playlist: in teoria si comincia sempre dall'elemento 0 , ma se l'utente ha selezionato un elemento ( currentRow ) del listWidget utilizziamo questo file per cominciare la riproduzione.

player->setVolume(ui->volume->value());
player->playlist()->setCurrentIndex(inizio);
player->play();
}
}

Ora possiamo concludere la funzione avviando davvero la riproduzione: impostiamo il volume, impostiamo il file di partenza e lanciamo la riproduzione della playlist con la funzione play del player .

A questo punto possiamo tornare su QtCreator , installato nella prima pagina di questo articolo. Questo ambiente di sviluppo, infatti, ci permette di compilare anche per Android. Vediamo dunque produrre un pacchetto APK per la piattaforma mobile Google utilizzando QtCreator.

Prima di tutto dobbiamo procurarci sia l'SDK che l'NDK Android. L' SDK si trova nella pagina di Android Studio, tra i download alternativi (infatti non ci serve l'intero Android Studio).

Poi possiamo aprire QtCreator , anche senza aver caricato alcun progetto, e scegliere il menu Tools/Options . Qui, nella scheda Android , indichiamo la posizione in cui abbiamo scaricato l'NDK e l'SDK ed anche gli eseguibili di OpenJDK e di ANT.

Nella scheda Build&Run della stessa finestra, controlliamo la sezione Qt Versions . Dovremmo trovare le Qt for Android armv7 : se questa versione non c'è, vuol dire che dobbiamo installare il pacchetto relativo (su Ubuntu è qt5-qmake-arm-linux-gnueabihf ).

Quando apriamo il nostro progetto con QtCreator, possiamo configurare i kit con cui vogliamo compilare il programma. Il nostro music player, ad esempio, può essere compilato per tanti sistemi diversi, tra i quali Android armeabi-v7 .

Ora, basta cliccare sul pulsante a forma di triangolo verde per avviare la compilazione e l'esecuzione dell'applicazione. Il pacchetto APK verrà salvato nella cartella android-build/build/outputs/apk , e sarà avviato con l'emulatore Android.

Possiamo anche provare l'APK direttamente su un nostro smartphone o tablet (con architettura armv7): basta copiare su di esso il file e avviarlo. Ci verrò chiesto se intendiamo davvero installare l'applicazione, e poi potremo provarla come una normale app.

Android Studio, l'IDE ufficiale
Se preferiamo programmare con Java, ci servirà Android Studio completo. Prima di tutto dobbiamo procurarci l'ultima versione disponibile di Android Studio . Il file che scarichiamo è uno ZIP che contiene soltanto l'IDE.

Siccome nella versione per GNU/Linux di Android Studio non è incorporato l'SDK, dobbiamo scaricarlo a parte dalla stessa pagina. Se vogliamo utilizzare linguaggi diversi da Java, dovremo scaricare anche l'Android NDK.

Affinché Android Studio possa funzionare, ci serve anche il JDK. Possiamo installarlo tramite il pacchetto openjdk-7-jdk , presente su tutte le principali distro GNU/Linux nel gestore dei pacchetti.

Dobbiamo infine estrarre tutti i file degli archivi scaricati in una unica cartella facile da raggiungere, per esempio la cartella /opt . Il file che permette di lanciare Android Studio è android-studio/bin/studio.sh .

Le nostre app vanno sul Play Store
Se intendiamo pubblicare le nostre app Android sul Play Store dovremo registrarci al sito Google Play Developer Console . Bisogna sottoscrivere il contratto e pagare una tariffa di iscrizione di 25 dollari. Quando l'iscrizione viene verificata, possiamo specificare un nostro riferimento bancario per ottenere gli eventuali ricavi derivanti dalla vendita dell'app (se intendiamo venderla e non rilasciarla gratuitamente). È a questo punto che è possibile caricare sul sito la nostra app e configurare la sua pagina, per pubblicarla nello store.

Luca Tringali

Link copiato negli appunti

Ti potrebbe interessare

Pubblicato il
6 ott 2016
Link copiato negli appunti