Encapsulation d'une partie de l'api socket portable

Soyez le premier à donner votre avis sur cette source.

Vue 6 351 fois - Téléchargée 207 fois

Description

voila, ceci est une tentative d'encapsulation dans des classes d'une partie des api sockets.

ce code nécéssite une partie de boost : boost::noncopyable (www.boost.org) qui empeche la copie d'une instance d'un objet.

Source / Exemple :


#ifndef sock_h
#define sock_h

#include <boost/utility.hpp>
#include <string>

#ifdef WIN32
	#include <winsock.h>
	#define wsastart(); {WSADATA WsaData; WSAStartup(MAKEWORD(1,1), &WsaData);}
	#define wsaclean(); WSACleanup();
	#pragma comment(lib, "ws2_32.lib")
#else
	#define wsastart();
	#define wsaclean();
	#include <unistd.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <netdb.h>
	#include <sys/types.h>
	#define SOCKET_ERROR (-1) /* défini dans winsock.h */
	typedef int SOCKET;
	#define INVALID_SOCKET (SOCKET)(~0) /* défini dans winsock.h */
	#define closesocket close
#endif

struct socket_error : std::exception {};

struct base_sock : boost::noncopyable
{
	struct address
	{
	protected:
		std::string _name;
		unsigned short _port;

	public:
		address(const address & other)
			: _name(other._name), _port(other._port)
		{
		}

		address(const std::string & __name, const unsigned short & __port)
			: _name(__name), _port(__port)
		{
		}

		address(const std::string & __name)
			: _name(__name), _port(0)
		{
		}

		address(const unsigned short & __port)
			: _port(__port)
		{
		}

		address()
			: _port(0)
		{
		}

		address & operator = (const address & other)
		{
			_name = other._name;
			_port = other._port;
			return *this;
		}
		
		void clear()
		{
			_name = "";
			_port = 0;
		}

		bool is_empty() const
		{
			return _name == "" && _port == 0;
		}

		const unsigned short & port() const
		{
			return _port;
		}

		const std::string & name() const
		{
			return _name;
		}

		unsigned short & set_port(const unsigned short & port)
		{
			return _port = port;
		}

		std::string & set_name(const std::string & name)
		{
			return _name = name;
		}

		bool operator == (const address & other) const
		{
			return (_name == other._name) && (_port == other._port);
		}
	};
	
protected:
	address sock_address;
	SOCKET s;

private:
	void create()
	{
		close();
		s = socket(AF_INET, SOCK_STREAM, 0);
		if(s == INVALID_SOCKET) throw socket_error();
	}

public:

	base_sock()
		: s(INVALID_SOCKET)
	{
		wsastart();
	}
	
	virtual ~base_sock()
	{
		close();
		wsaclean();
	}
	
	bool good() const
	{
		return s != INVALID_SOCKET;
	}

	virtual void write(const std::string & data) const
	{
		if(!good()) throw socket_error();
		if(::send(s, data.data(), (int)data.size(), 0) == SOCKET_ERROR) throw socket_error();
	}

	virtual std::string read() const
	{
		if(!good()) throw socket_error();

		// on crée un buf avec la taille max de data qu'il peut recevoir
		unsigned long cbdata;
		int cbopt = sizeof(cbdata);
		if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char*)&(cbdata), &cbopt) == SOCKET_ERROR) throw socket_error();
		char* data = new char[cbdata];

		// on recoit
		int len;
		if((len = recv(s, data, cbdata, 0)) == SOCKET_ERROR)
		{
			delete[] data;
			throw socket_error();
		} else {
			std::string ret(data, len);
			delete[] data;
			return ret;
		}
	}
	
	void close()
	{
		if(!good()) return;
		::closesocket(s);
		sock_address.clear();
		s = INVALID_SOCKET;
	}

	void listen(const address & addr)
	{
		create();

	    sockaddr_in sain;
	    sain.sin_family = AF_INET;
		sain.sin_port = ::htons(addr.port());

		if(addr.name() == "")
	    sain.sin_addr.s_addr = INADDR_ANY;
		else
		{
			hostent* host = ::gethostbyname(addr.name().c_str());
			if(host == NULL) throw socket_error();
			memcpy((void*)&sain.sin_addr, (void*)host->h_addr, 4);
		}

		if(::bind(s, (struct sockaddr *) &sain, sizeof(struct sockaddr_in)) == SOCKET_ERROR) throw socket_error();
		sock_address = addr;

		if(::listen(s, SOMAXCONN) == SOCKET_ERROR) throw socket_error();
	}

	void accept(const base_sock & listen_sock)
	{
		if(!listen_sock.good()) throw socket_error();
		
		create();

		sockaddr_in addr;
		int len = sizeof(sockaddr_in);
		s = ::accept(listen_sock.s, (sockaddr*)&addr, &len);
		if(s == INVALID_SOCKET) throw socket_error();

		sock_address.set_port(addr.sin_port);
		sock_address.set_name(inet_ntoa(addr.sin_addr));
	}
	
	void connect(const address & addr)
	{
		create();

		sockaddr_in sain;
		int len = sizeof(sockaddr_in);
	    sain.sin_family = AF_INET;
		sain.sin_port = ::htons(addr.port());

		hostent* host = ::gethostbyname(addr.name().c_str());
		if(host == NULL) throw socket_error();
		memcpy((void*)&sain.sin_addr, (void*)host->h_addr, 4);
		
		if(::connect(s, (struct sockaddr *) &sain, sizeof(struct sockaddr_in)) == SOCKET_ERROR) throw socket_error();
		sock_address = addr;
	}

	const address & get_address()
	{
		return sock_address;
	}

	class sock_set : boost::noncopyable
	{
		// initialisation d'une instance nulle
		sock_set(const bool & is_null)
			: bnull(is_null)
		{
		}

	protected:
		fd_set fds;
		bool bnull;

	public:
		
		// renvoit un sock_set nul
		static sock_set & null()
		{
			static sock_set ret(true);
			return ret;
		}

		sock_set()
			: bnull(false)
		{
			FD_ZERO(&fds);
		}

		inline void add(const base_sock & _s)
		{
			if(bnull) throw socket_error();
			if(!_s.good()) throw socket_error();
			FD_SET(_s.s, &fds);
		}

		inline void del(const base_sock & _s)
		{
			if(bnull) throw socket_error();
			if(!_s.good()) throw socket_error();
			FD_CLR(_s.s, &fds);
		}

		inline bool is_set(const base_sock & _s) const
		{
			if(bnull) throw socket_error();
			if(!_s.good()) throw socket_error();
			return FD_ISSET(_s.s, &fds) != 0;
		}

		static int select(sock_set & read_set, sock_set & write_set, sock_set & error_set, const timeval & timeout)
		{
			if(read_set.bnull && write_set.bnull && error_set.bnull) throw socket_error();

			fd_set * r = read_set.bnull ? NULL : &(read_set.fds);
			fd_set * w = write_set.bnull ? NULL : &(write_set.fds);
			fd_set * e = error_set.bnull ? NULL : &(error_set.fds);
			
			int ret = ::select(0, r, w, e, &timeout);
			if(ret == SOCKET_ERROR) throw socket_error();
			return ret;
		}

	};
};

#endif

Conclusion :


je l'ai testé sous windows (vc++7) ca a l'air de bien fonctionner.
normalement il devrait compiler/fonctionner sous linux, si qq1 pouvait tester (je n'en ai plus sous la main)

j'essaierais rapidement de dériver base_sock pour supporter la cryptographie, et pour respecter l'interface des i/o streams standards du c++, ainsi que d'améliorer la gestion d'erreur.

merci de me signaler tout bug, suggestion ou demande d'explication :)

14/01 : maj

correction de 2-3 petites fautes
petites modifications pour faciliter la dérivation
suppression de la lourdeur d'utilisation du template select par la mise en place d'un sock_set null

Codes Sources

A voir également

Ajouter un commentaire Commentaires
xterminhate Messages postés 371 Date d'inscription dimanche 4 janvier 2004 Statut Membre Dernière intervention 23 septembre 2009
18 janv. 2004 à 01:54
Blackgoddess,

J'attendais avec impatience le résultat de ton travail.

Alors voici quelques (petites) remarques pour commencer. Si l'utilisateur manipules plusieurs instances de base_sock, il execute plusieurs fois WSAStart et WSAClean...aucune idée de l'impact mais un vieux flag static pourrait permettre de l'éviter.

D'autre part, si l'utilisateur souhaite exploiter base_sock pour transmettre de grandes quantitiés d'informations, alors il est nécessaire d'implementer un mécanisme de fragmentation dans write() (contenant l'appel de send()). En effet, la taille du buffer à envoyer ne peut pas exceder SO_MAX_MSG_SIZE. Cela doit etre transparent pour l'utilisateur.

A suivre...

Cordialement,
Xter.
BlackGoddess Messages postés 338 Date d'inscription jeudi 22 août 2002 Statut Membre Dernière intervention 14 juin 2005
18 janv. 2004 à 14:29
pour les appels a WSAStartup et WSACleanup :
lorsqu'un processus appelle une fois WSAStartup, les dll réseau sont chargées. puis, lorsqu'on le rappelle une 2eme fois, un compteur interne estr sulement incrémenté. pour les appels a WSACleanup, le compteur interne est décrémenté et lorsqu'il atteint 0 les dll sont déchargées.
par contre tu as peut-etre raison dans le sens ou un appel de WSAStartup va négocier la version entre celle des dll reseau et celle que ton prog demande, et ce n'est pas très optimisé de refaire cette négociation à chaque instanciation.

ensuite, pour les grandes quantités d'informations, il faut bien comprendre que ceci n'est pour l'instant qu'une encapsulation 'basique' de l'api c socket. Je suis en train de développer d'autres classes de dérivant de celle-ci pour supporter un transfert crypté, la fragmentation de paquets comme tu en parle, et faire respecter l'interface istream / ostream standard c++ pour la lecture/ecriture d'un socket.

en tout cas merci de ces remarques :)
xterminhate Messages postés 371 Date d'inscription dimanche 4 janvier 2004 Statut Membre Dernière intervention 23 septembre 2009
18 janv. 2004 à 14:55
Je pense que tu devrais intégrer une fragmentation basique dans la fonction membre write. Uniquement basée sur la taille max du buffer d'émission. Et je m'explique.

On constate trop souvent "des fuites" dans nos abstractions et cela est dommagable. En effet, l'utilisateur de l'abstraction base_sock n'est pas censée connaitre les limitations des fonctions de la librairie socket que tu encapsules (en particulier la fonction send).

D'ailleurs, la couche logicielle plus avancée (que tu programmes) et qui exploite base_sock n'est pas non plus censée connaitre ses limitations. Imagine que tu developpes cette couche logicielle avancée à partir d'une absctration base_sock que tu n'aurais pas programmé....

Donc, pour être une parfaite abstraction, base_sock devrait implementer ce mécanisme de fragmentation.

Bon courage pour la suite !

Cordialement,
Xter.
xterminhate Messages postés 371 Date d'inscription dimanche 4 janvier 2004 Statut Membre Dernière intervention 23 septembre 2009
18 janv. 2004 à 15:53
Dans read, tu testes la valeur de retour de recv() selon deux cas (>=0 ou SOCKET_ERROR).

Il faut distinguer les cas >0 et =0. En effet, =0 signifie que la connexion a été fermée.

Cordialement,
Xter.
BlackGoddess Messages postés 338 Date d'inscription jeudi 22 août 2002 Statut Membre Dernière intervention 14 juin 2005
18 janv. 2004 à 18:10
tu as en effet parfaitement raison, je vais corriger ca :)
merci bcp :)

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.