Browse Source

inf224 project

master
Adrien MAES 1 year ago
commit
4dd7b13e53
  1. 70
      README
  2. BIN
      cours inf224.pdf
  3. 36
      cpp/Base.cpp
  4. 36
      cpp/Base.h
  5. 44
      cpp/Film.cpp
  6. 49
      cpp/Film.h
  7. 79
      cpp/Gestion.cpp
  8. 55
      cpp/Gestion.h
  9. 34
      cpp/Groupe.cpp
  10. 33
      cpp/Groupe.h
  11. 100
      cpp/Makefile
  12. 113
      cpp/Makefile-cliserv
  13. 35
      cpp/Photo.cpp
  14. 35
      cpp/Photo.h
  15. 27
      cpp/Video.cpp
  16. 32
      cpp/Video.h
  17. 469
      cpp/ccsocket.cpp
  18. 353
      cpp/ccsocket.h
  19. 68
      cpp/client.cpp
  20. 133
      cpp/main.cpp
  21. 46
      cpp/server.cpp
  22. 122
      cpp/tcpserver.cpp
  23. 53
      cpp/tcpserver.h
  24. 59
      swing/Makefile
  25. 212
      swing/Telecommande.java

70
README

@ -0,0 +1,70 @@
***** PARTIE C++ *****
Etape 1 : OK
Etape 2 : OK
Etape 3 : OK
Etape 4 :
--- Comment appelle-t'on ce type de méthode et comment faut-il les déclarer ?
--> Ce type de méthode est une méthode abstraite, il faut la déclarer en
--> rajoutant const = 0 dans le header
Etape 5 :
--- Quelle est la propriété caractéristique de l'orienté objet qui permet de faire cela ?
--> C'est le polymorphisme
--- Qu'est-il spécifiquement nécessaire de faire dans le cas du C++ ?
--> il faut déclarer les méthodes "virtual"
--- Quel est le type des éléments du tableau : est-ce que ce tableau contient
--- les objets ou des pointeurs vers ces objets ?
---> Ce tableau contient des pointeurs vers ces objets
Etape 6 :
--- Que faut-il faire pour que l'objet Film ait plein contrôle sur ses données
--- et que son tableau de durées des chapitres ne puisse pas être modifié (ou pire, détruit) à son insu ?
--> Il faut recopier le tableau passé en paramètre
--- Quelle est la solution très simple que propose C/C++ pour éviter ce problème ?
--> Il suffit de déclarer la méthode const *
Etape 7 :
--- Parmi les classes précédemment écrites quelles sont celles qu'il faut modifier
--- afin qu'il n'y ait pas de fuite mémoire quand on détruit leurs instances ?
--> Il faut modifier la classe Film car elle possède un attribut dont le type
--> n'est pas un type de base
--- Pourquoi et que faudrait-il faire ?
--> La copie d'objets peut poser problème car ce serait une copie superficielle,
--> Les attributs qui sont des pointeurs pointeraient vers le meme objet.
--> Il faut alors redéfinir l'opérateur = et le constructeur de copie
Etape 8 :
--- On rappelle aussi que la liste d'objets doit en fait être une liste de pointeurs d'objets. Pourquoi ?
--> Si on veut qu'un objet puisse apartenir à plusieurs groupes, il faut que chaque
--> groupe contienne des pointeurs vers les objets, pour ne pas dupliquer les objets
--> En java, il n'y a pas de pointeurs explicite, tout est fait implicitement
Etape 9 : OK
Etape 10 :
--- Comment peut-on l'interdire, afin que seule la classe servant à manipuler les objets puisse en créer de nouveaux ?
--> On peut rendre le constructeur private et mettre en amie la classe Gestion
Etape 11 : OK
Etape 12 : non traitée
Etape 13 : non traitée
***** PARTIE JAVASWING *****
Utilisation des AbstractAction

BIN
cours inf224.pdf

Binary file not shown.

36
cpp/Base.cpp

@ -0,0 +1,36 @@
#include "Base.h"
#include <iostream>
#include <string>
using namespace std;
Base::Base() {
}
Base::Base(string name, string pathname) : name(name), pathname(pathname) {
}
Base::~Base() {
}
void Base::setName(string _name) {
name = _name;
}
void Base::setPathName(string _pathname) {
pathname = _pathname;
}
string Base::getName() const {
return name;
}
string Base::getPathName() const {
return pathname;
}
void Base::affichage(ostream& flux) const {
flux << "Le fichier : " << name << " a pour chemin : " << pathname << endl;
}

36
cpp/Base.h

@ -0,0 +1,36 @@
#ifndef BASE_H
#define BASE_H
#include <string>
#include <iostream>
/**
* @brief Classe de base des fichiers multimedias
*
* les méthodes @e setX et @e getX sont les getteurs et les setteurs
* des attributs @e name et @e pathname
*
* la méthode @e affichage permet d'afficher sur le flux donné
* les attributs
*
* la méthode play est abstraite et sera implémentée par les sous-classes
* en effet elle dépend du type de fichier
*/
class Base {
protected:
std::string name{};
std::string pathname{};
public:
Base();
Base(std::string, std::string);
virtual ~Base();
void setName(std::string);
void setPathName(std::string);
std::string getName() const;
std::string getPathName() const;
virtual void affichage(std::ostream&) const;
virtual void play() const = 0;
};
#endif

44
cpp/Film.cpp

@ -0,0 +1,44 @@
#include "Video.h"
#include "Film.h"
#include <iostream>
#include <string>
using namespace std;
Film::Film(string name, string pathname, unsigned int duree) : Video(name, pathname, duree) {
}
Film::~Film() {
cout << "film détruit" << endl;
delete[] chapitres;
}
Film::Film(Film const& filmACopier) : Video(filmACopier.name, filmACopier.pathname, filmACopier.duree), nbChapitres(filmACopier.nbChapitres) {
chapitres = new unsigned int[filmACopier.nbChapitres];
for (unsigned int i(0); i<filmACopier.nbChapitres; i++) {
chapitres[i] = filmACopier.chapitres[i];
}
}
void Film::setChapitres(unsigned int const * _chapitres, unsigned int size) {
delete[] chapitres;
chapitres = new unsigned int[size];
nbChapitres = size;
for (unsigned int i(0); i<size; i++) {
chapitres[i] = _chapitres[i];
}
}
unsigned int const * Film::getChapitres() const {
return chapitres;
}
unsigned int Film::getNbChapitres() const {
return nbChapitres;
}
void Film::afficheChapitres(std::ostream& flux) const {
for (unsigned int i(0); i < nbChapitres; i++) {
flux << "le chapitre " << i +1 << " a pour durée : " << chapitres[i] << endl;
}
}

49
cpp/Film.h

@ -0,0 +1,49 @@
#ifndef FILM_H
#define FILM_H
#include <string>
#include <iostream>
#include "Video.h"
/**
* @brief Classe des Films, héritant de la classe @e Video
*
* Par rapport aux vidéos, les films ont des chapitres
* on trouve donc pour cela deux attributs supplémentaires
*
* les getteurs et les setteurs correspondent à ces nouveaux attributs
*
* il a é également nécessaire de réimplémenter l'opérateur @e =
*/
class Film : public Video {
private:
unsigned int nbChapitres{};
unsigned int * chapitres{};
public:
Film(std::string, std::string, unsigned int = 0);
Film(Film const&);
void setChapitres(unsigned int const *, unsigned int);
unsigned int const * getChapitres() const;
unsigned int getNbChapitres() const;
void afficheChapitres(std::ostream&) const;
virtual ~Film();
Film& operator=(Film const& filmACopier) {
if (this != &filmACopier) {
name = filmACopier.name;
pathname = filmACopier.pathname;
duree = filmACopier.duree;
nbChapitres = filmACopier.nbChapitres;
delete [] chapitres;
chapitres = new unsigned int[filmACopier.nbChapitres];
for (unsigned int i(0); i<filmACopier.nbChapitres; i++) {
chapitres[i] = filmACopier.chapitres[i];
}
}
return *this;
}
};
#endif

79
cpp/Gestion.cpp

@ -0,0 +1,79 @@
#include "Gestion.h"
#include <iostream>
#include <string>
#include <map>
#include "Base.h"
#include "Groupe.h"
#include "Photo.h"
#include "Video.h"
#include "Film.h"
using namespace std;
PhotoPtr Gestion::creerPhoto(string name, string pathname, unsigned int latitude, unsigned int longitude) {
PhotoPtr obj(new Photo(name, pathname, latitude, longitude));
objets[name] = obj;
return obj;
}
VideoPtr Gestion::creerVideo(string name, string pathname, unsigned int duree) {
VideoPtr obj(new Video(name, pathname, duree));
objets[name] = obj;
return obj;
}
FilmPtr Gestion::creerFilm(string name, string pathname, unsigned int duree) {
FilmPtr obj(new Film(name, pathname, duree));
objets[name] = obj;
return obj;
}
GroupePtr Gestion::creerGroupe(string name) {
GroupePtr obj(new Groupe(name));
groupes[name] = obj;
return obj;
}
void Gestion::afficher(string name, ostream& flux) {
int cpt = 0;
map<string,BasePtr>::iterator it;
it = objets.find(name);
if (it != objets.end()) it->second->affichage(flux);
else cpt += 1;
map<string,GroupePtr>::iterator it2;
it2 = groupes.find(name);
if (it2 != groupes.end()) it2->second->affichage(flux);
else cpt += 1;
if (cpt == 2) flux << "aucun fichier ou groupe pour ce nom : " << name << endl;
}
void Gestion::jouer(string name) {
map<string,BasePtr>::iterator it;
it = objets.find(name);
if (it != objets.end()) it->second->play();
}
void Gestion::supprimer(string name) {
map<string,GroupePtr>::iterator it2;
it2 = groupes.find(name);
if (it2 != groupes.end()) groupes.erase(it2);
map<string,BasePtr>::iterator it1;
it1 = objets.find(name);
if (it1 != objets.end()) objets.erase(it1);
for (auto & itg : groupes) {
itg.second->supprimer(name);
}
}
void Gestion::lister(ostream& flux) {
flux << "Les groupes disponibles et leurs éléments sont les suivants : " << endl;
for (auto & it : groupes) {
it.second->affichage(flux);
}
}

55
cpp/Gestion.h

@ -0,0 +1,55 @@
#ifndef GESTION_H
#define GESTION_H
#include <string>
#include <iostream>
#include <map>
#include "Base.h"
#include "Groupe.h"
#include "Photo.h"
#include "Video.h"
#include "Film.h"
/**
* @brief Gestion est la classe dont une instance est une base de donnée
* d'une box média
*
* Cette classe comprend deux attributs permettant de manipuler les données
* Ces deux attributs sont des @e map, un pour les fichier médias, et
* un pour les groupes (cf la documentation de Groupe)
*
* Cette classe possède 4 méthodes de création d'objet :
* - trois permettant de créer les trois types de média
* - une permettant de créer un groupe
*
* Par création on entend : création de l'objet proprement dit ET ajout de cet
* objet au @e map correspondant
*
* Enfin, quatres méthodes permettent au client d'intéragir avec ces données :
* - afficher : trouve et affiche un media ou groupe selon sa méthode d'affichage
* - jouer : trouve et joue un media selon sa méthode de lecture
* - supprimer : trouve et supprime un media ou un groupe
* - lister : liste le contenu du @e map @b groupes
*/
typedef std::shared_ptr<Photo> PhotoPtr;
typedef std::shared_ptr<Video> VideoPtr;
typedef std::shared_ptr<Film> FilmPtr;
typedef std::shared_ptr<Groupe> GroupePtr;
class Gestion {
private:
std::map<std::string, BasePtr> objets;
std::map<std::string, GroupePtr> groupes;
public:
PhotoPtr creerPhoto(std::string, std::string, unsigned int = 0, unsigned int = 0);
VideoPtr creerVideo(std::string, std::string, unsigned int = 0);
FilmPtr creerFilm(std::string, std::string, unsigned int = 0);
GroupePtr creerGroupe(std::string);
void afficher(std::string,std::ostream&);
void jouer(std::string);
void supprimer(std::string);
void lister(std::ostream&);
};
#endif

34
cpp/Groupe.cpp

@ -0,0 +1,34 @@
#include "Groupe.h"
#include <iostream>
#include <string>
using namespace std;
Groupe::Groupe(string name) : name(name) {}
string Groupe::getName() const {
return name;
}
void Groupe::affichage(ostream& flux) const {
flux << "informations du groupe : " << name << endl << "{" << endl;
for (auto & it : *this) {
flux << "; - ";
it->affichage(flux);
}
flux << ";}" << endl;
}
void Groupe::supprimer(string name) {
list<BasePtr>::iterator it, it_tosuppr;
it_tosuppr = this->end();
it = this->begin();
while (it != this->end()) {
if ((*it)->getName() == name) {
cout << "erase" << endl;
it_tosuppr = it;
}
++it;
}
if (it_tosuppr != this->end()) this->erase(it_tosuppr);
}

33
cpp/Groupe.h

@ -0,0 +1,33 @@
#ifndef GROUPE_H
#define GROUPE_H
#include <string>
#include <iostream>
#include <list>
#include <memory>
#include "Base.h"
/**
* @brief Classe permettant de regrouper des objets
*
* Les objets multimedias peuvent être regroupés par catégories, par groupes
* Cette classe est une sous-classe de la classe template @e std::list
*
* On y trouve une méthode pour récupérer le nom (getteur), une méthode
* permettant d'afficher chacun des objets que contient un groupe, et une
* méthode permettant de supprimer un élément
*/
typedef std::shared_ptr<Base> BasePtr;
class Groupe : public std::list<BasePtr> {
private:
std::string name{};
public:
Groupe(std::string name);
std::string getName() const;
void affichage(std::ostream&) const;
void supprimer(std::string);
};
#endif

100
cpp/Makefile

@ -0,0 +1,100 @@
##########################################
#
# Exemple de Makefile
# Eric Lecolinet - Reda Dehak - Telecom ParisTech 2015
# INF224 - TP C++ - http://www.telecom-paristech.fr/~elc/inf224
#
##########################################
#
# Nom du programme
#
PROG = myprog
#
# Fichiers sources (NE PAS METTRE les .h ni les .o seulement les .cpp)
#
SOURCES = Base.cpp main.cpp Video.cpp Photo.cpp Film.cpp Groupe.cpp Gestion.cpp tcpserver.cpp ccsocket.cpp
#
# Fichiers objets (ne pas modifier sauf si l'extension n'est pas .cpp)
#
OBJETS = ${SOURCES:%.cpp=%.o}
#
# Compilateur C++
#
CXX = c++
#
# Options du compilateur C++
# -g pour debugger, -O optimise, -Wall affiche les erreurs, -I pour les headers
# -std=c++11 pour C++11
# Exemple: CXXFLAGS= -std=c++11 -Wall -O -I/usr/local/qt/include
#
CXXFLAGS = -std=c++11 -Wall -g
#
# Options de l'editeur de liens
#
LDFLAGS =
#
# Librairies a utiliser
# Exemple: LDLIBS = -L/usr/local/qt/lib -lqt
#
LDLIBS = -lpthread
##########################################
#
# Regles de construction/destruction des .o et de l'executable
# depend-${PROG} sera un fichier contenant les dependances
#
all: ${PROG}
run: ${PROG}
./${PROG}
${PROG}: depend-${PROG} ${OBJETS}
${CXX} -o $@ ${CXXFLAGS} ${LDFLAGS} ${OBJETS} ${LDLIBS}
clean:
-@$(RM) *.o depend-${PROG} core 1>/dev/null 2>&1
clean-all: clean
-@$(RM) ${PROG} 1>/dev/null 2>&1
tar:
tar cvf ${PROG}.tar.gz ${SOURCES}
# Gestion des dependances : creation automatique des dependances en utilisant
# l'option -MM de g++ (attention tous les compilateurs n'ont pas cette option)
#
depend-${PROG}:
${CXX} ${CXXFLAGS} -MM ${SOURCES} > depend-${PROG}
###########################################
#
# Regles implicites
#
.SUFFIXES: .cpp .cxx .c
.cpp.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
.cxx.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
.c.o:
$(CC) -c (CFLAGS) $(INCPATH) -o $@ $<
#############################################
#
# Inclusion du fichier des dependances
#
-include depend-${PROG}

113
cpp/Makefile-cliserv

@ -0,0 +1,113 @@
##########################################
#
# Makefile pour client/serveur TCP
# Eric Lecolinet - Reda Dehak - Telecom ParisTech 2015
# INF224 - TP C++ - http://www.telecom-paristech.fr/~elc/inf224
#
##########################################
#
# Nom du programme
#
CLIENT=client
SERVER=server
CLISERV=cliserv
#
# Fichiers sources (NE PAS METTRE les .h ni les .o mais seulement les .cpp)
#
CLIENT_SOURCES=client.cpp ccsocket.cpp
SERVER_SOURCES=server.cpp tcpserver.cpp ccsocket.cpp
CLISERV_SOURCES=client.cpp server.cpp tcpserver.cpp ccsocket.cpp Makefile-cliserv
#
# Fichiers objets (ne pas modifier, sauf si l'extension n'est pas .cpp)
#
CLIENT_OBJETS=${CLIENT_SOURCES:%.cpp=%.o}
SERVER_OBJETS=${SERVER_SOURCES:%.cpp=%.o}
#
# Compilateur C++ (suivant les systemes CXX ou CCC)
#
CXX= c++
#
# Options du compilateur C++
# -g pour debugger, -O optimise, -Wall affiche les erreurs, -I pour les headers
# -std=c++11 pour C++11
# Example: CXXFLAGS= -std=c++11 -Wall -O -I/usr/local/qt/include
#
CXXFLAGS= -std=c++11 -Wall -g
#
# Options de l'editeur de liens
#
LDFLAGS=
#
# Librairies a utiliser
# Example: LDLIBS = -L/usr/local/qt/lib -lqt
#
LDLIBS= -lpthread
##########################################
#
# Regles de construction/destruction des .o et de l'executable
# depend-${PROG} sera un fichier contenant les dependances
#
all: ${CLIENT} ${SERVER}
run-${SERVER}: ${SERVER}
./${SERVER}
run-${CLIENT}: ${CLIENT}
./${CLIENT}
${CLIENT}: depend-${CLIENT} ${CLIENT_OBJETS}
${CXX} -o $@ ${CXXFLAGS} ${LDFLAGS} ${CLIENT_OBJETS} ${LDLIBS}
${SERVER}: depend-${SERVER} ${SERVER_OBJETS}
${CXX} -o $@ ${CXXFLAGS} ${LDFLAGS} ${SERVER_OBJETS} ${LDLIBS}
clean:
-@$(RM) *.o depend-${CLIENT} depend-${SERVER} core 1>/dev/null 2>&1
clean-all: clean
-@$(RM) -${CLIENT} -${SERVER} 1>/dev/null 2>&1
tar:
tar cvf ${CLISERV}.tar.gz ${CLISERV_SOURCES}
# Gestion des dependances : creation automatique des dependances en utilisant
# l'option -MM de g++ (attention tous les compilateurs n'ont pas cette option)
#
depend-${CLIENT}:
${CXX} ${CXXFLAGS} -MM ${CLIENT_SOURCES} > depend-${CLIENT}
depend-${SERVER}:
${CXX} ${CXXFLAGS} -MM ${SERVER_SOURCES} > depend-${SERVER}
###########################################
#
# Regles implicites
#
.SUFFIXES: .cpp .cxx .c
.cpp.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
.cxx.o:
$(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<
.c.o:
$(CC) -c (CFLAGS) $(INCPATH) -o $@ $<
#############################################
#
# Inclusion du fichier des dependances
#
-include depend-${CLIENT}
-include depend-${SERVER}

35
cpp/Photo.cpp

@ -0,0 +1,35 @@
#include "Photo.h"
#include "Base.h"
#include <iostream>
#include <string>
using namespace std;
Photo::Photo(std::string name, std::string pathname, unsigned int latitude, unsigned int longitude) : Base(name, pathname), latitude(latitude), longitude(longitude) {}
Photo::~Photo() {}
void Photo::setLatitude(unsigned int _latitude) {
latitude = _latitude;
}
void Photo::setLongitude(unsigned int _longitude) {
longitude = _longitude;
}
unsigned int Photo::getLatitude() const {
return latitude;
}
unsigned int Photo::getLongitude() const {
return longitude;
}
void Photo::affichage(ostream& flux) const {
Base::affichage(flux);
flux << "La photo a pour latitude : " << latitude << " et pour longitude : " << longitude << endl;
}
void Photo::play() const {
string command = "display " + pathname + " &";
system(command.c_str());
}

35
cpp/Photo.h

@ -0,0 +1,35 @@
#ifndef PHOTO_H
#define PHOTO_H
#include <string>
#include <iostream>
#include "Base.h"
/**
* @brief Classe des medias de type photo, héritant de Base
*
* Une photo possède deux attributs particuliers : une latitude et une longitude
*
* La classe comporte les getteurs et setteurs associés
*
* La fonction affichage affiche sur le flux les attributs
*
* La fonction play affiche la photo avec la commande @e display
*/
class Photo : public Base {
private:
unsigned int latitude{};
unsigned int longitude{};
public:
Photo(std::string, std::string, unsigned int = 0, unsigned int = 0);
virtual ~Photo();
void setLatitude(unsigned int);
void setLongitude(unsigned int);
unsigned int getLatitude() const;
unsigned int getLongitude() const;
virtual void affichage(std::ostream&) const;
virtual void play() const;
};
#endif

27
cpp/Video.cpp

@ -0,0 +1,27 @@
#include "Video.h"
#include "Base.h"
#include <iostream>
#include <string>
using namespace std;
Video::Video(string name, string pathname, unsigned int duree) : Base(name, pathname), duree(duree) {}
Video::~Video() {}
void Video::setDuree(unsigned int _duree) {
duree = _duree;
}
unsigned int Video::getDuree() const {
return duree;
}
void Video::affichage(ostream& flux) const {
Base::affichage(flux);
flux << "La vidéo a pour durée : " << duree << endl;
}
void Video::play() const {
string command = "mpv " + pathname + " &";
system(command.c_str());
}

32
cpp/Video.h

@ -0,0 +1,32 @@
#ifndef VIDEO_H
#define VIDEO_H
#include <string>
#include <iostream>
#include "Base.h"
/**
* @brief Classe des medias de type vidéo, héritant de Base
*
* Une photo possède un attribut particulier : une durée
*
* La classe comporte le getteur et le setteur associés
*
* La fonction affichage affiche sur le flux l'attribut
*
* La fonction play affiche la vidéo avec la commande @e mpv
*/
class Video : public Base {
protected:
unsigned int duree{};
public:
Video(std::string, std::string, unsigned int = 0);
virtual ~Video();
void setDuree(unsigned int);
unsigned int getDuree() const;
virtual void affichage(std::ostream&) const;
virtual void play() const;
};
#endif

469
cpp/ccsocket.cpp

@ -0,0 +1,469 @@
//
// ccsocket: C++ Classes for TCP/IP and UDP Datagram INET Sockets.
// (c) Eric Lecolinet 2016/2020 - http://www.telecom-paristech.fr/~elc
//
#include <iostream>
#include <cstring>
#include <cstdlib>
#if defined(_WIN32) || defined(_WIN64)
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h> // fcntl.h won't compile without unistd.h !
#include <netinet/tcp.h>
#include <netdb.h>
#endif
#include <fcntl.h>
#include <csignal>
#include "ccsocket.h"
using namespace std;
void Socket::startup() {
#if defined(_WIN32) || defined(_WIN64)
static bool started = false;
static WSADATA WSAData;
if (!started) {
started = true;
WSAStartup(MAKEWORD(2, 0), &WSAData);
}
#endif
}
void Socket::cleanup() {
#if defined(_WIN32) || defined(_WIN64)
static bool started = false;
if (!started) {
started = true;
WSACleanup();
}
#endif
}
Socket::Socket(int type) {
startup();
// family is AF_INET (AF_UNIX or AF_INET6 not supported)
// type can be SOCK_STREAM (TCP/IP) or SOCK_DGRAM (datagram connection)
// protocol is 0 (ie chosen automatically)
sockfd_ = ::socket(AF_INET, type, 0);
// ignore SIGPIPES when possible
#if defined(SO_NOSIGPIPE)
int set = 1;
setsockopt(sockfd_, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));
#endif
}
Socket::Socket(int, SOCKET sockfd) : sockfd_(sockfd) {
startup();
}
Socket::~Socket() {
close();
}
// for INET4 sockets
int Socket::setLocalAddress(SOCKADDR_IN& addr, int port) {
addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
return 0;
}
// for INET4 sockets
int Socket::setAddress(SOCKADDR_IN& addr, const string& host, int port) {
#if defined(_WIN32) || defined(_WIN64)
addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
inet_pton(AF_INET, host.data(), &addr.sin_addr.s_addr);
#else
addr = {};
struct hostent* hent = NULL;
// gethostbyname() is obsolete!
if (host.empty() || !(hent = ::gethostbyname(host.c_str()))) return -1; // host not found
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
// NB: data might be misaligned but correct result because memcpy() is used
::memcpy(&addr.sin_addr, hent->h_addr_list[0], hent->h_length);
#endif
return 0;
}
int Socket::bind(int port) {
if (sockfd_ == INVALID_SOCKET) return InvalidSocket;
// for INET4 sockets
SOCKADDR_IN addr;
setLocalAddress(addr, port);
// assigns the address specified by addr to sockfd (returns -1 on error, 0 otherwise)
return ::bind(sockfd_, (const SOCKADDR*)&addr, sizeof(addr));
}
int Socket::bind(const string& host, int port) {
if (sockfd_ == INVALID_SOCKET) return InvalidSocket;
// for INET4 sockets
SOCKADDR_IN addr;
if (setAddress(addr, host, port) < 0) return UnknownHost;
// assigns the address specified by addr to sockfd (returns -1 on error, 0 otherwise)
return ::bind(sockfd_, (const SOCKADDR*)&addr, sizeof(addr));
}
int Socket::connect(const string& host, int port) {
if (sockfd_ == INVALID_SOCKET) return InvalidSocket;
// for INET4 sockets
SOCKADDR_IN addr;
if (setAddress(addr, host, port) < 0) return UnknownHost;
// connects sockfd to the address specified by addr (returns -1 on error, 0 otherwise)
return ::connect(sockfd_, (SOCKADDR*)&addr, sizeof(addr));
}
int Socket::close() {
int stat = 0;
if (sockfd_ != INVALID_SOCKET) {
stat = ::shutdown(sockfd_, 2); // SHUT_RDWR=2
#if defined(_WIN32) || defined(_WIN64)
stat += ::closesocket(sockfd_);
#else
stat += ::close(sockfd_);
#endif
}
sockfd_ = INVALID_SOCKET;
return stat;
}
void Socket::shutdownInput() {
::shutdown(sockfd_, 0);
}
void Socket::shutdownOutput() {
::shutdown(sockfd_, 1/*SD_SEND*/);
}
#if !defined(_WIN32) && !defined(_WIN64)
int Socket::setReceiveBufferSize(int size) {
return ::setsockopt(sockfd_, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int));
}
int Socket::setSendBufferSize(int size) {
return ::setsockopt(sockfd_, SOL_SOCKET, SO_SNDBUF, &size, sizeof(int));
}
int Socket::setReuseAddress(bool state) {
int set = state;
return ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(int));
}
int Socket::setSoLinger(bool on, int time) {
struct linger l;
l.l_onoff = on; // Linger active
l.l_linger = time; // How long to linger for
return ::setsockopt(sockfd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
}
int Socket::setSoTimeout(int timeout) {
struct timeval tv;
tv.tv_sec = timeout / 1000; // ms to seconds
tv.tv_usec = (timeout % 1000) * 1000; // ms to microseconds
return ::setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
int Socket::setTcpNoDelay(bool state) {
int set = state;
return ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &set, sizeof(int));
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ServerSocket::ServerSocket() {
Socket::startup();
sockfd_ = ::socket(AF_INET, SOCK_STREAM, 0);
}
ServerSocket::~ServerSocket() {
close();
}
Socket* ServerSocket::createSocket(SOCKET sockfd) {
return new Socket(SOCK_STREAM, sockfd);
}
int ServerSocket::bind(int port, int backlog) {
if (sockfd_ == INVALID_SOCKET) return Socket::InvalidSocket;
SOCKADDR_IN addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (::bind(sockfd_, (SOCKADDR*)&addr, sizeof(addr)) < 0) return -1;
#if defined(_WIN32) || defined(_WIN64)
#else
socklen_t taille = sizeof addr;
if (::getsockname(sockfd_, (SOCKADDR*)&addr, &taille) < 0) return -1;
#endif
// le serveur se met en attente sur le socket d'ecoute
// listen s'applique seulement aux sockets de type SOCK_STREAM ou SOCK_SEQPACKET.
if (::listen(sockfd_, backlog) < 0) return -1;
return 0;
}
int ServerSocket::close() {
int stat = 0;
if (sockfd_ != INVALID_SOCKET) {
// ::shutdown(sockfd, SHUT_RDWR);
#if defined(_WIN32) || defined(_WIN64)
::closesocket(sockfd_);
#else
::close(sockfd_);
#endif
}
sockfd_ = INVALID_SOCKET;
return stat;
}
Socket* ServerSocket::accept() {
SOCKET sock_com{};
#if defined(_WIN32) || defined(_WIN64)
SOCKADDR_IN addr_com;
int sizeofaddr_ = sizeof(addr_com);
sock_com = ::accept(sockfd_, (SOCKADDR*)&addr_com, &sizeofaddr_);
if (sock_com == INVALID_SOCKET) return nullptr;
#else
// cf. man -s 3n accept, EINTR et EWOULBLOCK ne sont pas geres!
if ((sock_com = ::accept(sockfd_, NULL, NULL)) < 0) return nullptr;
#endif
return createSocket(sock_com);
}
#if !defined(_WIN32) && !defined(_WIN64)
int ServerSocket::setReceiveBufferSize(int size) {
return ::setsockopt(sockfd_, SOL_SOCKET, SO_RCVBUF, &size, sizeof(int));
}
int ServerSocket::setReuseAddress(bool state) {
int set = state;
return ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(int));
}
int ServerSocket::setSoTimeout(int timeout) {
struct timeval tv;
tv.tv_sec = timeout / 1000; // ms to seconds
tv.tv_usec = (timeout % 1000) * 1000; // ms to microseconds
return ::setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
int ServerSocket::setTcpNoDelay(bool state) {
// turn off TCP coalescence for INET sockets (useful in some cases to avoid delays)
int set = state;
return ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, (char*)&set, sizeof(int));
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct InputBuffer {
InputBuffer(size_t size) :
buffer(new char[size]),
begin(buffer),
end(buffer + size), remaining(0) {
}
~InputBuffer() {
delete[] buffer;
}
char* buffer;
char* begin;
char* end;
SOCKSIZE remaining;
};
SocketBuffer::SocketBuffer(Socket* sock, size_t inSize, size_t outSize) :
insize_(inSize),
outsize_(outSize),
insep_(-1), // means '\r' or '\n' or "\r\n"
outsep_('\n'),
sock_(sock),
in_(nullptr) {
}
SocketBuffer::SocketBuffer(Socket& sock, size_t inSize, size_t outSize)
: SocketBuffer(&sock, inSize, outSize) {}
SocketBuffer::~SocketBuffer() {
delete in_;
}
void SocketBuffer::setReadSeparator(int separ) {
insep_ = separ;
}
void SocketBuffer::setWriteSeparator(int separ) {
outsep_ = separ;
}
SOCKSIZE SocketBuffer::readLine(string& str) {
str.clear();
if (!sock_) return Socket::InvalidSocket;
if (!in_) in_ = new InputBuffer(insize_);
while (true) {
if (retrieveLine(str, in_->remaining)) return str.length() + 1;
// - received > 0: data received
// - received = 0: nothing received (shutdown or empty message)
// - received < 0: an error occurred
SOCKSIZE received = sock_->receive(in_->begin, in_->end - in_->begin);
if (received <= 0) return received; // -1 (error) or 0 (shutdown)
if (retrieveLine(str, received)) return str.length() + 1;
}
}
bool SocketBuffer::retrieveLine(string& str, SOCKSIZE received) {
if (received <= 0 || in_->begin > in_->end) {
in_->begin = in_->buffer;
return false;
}
// search for separator
char* sep = nullptr;
int sepLen = 1;
if (insep_ < 0) { // means: '\r' or '\n' or "\r\n"
for (char* p = in_->begin; p < in_->begin + received; ++p) {
if (*p == '\n') {
sep = p;
sepLen = 1;
break;
}
else if (*p == '\r') {
sep = p;
if (p < in_->begin + received - 1 && *(p + 1) == '\n') {
sepLen = 2;
}
break;
}
}
}
else {
for (char* p = in_->begin; p < in_->begin + received; ++p)
if (*p == insep_) {
sep = p;
break;
}
}
if (sep) {
str.append(in_->begin, sep - in_->begin);
in_->remaining = received - (sep + sepLen - in_->begin);
in_->begin = sep + sepLen;
return true;
}
else {
str.append(in_->begin, received);
in_->begin = in_->buffer;
in_->remaining = 0;
return false;
}
}
SOCKSIZE SocketBuffer::writeLine(const string& str) {
if (!sock_) return Socket::InvalidSocket;
// a negature value of _outSep means that \r\n must be added
size_t len = str.length() + (outsep_ < 0 ? 2 : 1);
// if len is not too large, try to send everything in one block
if (len <= outsize_) {
char* buf = (char*)malloc(len);
::memcpy(buf, str.c_str(), str.length());
if (outsep_ >= 0) buf[len - 1] = char(outsep_);
else {
buf[len - 2] = '\r';
buf[len - 1] = '\n';
}
auto stat = write(buf, len);
delete buf;
return stat;
}
else {
SOCKSIZE sent = write(str.c_str(), str.length());
char buf[] = {char(outsep_), 0};
if (outsep_ >= 0) sent += sock_->send(buf, 1);
else sent += sock_->send("\r\n", 2);
return sent;
}
}
SOCKSIZE SocketBuffer::write(const char* s, size_t len) {
if (!sock_) return Socket::InvalidSocket;
const char* begin = s;
const char* end = s + len;
SOCKSIZE totalSent = 0;
while (begin < end) {
// - sent > 0: data sent
// - sent = 0: file was shutdown
// - sent < 0: an error occurred
SOCKSIZE sent = sock_->send(begin, end - begin);
if (sent <= 0) return sent; // -1 (error) or 0 (shutdown)
begin += sent;
totalSent += sent;
}
return totalSent;
}
SOCKSIZE SocketBuffer::read(char* s, size_t len) {
if (!sock_) return Socket::InvalidSocket;
char* begin = s;
char* end = s + len;
SOCKSIZE totalReceived = 0;
while (begin < end) {
SOCKSIZE received = sock_->receive(begin, end - begin);
if (received <= 0) return received; // -1 (error) or 0 (shutdown)
begin += received;
totalReceived += received;
}
return totalReceived;
}

353
cpp/ccsocket.h

@ -0,0 +1,353 @@
//
// ccsocket: C++ Classes for TCP/IP and UDP Datagram INET Sockets.
// (c) Eric Lecolinet 2016/2020 - https://www.telecom-paris.fr/~elc
//
// - Socket: TCP/IP or UDP/Datagram IPv4 socket
// - ServerSocket: TCP/IP Socket Server
// - SocketBuffer: preserves record boundaries when exchanging data
// between TCP/IP sockets.
//
#ifndef ccuty_ccsocket
#define ccuty_ccsocket 1
#include <string>
#if defined(_WIN32) || defined(_WIN64)
#include <winsock2.h>
#define SOCKSIZE int
#define SOCKDATA char
#else
#include <sys/types.h>
#include <sys/socket.h>
#define SOCKET int
#define SOCKADDR struct sockaddr
#define SOCKADDR_IN struct sockaddr_in
#define INVALID_SOCKET -1
#define SOCKSIZE ssize_t
#define SOCKDATA void
#endif
// ignore SIGPIPES when possible
#if defined(MSG_NOSIGNAL)
# define NO_SIGPIPE_(flags) (flags | MSG_NOSIGNAL)
#else
# define NO_SIGPIPE_(flags) (flags)
#endif
/** TCP/IP or UDP/Datagram IPv4 socket.
* AF_INET connections following the IPv4 Internet protocol are supported.
* @note
* - ServerSocket should be used on the server side.
* - SIGPIPE signals are ignored when using Linux, BSD or MACOSX.
* - TCP/IP sockets do not preserve record boundaries but SocketBuffer solves this problem.
*/
class Socket {
public:
/// Socket errors.
/// - Socket::Failed (-1): could not connect, could not bind, etc.
/// - Socket::InvalidSocket (-2): invalid socket or wrong socket type
/// - Socket::UnknownHost (-3): could not reach host
enum Errors { Failed = -1, InvalidSocket = -2, UnknownHost = -3 };
/// initialisation and cleanup of sockets on Widows.
/// @note startup is automaticcaly called when a Socket or a ServerSocket is created
/// @{
static void startup();
static void cleanup();
/// @}
/// Creates a new Socket.
/// Creates a AF_INET socket using the IPv4 Internet protocol. Type can be:
/// - SOCK_STREAM (the default) for TCP/IP connected stream sockets
/// - SOCK_DGRAM for UDP/datagram sockets (available only or Unix/Linux)
Socket(int type = SOCK_STREAM);
/// Creates a Socket from an existing socket file descriptor.
Socket(int type, SOCKET sockfd);
/// Destructor (closes the socket).
~Socket();
/// Connects the socket to an address.
/// Typically used for connecting TCP/IP clients to a ServerSocket.
/// On Unix/Linux host can be a hostname, on Windows it can only be an IP address.
/// @return 0 on success or a negative value on error which is one of Socket::Errors
int connect(const std::string& host, int port);
/// Assigns the socket to localhost.
/// @return 0 on success or a negative value on error, see Socket::Errors
int bind(int port);
/// Assigns the socket to an IP address.
/// On Unix/Linux host can be a hostname, on Windows it can only be an IP address.
/// @return 0 on success or a negative value on error, see Socket::Errors
int bind(const std::string& host, int port);
/// Closes the socket.
int close();
/// Returns true if the socket has been closed.
bool isClosed() const { return sockfd_ == INVALID_SOCKET; }
/// Returns the descriptor of the socket.
SOCKET descriptor() { return sockfd_; }
/// Disables further receive operations.
void shutdownInput();
/// Disables further send operations.
void shutdownOutput();
/// Send sdata to a connected (TCP/IP) socket.
/// Sends the first _len_ bytes in _buf_.
/// @return the number of bytes that were sent, or 0 or shutdownInput() was called on the other side,
/// or Socket::Failed (-1) if an error occured.
/// @note TCP/IP sockets do not preserve record boundaries, see SocketBuffer.
SOCKSIZE send(const SOCKDATA* buf, size_t len, int flags = 0) {
return ::send(sockfd_, buf, len, NO_SIGPIPE_(flags));
}
/// Receives data from a connected (TCP/IP) socket.
/// Reads at most _len_ bytes fand stores them in _buf_.
/// By default, this function blocks the caller until thre is availbale data.
/// @return the number of bytes that were received, or 0 or shutdownOutput() was called on the
/// other side, or Socket::Failed (-1) if an error occured.
SOCKSIZE receive(SOCKDATA* buf, size_t len, int flags = 0) {
return ::recv(sockfd_, buf, len, flags);
}
#if !defined(_WIN32) && !defined(_WIN64)
/// Sends data to a datagram socket.
SOCKSIZE sendTo(void const* buf, size_t len, int flags,
SOCKADDR const* to, socklen_t addrlen) {
return ::sendto(sockfd_, buf, len, NO_SIGPIPE_(flags), to, addrlen);
}
/// Receives data from datagram socket.
SOCKSIZE receiveFrom(void* buf, size_t len, int flags,
SOCKADDR* from, socklen_t* addrlen) {
return ::recvfrom(sockfd_, buf, len, flags, from, addrlen);
}
/// Set the size of the TCP/IP input buffer.
int setReceiveBufferSize(int size);
/// Enable/disable the SO_REUSEADDR socket option.
int setReuseAddress(bool);
/// Set the size of the TCP/IP output buffer.
int setSendBufferSize(int size);
/// Enable/disable SO_LINGER with the specified linger time in seconds.
int setSoLinger(bool, int linger);
/// Enable/disable SO_TIMEOUT with the specified timeout (in milliseconds).
int setSoTimeout(int timeout);
/// Enable/disable TCP_NODELAY (turns on/off TCP coalescence).
int setTcpNoDelay(bool);
/// Return the size of the TCP/IP input buffer.
int getReceiveBufferSize() const;
/// Return SO_REUSEADDR state.
bool getReuseAddress() const;
/// Return the size of the TCP/IP output buffer.
int getSendBufferSize() const;
/// Return SO_LINGER state and the specified linger time in seconds.
bool getSoLinger(int& linger) const;
/// Return SO_TIMEOUT value.
int getSoTimeout() const;
/// Return TCP_NODELAY state.
bool getTcpNoDelay() const;
#endif
private:
friend class ServerSocket;
// Initializes a local INET4 address, returns 0 on success, -1 otherwise.
int setLocalAddress(SOCKADDR_IN& addr, int port);
// Initializes a remote INET4 address, returns 0 on success, -1 otherwise.
int setAddress(SOCKADDR_IN& addr, const std::string& host, int port);
SOCKET sockfd_{};
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
Socket& operator=(Socket&&) = delete;
};
/// TCP/IP IPv4 server socket.
/// Waits for requests to come in over the network.
/// TCP/IP sockets do not preserve record boundaries but SocketBuffer solves this problem.
class ServerSocket {
public:
/// Creates a listening socket that waits for connection requests by TCP/IP clients.
ServerSocket();
~ServerSocket();
/// Accepts a new connection request and returns a socket for exchanging data with this client.
/// This function blocks until there is a connection request.
/// @return the new Socket or nullptr on error.
Socket* accept();
/// Assigns the server socket to localhost.
/// @return 0 on success or a negative value on error, see Socket::Errors
int bind(int port, int backlog = 50);
/// Closes the socket.
int close();
/// Returns true if the socket was closed.
bool isClosed() const { return sockfd_ == INVALID_SOCKET; }
/// Returns the descriptor of the socket.
SOCKET descriptor() { return sockfd_; }
#if !defined(_WIN32) && !defined(_WIN64)
/// Sets the SO_RCVBUF option to the specified value.
int setReceiveBufferSize(int size);
/// Enables/disables the SO_REUSEADDR socket option.
int setReuseAddress(bool);
/// Enables/disables SO_TIMEOUT with the specified timeout (in milliseconds).
int setSoTimeout(int timeout);
/// Turns on/off TCP coalescence (useful in some cases to avoid delays).
int setTcpNoDelay(bool);
#endif
private:
Socket* createSocket(SOCKET);
SOCKET sockfd_{}; // listening socket.
ServerSocket(const ServerSocket&) = delete;
ServerSocket& operator=(const ServerSocket&) = delete;
ServerSocket& operator=(ServerSocket&&) = delete;
};
/** Preserves record boundaries when exchanging messages between connected TCP/IP sockets.
* Ensures that one call to readLine() corresponds to one and exactly one call to writeLine() on the other side.
* By default, writeLine() adds \n at the end of each message and readLine() searches for \n, \r or \n\r
* so that it can retreive the entire record. Beware messages should thus not contain these charecters.
*
* @code
* int main() {
* Socket sock;
* SocketBuffer sockbuf(sock);
*
* int status = sock.connect("localhost", 3331);
* if (status < 0) {
* cerr << "Could not connect" << endl;
* return 1;
* }
*
* while (cin) {
* string request, response;
* cout << "Request: ";
* getline(cin, request);
*
* if (sockbuf.writeLine(request) < 0) {
* cerr << "Could not send message" << endl;
* return 2;
* }
* if (sockbuf.readLine(response) < 0) {
* cerr << "Couldn't receive message" << endl;
* return 3;
* }
* }
* return 0;
* }
@endcode
*/
class SocketBuffer {
public:
/// Constructor.
/// _socket_ must be a connected TCP/IP Socket. It should **not** be deleted as long as the
/// SocketBuffer is used.
/// _inputSize_ and _ouputSize_ are the sizes of the buffers that are used internally for exchanging data.
/// @{
SocketBuffer(Socket*, size_t inputSize = 8192, size_t ouputSize = 8192);
SocketBuffer(Socket&, size_t inputSize = 8192, size_t ouputSize = 8192);
/// @}
~SocketBuffer();
/** Read a message from a connected socket.
* readLine() receives one (and only one) message sent by writeLine() on the other side,
* ie, a call to writeLine() corresponds to one and exactly one call to readLine() on the other side.
* The received data is stored in _message_. This method blocks until the message is fully received.
*
* @return The number of bytes that were received or one of the following values:
* - 0: shutdownOutput() was called on the other side
* - Socket::Failed (-1): a connection error occured
* - Socket::InvalidSocket (-2): the socket is invalid.
* @note the separator (eg \n) is counted in the value returned by readLine().
*/
SOCKSIZE readLine(std::string& message);
/** Send a message to a connected socket.
* writeLine() sends a message that will be received by a single call of readLine() on the other side,
*
* @return see readLine()
* @note if _message_ contains one or several occurences of the separator, readLine() will be
* called as many times on the other side.
*/
SOCKSIZE writeLine(const std::string& message);
/// Reads exactly _len_ bytes from the socket, blocks otherwise.
/// @return see readLine()
SOCKSIZE read(char* buffer, size_t len);
/// Writes _len_ bytes to the socket.
/// @return see readLine()
SOCKSIZE write(const char* str, size_t len);
/// Returns the associated socket.
Socket* socket() { return sock_; }
/// Returns/changes the separator used by readLine().
/// setReadSeparator() changes the symbol used by readLine() to separate successive messages:
/// - if _separ_ < 0 (the default) readLine() searches for \\n, \\r or \\n\\r.
/// - if _separ_ >= 0, readLine() searches for this character to separate messages,
/// @{
void setReadSeparator(int separ);
int readSeparator() const { return insep_; }
// @}
/// Returns/changes the separator used by writeLine().
/// setWriteSeparator() changes the character(s) used by writeLine() to separate successive messages:
/// - if _separ_ < 0 (the default) writeLine() inserts \\n\\r between successive lines.
/// - if _separ_ >= 0, writeLine() inserts _separ_ between successive lines,
/// @{
void setWriteSeparator(int separ);
int writeSeparator() const { return outsep_; }
// @}
private:
SocketBuffer(const SocketBuffer&) = delete;
SocketBuffer& operator=(const SocketBuffer&) = delete;
SocketBuffer& operator=(SocketBuffer&&) = delete;
protected:
bool retrieveLine(std::string& str, SOCKSIZE received);
size_t insize_{}, outsize_{};
int insep_{}, outsep_{};
Socket* sock_{};
struct InputBuffer* in_{};
};
#endif

68
cpp/client.cpp

@ -0,0 +1,68 @@
//
// Client C++ pour communiquer avec un serveur TCP
// eric lecolinet - telecom paristech - 2016/2020
//
#include <iostream>
#include <string>
#include <algorithm>
#