Skip to content

Instantly share code, notes, and snippets.

@Geal
Last active June 21, 2019 21:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Geal/0b51b5021f5620f6209291b86d7b00aa to your computer and use it in GitHub Desktop.
Save Geal/0b51b5021f5620f6209291b86d7b00aa to your computer and use it in GitHub Desktop.
TP multithreading EPSI 4 2018 28/03/2018
#include "ClientSocket.h"
#include "SocketException.h"
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
int main (int argc, char* argv[]) {
if(argc != 3) {
cout << "invalid number of arguments: call with `./client IP port`" << endl;
return 1;
}
string host = argv[1];
int port = atoi(argv[2]);
try {
ClientSocket client_socket ( host, port );
string reply;
try {
client_socket << "Test message.";
client_socket >> reply;
} catch ( SocketException& ) {}
cout << "We received this response from the server:\n\"" << reply << "\"\n";;
} catch ( SocketException& e ) {
cout << "Exception was caught:" << e.description() << endl;
}
return 0;
}
#include "ClientSocket.h"
#include "SocketException.h"
ClientSocket::ClientSocket ( std::string host, int port ){
if ( ! Socket::create() ){
throw SocketException ( "Could not create client socket." );
}
if ( ! Socket::connect ( host, port ) ) {
throw SocketException ( "Could not bind to port." );
}
}
const ClientSocket& ClientSocket::operator << ( const std::string& s ) const{
if ( ! Socket::send ( s ) ){
throw SocketException ( "Could not write to socket." );
}
return *this;
}
const ClientSocket& ClientSocket::operator >> ( std::string& s ) const{
if ( ! Socket::recv ( s ) ){
throw SocketException ( "Could not read from socket." );
}
return *this;
}
#ifndef ClientSocket_class
#define ClientSocket_class
#include "Socket.h"
class ClientSocket : private Socket{
public:
ClientSocket ( std::string host, int port );
virtual ~ClientSocket(){};
const ClientSocket& operator << ( const std::string& ) const;
const ClientSocket& operator >> ( std::string& ) const;
};
#endif

Cet exercice a pour but de démontrer l'usage de programmation en multithread pour améliorer les performances d'un programme, et d'explorer les compromis à faire.

En groupes de 2 à 4 personnes, vous travaillerez sur un client et un serveur pour un protocole de messagerie.

Fonctionnement du serveur

Le serveur fournit un salon de discussion commun à tous les clients connectés. Tout message envoyé par un client sera envoyé à tous les autres. Au démarrage de la connexion, le client enverra le nom d'utilisateur par la commande USER. Le client pourra ensuite recevoir les messages provenant d'autres utilisateurs, ainsi qu'envoyer ses propres messages par la commande MSG.

Explorez plusieurs solutions permettant de gérer de nombreux clients en même temps:

  • solution naïve: le serveur passe en boucle sur toutes les sockets pour vérifier si elles ont des messages (marquer la socket comme "non bloquante" pour ce test)
  • à chaque nouvelle connexion d'un client, créer un thread pour gérer cette connexion. Comment gérer l'envoi des messages entre threads? Combien de clients pouvez-vous gérer en même temps?
  • même principe que la connexion précédente, mais cette fois en employant un pool de threads déjà créés. Obtenez-vous un gain de performances par rapport à la solution précédente?
  • testez un serveur en single thread utilisant la fonction poll() pour surveiller plusieurs sockets en même temps. Combien de clients pouvez-vous gérer en même temps sur un seul thread?

Fonctionnement du client

Le client attend au démarrage une première entrée de l'utilisateur pour indiquer son nom. Le nom sera utilisé pour indiquer son identité au server par la commande USER. Après avoir indiqué le nom, le client pourra recevoir des messages du serveur à tout moment, et l'utilisateur pourra envoyer des messages à tout moment. Les messages seront affichés avec le nom de l'utilisateur qui l'a envoyé.

Trouvez une solution permettant d'afficher les messages reçus alors que l'utilisateur peut être au milieu de l'écriture d'un message.

Code fourni

Pour démarrer l'exercice plus rapidement, des exemples de code pour le client et le serveur sont fournis, ainsi qu'un fichier Makefile permettant de tout compiler en même temps (lancez la compilation en ligne de commande par la commande make).

Protocole de communication

Client vers serveur

USER nom_d_utilisateur\n: indique au serveur le nom d'utilisateur choisi par le client. Le nom d'utilisateur ne doit pas contenir d'espace. MSG contenu_du_message\n: envoie un message sur le salon de discussion. Le message ne doit pas contenir de sauts de ligne

Serveur vers client

MSG nom_d_utilisateur contenu_du_message: contient un message envoyé par un autre utilisateur

Debug et développement

Vous pouvez utiliser la commande netcat pour tester le client et le serveur, notamment sous une charge plus élevée.

server_objects = ServerSocket.o Socket.o server.o
client_objects = ClientSocket.o Socket.o client.o
all : server client
server: $(server_objects)
g++ -std=c++11 -lpthread -pthread -o server $(server_objects)
client: $(client_objects)
g++ -std=c++11 -lpthread -pthread -o client $(client_objects)
Socket: Socket.cpp
ServerSocket: ServerSocket.cpp
ClientSocket: ClientSocket.cpp
server.o: server.cpp
g++ -std=c++11 -lpthread -pthread -c server.cpp
client.o: client.cpp
g++ -std=c++11 -lpthread -pthread -c client.cpp
clean:
rm -f *.o server client
#include "ServerSocket.h"
#include "SocketException.h"
#include <string>
#include <iostream>
#include <cstdlib>
using namespace std;
int main (int argc, char* argv[]) {
if(argc !=2) {
cout << "invalid number of arguments: call with `./server port`" << endl;
return 1;
}
int port = atoi(argv[1]);
cout << "running....\n";
try {
ServerSocket server ( port );
while ( true ) {
ServerSocket new_sock;
server.accept ( new_sock );
try {
while ( true ) {
string data;
new_sock >> data;
new_sock << data;
}
} catch ( SocketException& ) {}
}
} catch ( SocketException& e ) {
cout << "Exception was caught:" << e.description() << "\nExiting" << endl;
}
return 0;
}
#include "ServerSocket.h"
#include "SocketException.h"
ServerSocket::ServerSocket ( int port ){
if ( ! Socket::create() ){
throw SocketException ( "Could not create server socket." );
}
if ( ! Socket::bind ( port ) ){
throw SocketException ( "Could not bind to port." );
}
if ( ! Socket::listen() ){
throw SocketException ( "Could not listen to socket." );
}
}
ServerSocket::~ServerSocket(){}
const ServerSocket& ServerSocket::operator << ( const std::string& s ) const{
if ( ! Socket::send ( s ) ){
throw SocketException ( "Could not write to socket." );
}
return *this;
}
const ServerSocket& ServerSocket::operator >> ( std::string& s ) const{
if ( ! Socket::recv ( s ) ){
throw SocketException ( "Could not read from socket." );
}
return *this;
}
void ServerSocket::accept ( ServerSocket& sock ){
if ( ! Socket::accept ( sock ) ){
throw SocketException ( "Could not accept socket." );
}
}
#ifndef ServerSocket_class
#define ServerSocket_class
#include "Socket.h"
class ServerSocket : private Socket
{
public:
ServerSocket ( int port );
ServerSocket (){};
virtual ~ServerSocket();
const ServerSocket& operator << ( const std::string& ) const;
const ServerSocket& operator >> ( std::string& ) const;
void accept ( ServerSocket& );
};
#endif
#include "Socket.h"
#include "string.h"
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#ifdef __APPLE__
#define SO_NOSIGPIPE 0x1022
#define MSG_NOSIGNAL 0x1022
#endif
Socket::Socket() : m_sock ( -1 ) {
memset ( &m_addr, 0, sizeof ( m_addr ) );
}
Socket::~Socket(){
if ( is_valid() ) {
::close ( m_sock );
}
}
bool Socket::create() {
m_sock = socket ( AF_INET, SOCK_STREAM, 0 );
if ( !is_valid() ) {
return false;
}
// TIME_WAIT - argh
int on = 1;
if ( setsockopt ( m_sock, SOL_SOCKET, SO_REUSEADDR, ( const char* ) &on, sizeof ( on ) ) == -1 ) {
return false;
}
return true;
}
bool Socket::bind ( const int port ){
if ( ! is_valid() ) {
return false;
}
m_addr.sin_family = AF_INET;
m_addr.sin_addr.s_addr = INADDR_ANY;
m_addr.sin_port = htons ( port );
int bind_return = ::bind ( m_sock,
( struct sockaddr * ) &m_addr,
sizeof ( m_addr ) );
return bind_return != -1;
}
bool Socket::listen() const {
if ( ! is_valid() ) {
return false;
}
int listen_return = ::listen ( m_sock, MAXCONNECTIONS );
return listen_return != -1;
}
bool Socket::accept ( Socket& new_socket ) const {
int addr_length = sizeof ( m_addr );
new_socket.m_sock = ::accept ( m_sock, ( sockaddr * ) &m_addr, ( socklen_t * ) &addr_length );
return new_socket.m_sock > 0;
}
bool Socket::send ( const std::string s ) const {
int status = ::send ( m_sock, s.c_str(), s.size(), MSG_NOSIGNAL );
return status != -1;
}
int Socket::recv ( std::string& s ) const
{
char buf [ MAXRECV + 1 ];
s = "";
memset ( buf, 0, MAXRECV + 1 );
int status = ::recv ( m_sock, buf, MAXRECV, 0 );
if ( status == -1 ) {
std::cout << "status == -1 errno == " << errno << " in Socket::recv\n";
return 0;
} else if ( status == 0 ) {
return 0;
} else {
s = buf;
return status;
}
}
bool Socket::connect ( const std::string host, const int port ) {
if ( ! is_valid() ) return false;
m_addr.sin_family = AF_INET;
m_addr.sin_port = htons ( port );
int status = inet_pton ( AF_INET, host.c_str(), &m_addr.sin_addr );
if ( errno == EAFNOSUPPORT ) return false;
status = ::connect ( m_sock, ( sockaddr * ) &m_addr, sizeof ( m_addr ) );
return status == 0;
}
void Socket::set_non_blocking ( const bool b ) {
int opts;
opts = fcntl ( m_sock,
F_GETFL );
if ( opts < 0 ){
return;
}
if ( b ) {
opts = ( opts | O_NONBLOCK );
} else {
opts = ( opts & ~O_NONBLOCK );
}
fcntl ( m_sock, F_SETFL, opts );
}
// Definition of the Socket class
#ifndef Socket_class
#define Socket_class
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string>
#include <arpa/inet.h>
const int MAXHOSTNAME = 200;
const int MAXCONNECTIONS = 5;
const int MAXRECV = 500;
class Socket {
public:
Socket();
virtual ~Socket();
// Server initialization
bool create();
bool bind ( const int port );
bool listen() const;
bool accept ( Socket& ) const;
// Client initialization
bool connect ( const std::string host, const int port );
// Data Transimission
bool send ( const std::string ) const;
int recv ( std::string& ) const;
void set_non_blocking ( const bool );
bool is_valid() const { return m_sock != -1; }
int m_sock;
sockaddr_in m_addr;
};
#endif
// SocketException class
#ifndef SocketException_class
#define SocketException_class
#include <string>
class SocketException {
public:
SocketException ( std::string s ) : m_s ( s ) {};
~SocketException (){};
std::string description() { return m_s; }
private:
std::string m_s;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment