My task was to write a server class, which uses select()
, for a student projet.
It was working, but i'm now willing to improve it a lot, and be able to use it in other personnal projects, so any critique and improvement idea is welcomed !
Here is the main.cpp :
// example packet
typedef struct packet {
char msg[ 4096 ];
} packet;
int main( int ac, char * av[] )
{
packet shutdownPacket = { "QUIT" };
Server< packet > server;
if ( ac != 2 ) {
cerr << "Usage : ./server port" << endl;
return ( EXIT_FAILURE );
}
try {
server.bindSock( av[1] );
server.listenPort();
server.setDefaultOnReadCallback( & OnReadCallback ); // this one will be detailed beyond
server.setOnShutdownPacket( shutdownPacket ); // this packet will be sent to all clients on server quit
server.startServer();
} catch ( const exception & e ) {
cerr << e.what() << endl;
}
return ( EXIT_SUCCESS );
}
The function pointer passed to setDefaultOnReadCallback()
is used to define the behavior when activity is detected on the client file descriptor, here is an example that will just forward the packet to every other clients :
void OnReadCallback( Server< header > & server,
Client< header > & client )
{
auto list = server.getClientList();
packet * clientPacket = server.getPacket();
for ( size_t i = 0; i < list.size(); i++ ) {
if ( list[i].fd() != client.fd() )
server.send( list[i], clientPacket, server.getPacketSize() );
}
}
And here is the server implementation :
#pragma once
#include <vector>
#include <string>
#include <stdexcept>
#include <iostream>
#include <algorithm>
#include "ServerSSL.h"
template <class T> class Client;
template <class T> class Server;
template <typename T>
using callback = void (*)( Server<T> & server, Client<T> & client );
template <class T>
class Server {
private:
bool _working;
SOCKET _listenFd;
int _fdMax;
vector< Client< T > > _selectFds;
fd_set _initFds;
fd_set _readFds;
T * _packet;
T _onShutDownPacket;
pSize _packetSize;
ServerSSL _ssl; // i won't detail the implementation here but the server uses SSL
callback<T> _defaultOnReadCallback;
public:
Server()
{
_working = true;
_listenFd = INVALID_SOCKET;
_packetSize = sizeof( T );
_packet = new T;
_defaultOnReadCallback = NULL;
memset( _packet, 0, _packetSize );
memset( & _onShutDownPacket, 0, _packetSize );
FD_ZERO( & _initFds );
}
~Server()
{
delete _packet;
}
void bindSock( const string & port )
{
struct addrinfo *res;
struct addrinfo *tmp;
struct addrinfo hints;
SOCKET sockfd;
int err;
memset( & hints, 0, sizeof( struct addrinfo ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
size_t p;
try {
p = stol( port );
} catch ( const exception & e ) {
p = 1664;
}
err = getaddrinfo( NULL, to_string( p ).c_str(), & hints, & res );
if ( err != 0 ) {
freeaddrinfo( res );
throw runtime_error( gai_strerror( err ) );
}
for ( tmp = res; tmp != NULL; tmp = tmp->ai_next )
{
sockfd = socket( tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol );
if ( sockfd != INVALID_SOCKET ) {
if ( bind( sockfd, tmp->ai_addr, tmp->ai_addrlen ) == 0 ) {
break;
}
}
closesocket( sockfd );
}
freeaddrinfo( res );
if ( tmp == NULL )
throw runtime_error( "Unable to bind socket" );
_listenFd = sockfd;
}
void listenPort( const int backlog = 42 )
{
if ( listen( _listenFd, backlog ) == SOCKET_ERROR )
throw runtime_error( neterror( errno ) );
}
void startServer( void )
{
FD_SET( _listenFd, & _initFds );
_fdMax = _listenFd;
_selectFds.push_back( Client<T>( _listenFd ) );
for ( ; _working ; )
{
_readFds = _initFds;
if ( select( _fdMax + 1, & _readFds, NULL, NULL, NULL ) == -1 ) {
notifyServerQuit();
throw runtime_error( neterror( errno ) );
}
for ( size_t i = 0; i < _selectFds.size(); i++ ) {
if ( FD_ISSET( _selectFds[i].fd(), & _readFds ) ) {
if ( _selectFds[i].fd() == _listenFd )
acceptClient();
else
onReadCallback( _selectFds[i] );
}
}
}
notifyServerQuit();
}
void shutdown( void )
{
_working = false;
}
void setOnShutdownPacket( T packet )
{
_onShutDownPacket = packet;
}
void setDefaultOnReadCallback( const callback< T > defaultCallback )
{
_defaultOnReadCallback = defaultCallback;
}
SOCKET fd( void ) const
{
return _listenFd;
}
T * getPacket( void ) const
{
return _packet;
}
pSize getPacketSize( void ) const
{
return _packetSize;
}
vector< Client< T > > getClientList( void ) const
{
return _selectFds;
}
int send( const Client<T> & client, void * packet, const size_t size ) const
{
return _ssl.send( client.ssl(), packet, size );
}
int recv( const Client<T> & client, void * packet, const size_t size ) const
{
return _ssl.recv( client.ssl(), packet, size );
}
private:
void acceptClient( void )
{
SOCKADDR_STORAGE addr;
SOCKET nsockfd;
socklen_t addrlen;
addrlen = sizeof( addr );
memset( & addr, 0, addrlen );
nsockfd = accept( _listenFd, ( SOCKADDR * ) & addr, & addrlen );
if ( nsockfd == SOCKET_ERROR ) {
cerr << "accept " << neterror( errno ) << endl;
return ;
}
_selectFds.push_back( Client< T >( nsockfd, _ssl.new_connexion(), _defaultOnReadCallback ) );
FD_SET( nsockfd, & _initFds );
if ( nsockfd > _fdMax )
_fdMax = nsockfd;
cout << "Connection accepted" << endl;
}
void onReadCallback( Client<T> & client )
{
memset( _packet, 0, _packetSize );
int len = _ssl.recv( client.ssl(), _packet, _packetSize );
if ( len <= 0 )
closeConnection( len, client );
else
client.work( *this, client );
}
void closeConnection( const size_t recvRet, const Client<T> & client )
{
if ( recvRet == 0 )
cout << "Connection closed" << endl;
else {
cerr << "recv " << neterror( errno ) << endl;
}
if ( closesocket( client.fd() ) == SOCKET_ERROR )
cerr << "close " << neterror( errno ) << endl;
_ssl.delete_connexion( client.ssl() );
FD_CLR( client.fd(), & _initFds );
_selectFds.erase( remove_if( _selectFds.begin(),
_selectFds.end(),
[client] ( const Client<T> & cl )
{ return cl.fd() == client.fd(); } ),
_selectFds.end() );
}
void notifyServerQuit( void )
{
cout << _onShutDownPacket.cmd << endl;
for ( size_t i = 0; i < _selectFds.size(); i++ ) {
if ( _selectFds[i].ssl() ) {
_ssl.send( _selectFds[i].ssl(),
& _onShutDownPacket,
_packetSize );
_ssl.delete_connexion( _selectFds[i].ssl() );
}
if ( closesocket( _selectFds[i].fd() ) == SOCKET_ERROR )
cerr << "close " << neterror( errno ) << endl;
}
}
};
/*
** CLIENT CLASS
*/
template <class T>
class Client {
private:
SOCKET _fd;
SSL * _ssl;
callback<T> _defaultOnReadCallback;
public:
Client( const SOCKET fd, SSL * ssl = NULL, const callback<T> job = NULL ) : _fd( fd )
{
_defaultOnReadCallback = job;
setOnReadCallback( job );
_ssl = ssl;
if ( _ssl != NULL ) {
SSL_set_fd( _ssl, _fd );
if ( SSL_accept( _ssl ) == -1 )
throw runtime_error( ERR_reason_error_string( ERR_get_error() ) );
}
}
~Client() {}
SOCKET fd( void ) const
{
return _fd;
}
SSL * ssl( void ) const
{
return _ssl;
}
void setOnReadCallback( const callback<T> task = NULL )
{
if ( task == NULL )
work = _defaultOnReadCallback;
else
work = task;
}
callback<T> work;
};
for
statements), sometimes they're not. \$\endgroup\$ – user2296177 Nov 20 '15 at 19:53