Socket file transfer con le librerie boost


Introduzione

Questo aritcolo nasce da un problema che probabilemte sarà capitato anche a voi: trasferire un file attraverso internet. Quante volte vi sarà successo di chiaccherare con qualcuno su MSN, e di voler inviare o ricevere un file. Ora, MSN consente di trasferire file, ma ha un piccolo problema: è lento. Non so perchè, ma è lento. Quindi per file grandi vi sarete trovati di fronte a una scritta del tipo: "55 minuti rimanenti", o anche peggio. Cosa fare allora? La seconda cosa che in genere si tenta è di inviare il file via email. Ahimè, molte caselle di posta elettronica hanno ancora il limite di 10MB per gli allegati... In realtà ci sono molti modi per superare il problema, compreso trasferire il file usando skype (che è più veloce di MSN a trasferire i file), oppure caricare il file in uno spazio web e inviare il link all'altra persona.

Comunque, è proprio una volta che mi sono trovato ad affrontare questo problema che ho provato la soluzione più nerd che potessi pensare: scrivere un programmino server e uno client fatti apposta per trasferire file, inviare il client tramite MSN all'altra persona, e aspettare che il server sul mio computer ricevesse il file!

Ora, dato che dovevo risolvere un problema nel minor tempo possibile, il programma è davvero minimalista, non ha nemmeno una interfaccia grafica. Infatti, non lo pubblico su questo sito come soluzione al problema del trasferimento file, ma con scopo didattico, in quanto guardando il codice sorgente si può vedere come fare un server multithreading e un client in meno di 100 righe di codice ognuno.

I socket

Essendo un programmatore C++, ci sono poche sorprese su che linguaggio ho usato per scrivere il programma. Per effettuare una connessione internet occorre usare i socket, ma la libreria standard del C++ non offre i socket. Questo non è un grande problema, in quanto esistono almeno due soluzioni:
  1. Usare i socket del C. Ho immediatamente scartato questa soluzione per due motivi: primo perchè i socket in C non sono portabili, in Windows ad esempio esiste una libreria simile chiamata WinSocket, che è però abbastanza diversa da dover richiedere di scrivere due volte lo stesso codice, una per Linux, MacOS, e l'altra per Windows. Il secondo problema è lo stile troppo di basso livello di queste librerie, che portano a scrivere molto codice e a fare facilmete errori.
  2. Usare i socket delle Boost. Le boost, per chi non lo sapesse, sono delle collezioni di librerie multipiattaforma per C++ molto utili per svariati compiti che prima o poi i programmatori di trovano ad affrontare. Il vantaggio di questi socket è quello di essere delle classi che consentono una programmazione ad alto livello, e sono pure portabili sia su Windows che Linux e MacOS senza dover riscrivere pezzi di codice.
La libreria all'interno di Boost che si occupa del networking (e non solo) si chiama Asio. All'interno di questa libreria ho trovato i "socket iostream" che sono classi modellate come degli stream del C++ che sono le più semplici da usare. Creare un socket "client" che si connette a un server è davvero facile, basta una riga di codice:

ip::tcp::iostream socket("www.webalice.it","80");

Con questa riga si dichiara un istanza della classe ip::tcp::iostream di nome socket, che si connette al sito indicato dal primo parametro, e alla porta indicata dal secondo. Come primo parametro si può usare anche un indirizzo IP, ad esempio "127.0.0.1". Il costruttore della classe esegue da solo tutte le operazioni necessarie alla connessione, e si può quindi iniziare a trasmettere e ricevere subito. Per un elenco dei metodi di questa classe si può consultare sia la documentazione delle boost, sia quella della classe iostream standard (ad esmpio qui).
Per fare un server occorre qualche riga di codice in più, consiglio di guardare i sorgenti allegati alla fine dell'articolo.

Il protocollo

Dato che si tratta di una implementazione minimale, non ho implementato nessun protocollo standard come ad esempio FTP, ma ho scelto la soluzione più semplice che mi veniva in mente. Innanzitutto una volta aperta la connessione vengono trasmessi 4 byte sempre uguali, detti "magic". Il server li usa per controllare che dall'altra parte del socket c'è davvero il client che si aspetta, e se questi byte non sono corretti chiude la connessione. Poi viene trasmesso un byte che rappresenta la lunghezza del nome del file, poi viene trasmesso il nome del file e infine il contenuto binario del file. Non viene fatta nessuna codifica quale ad esempio base64 per ottenere la massima velocità.

Il codice

Il codice è disponibile qui:

Socket.zip

Il file contiene una cartella bin con i file precompilati per Linux e Windows, e due cartelle Client e Server con i sorgenti. Oltre ai sorgenti sono presenti i file di progetto per Netbeans, l'IDE che sto usando attualmente, che dovrebbero funzionare senza problemi su un computer Linux, e un makefile per Windows.
Se volete usare un IDE diverso, non dovete dimenticarvi di linkare con le librerie boost. Invece di spiegare come fare per ogni IDE esistente, riporto qui i vari comandi da dare in una shell per compilare i due programmi. Da questi comandi si dovrebbe capire come configurare ogni IDE (il primo comando è per compilare, il secondo per linkare)
g++ -c -O2 main.cpp
g++ -s -o client main.o -lpthread /usr/lib/libboost_system.a /usr/lib/libboost_thread.a
g++ -c -O2 main.cpp -D_WIN32_WINNT=0x0501
g++ -s -o Client.exe main.o -lwsock32 -lws2_32 -lboost_system -lboost_thread

Ottenere le librerie boost

Per compilare correttamente i programmi bisogna linkare con le librerie boost, ma per poter linkare con tali librerie occorre prima averle installate sul proprio computer, e qui c'è una brutta sorpresa: sul sito ufficiale http://www.boost.org/ le librerie sono disponibili solo come codice sorgente. Anche se compilarle è facile, è un'operazione che richiede molto tempo, in genere più di un ora (le librerie sono davvero complesse internamente).
Fortunatamente sia per Linux che per windows c'è un alternativa: