Encapsulation d'une partie de l'api socket portable

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

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.