E-MailRelay

gmultiserver.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gmultiserver.cpp
19///
20
21#include "gdef.h"
22#include "gmultiserver.h"
23#include "glisteners.h"
24#include "gdatetime.h"
25#include "gstr.h"
26#include "gtest.h"
27#include "glog.h"
28#include "gassert.h"
29#include <list>
30#include <algorithm>
31
32 GNet::MultiServer::MultiServer( EventState es , const G::StringArray & listener_list , unsigned int port ,
33 const std::string & server_type , ServerPeer::Config server_peer_config , Server::Config server_config ) :
34 m_es(es) ,
35 m_listener_list(listener_list) ,
36 m_port(port) ,
37 m_server_type(server_type) ,
38 m_server_peer_config(server_peer_config) ,
39 m_server_config(server_config) ,
40 m_if(es,*this) ,
41 m_interface_event_timer(*this,&MultiServer::onInterfaceEventTimeout,es)
42{
43 Listeners listeners( m_if , m_listener_list , m_port ) ;
44
45 // fail if any bad names (eg. "foo/bar")
46 if( listeners.hasBad() )
47 throw InvalidName( listeners.badName() ) ;
48
49 // fail if no addresses and no prospect of getting any
50 if( listeners.defunct() )
51 throw NoListeningAddresses() ;
52
53 // warn if no addresses from one or more interface names
54 if( listeners.hasEmpties() )
55 {
56 G_WARNING( "GNet::MultiServer::ctor: no addresses bound to named network interface"
57 << listeners.logEmpties() ) ;
58 }
59
60 // warn if doing nothing until an interface comes up
61 if( listeners.idle() )
62 {
63 G_WARNING( "GNet::MultiServer::ctor: " << m_server_type << " server: nothing to do: "
64 << "waiting for interface" << listeners.logEmpties() ) ;
65 }
66
67 // warn if we got addresses from an interface name but won't get dynamic updates
68 if( listeners.noUpdates() )
69 {
70 G_WARNING_ONCE( "GNet::MultiServer::ctor: named network interfaces "
71 "are not being monitored for address updates" ) ;
72 }
73
74 // instantiate the servers
75 for( const auto & fd : listeners.fds() )
76 createServer( Descriptor(fd) ) ;
77 for( const auto & a : listeners.fixed() )
78 createServer( a , true ) ;
79 for( const auto & a : listeners.dynamic() )
80 createServer( a , false ) ;
81}
82
83 GNet::MultiServer::~MultiServer()
84{
85 serverCleanup() ;
86}
87
88 void GNet::MultiServer::createServer( Descriptor fd )
89{
90 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es ,
91 fd , m_server_peer_config , m_server_config ) ) ;
92}
93
94 void GNet::MultiServer::createServer( const Address & address , bool fixed )
95{
96 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es ,
97 fixed , address , m_server_peer_config , m_server_config ) ) ;
98}
99
100 void GNet::MultiServer::createServer( const Address & address , bool fixed , std::nothrow_t )
101{
102 try
103 {
104 createServer( address , fixed ) ;
105 G_LOG_S( "GNet::MultiServer::createServer: new " << m_server_type
106 << " server on " << displayString(address) ) ;
107 }
108 catch( Socket::SocketBindError & e )
109 {
110 // (can fail here if notified too soon, but succeeds later)
111 G_LOG( "GNet::MultiServer::createServer: failed to bind " << displayString(address)
112 << " for new " << m_server_type << " server:"
113 << G::Str::tail(e.what(),std::string_view(e.what()).rfind(':')) ) ;
114 }
115}
116
117 void GNet::MultiServer::serverCleanup()
118{
119 for( auto & server : m_server_list )
120 {
121 server->cleanup() ;
122 }
123}
124
125 void GNet::MultiServer::onInterfaceEvent( const std::string & /*description*/ )
126{
127 // notifications can be periodic and/or bursty, so minimal logging here
128 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: network configuration change event" ) ;
129 m_if.load() ;
130 m_interface_event_timer.startTimer( 1U , 500000U ) ; // maybe increase for fewer bind warnings
131}
132
133 void GNet::MultiServer::onInterfaceEventTimeout()
134{
135 // get a fresh address list
136 Listeners listeners( m_if , m_listener_list , m_port ) ;
137
138 // delete old
139 for( auto server_iter = m_server_list.begin() ; server_iter != m_server_list.end() ; )
140 {
141 if( (*server_iter)->dynamic() && !gotAddressFor( **server_iter , listeners.dynamic() ) )
142 server_iter = removeServer( server_iter ) ;
143 else
144 ++server_iter ;
145 }
146
147 // create new
148 for( const auto & address : listeners.dynamic() )
149 {
150 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: address: " << displayString(address) ) ;
151 if( !gotServerFor(address) )
152 {
153 createServer( address , true , std::nothrow ) ;
154 }
155 }
156}
157
158GNet::MultiServer::ServerList::iterator GNet::MultiServer::removeServer( ServerList::iterator iter )
159{
160 auto & ptr = *iter ;
161 G_LOG_S( "GNet::MultiServer::removeServer: deleting " << m_server_type
162 << " server on " << displayString(ptr->address()) ) ;
163 return m_server_list.erase( iter ) ;
164}
165
166 bool GNet::MultiServer::match( const Address & interface_address , const Address & server_address )
167{
168 // both addresses should have a well-defined scope-id, so include scope-ids
169 // in the match -- this allows for multiple interfaces to have the same link-local address
170 return interface_address.same( server_address , interface_address.scopeId() && server_address.scopeId() ) ;
171}
172
173 bool GNet::MultiServer::gotAddressFor( const Listener & server , const AddressList & address_list ) const
174{
175 Address server_address = server.address() ;
176 return address_list.end() != std::find_if( address_list.begin() , address_list.end() ,
177 [server_address](const Address & a){ return match(a,server_address); } ) ;
178}
179
180 bool GNet::MultiServer::gotServerFor( Address interface_address ) const
181{
182 return std::any_of( m_server_list.begin() , m_server_list.end() ,
183 [interface_address](const ServerPtr &ptr){ return match(interface_address,ptr->address()); } ) ;
184}
185
186std::string GNet::MultiServer::displayString( const Address & address )
187{
188 return address.displayString( true ) ;
189}
190
191 void GNet::MultiServer::serverReport( const std::string & group ) const
192{
193 for( const auto & server : m_server_list )
194 {
195 G_ASSERT( server.get() != nullptr ) ;
196 if( !server ) continue ;
197 G_LOG_S( "GNet::MultiServer: " << (group.empty()?"":"[") << group << (group.empty()?"":"] ")
198 << m_server_type << " server on " << displayString(server->address()) ) ;
199 }
200}
201
202std::unique_ptr<GNet::ServerPeer> GNet::MultiServer::doNewPeer( EventStateUnbound esu ,
203 ServerPeerInfo && pi , const ServerInfo & si )
204{
205 return newPeer( esu , std::move(pi) , si ) ;
206}
207
208 bool GNet::MultiServer::hasPeers() const
209{
210 for( const auto & server : m_server_list )
211 {
212 G_ASSERT( server.get() != nullptr ) ;
213 if( !server ) continue ;
214 if( server->hasPeers() )
215 return true ;
216 }
217 return false ;
218}
219
220std::vector<std::weak_ptr<GNet::ServerPeer>> GNet::MultiServer::peers()
221{
222 using List = std::vector<std::weak_ptr<ServerPeer>> ;
223 List result ;
224 for( auto & server : m_server_list )
225 {
226 G_ASSERT( server.get() != nullptr ) ;
227 if( !server ) continue ;
228 List list = server->peers() ;
229 result.insert( result.end() , list.begin() , list.end() ) ;
230 }
231 return result ;
232}
233
234// ==
235
236GNet::MultiServerImp::MultiServerImp( MultiServer & ms , EventState es , bool fixed , const Address & address ,
237 ServerPeer::Config server_peer_config , Server::Config server_config ) :
238 GNet::Server(es,address,server_peer_config,server_config) ,
239 m_ms(ms) ,
240 m_fixed(fixed)
241{
242}
243
244GNet::MultiServerImp::MultiServerImp( MultiServer & ms , EventState es , Descriptor fd ,
245 ServerPeer::Config server_peer_config , Server::Config server_config ) :
246 GNet::Server(es,fd,server_peer_config,server_config) ,
247 m_ms(ms) ,
248 m_fixed(true)
249{
250}
251
252GNet::MultiServerImp::~MultiServerImp()
253= default;
254
255 bool GNet::MultiServerImp::dynamic() const
256{
257 return !m_fixed ;
258}
259
260 void GNet::MultiServerImp::cleanup()
261{
262 serverCleanup() ;
263}
264
265std::unique_ptr<GNet::ServerPeer> GNet::MultiServerImp::newPeer( EventStateUnbound esu , ServerPeerInfo && peer_info )
266{
267 MultiServer::ServerInfo server_info ;
268 server_info.m_address = address() ; // GNet::Server::address()
269 return m_ms.doNewPeer( esu , std::move(peer_info) , server_info ) ;
270}
271
272// ==
273
274GNet::MultiServer::ServerInfo::ServerInfo() :
275 m_address(Address::defaultAddress())
276{
277}
278
GNet::Address
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:63
GNet::Descriptor
A class that encapsulates a network socket file descriptor and an associated windows event handle.
Definition: gdescriptor.h:37
GNet::EventStateUnbound
The EventStateUnbound class is used as a device to force factory methods to plumb-in an ExceptionSour...
Definition: geventstate.h:231
GNet::EventState
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
GNet::Listeners
Represents a set of listening inputs which can be file-descriptor, interface or network address.
Definition: glisteners.h:42
GNet::Listeners::defunct
bool defunct() const
Returns true if no inputs and static.
Definition: glisteners.cpp:106
GNet::Listeners::hasEmpties
bool hasEmpties() const
Returns true if some named interfaces have no addresses.
Definition: glisteners.cpp:140
GNet::Listeners::logEmpties
std::string logEmpties() const
Returns a log-line snippet for hasEmpties().
Definition: glisteners.cpp:145
GNet::Listeners::fds
const std::vector< int > & fds() const
Exposes the list of fd inputs.
Definition: glisteners.cpp:150
GNet::Listeners::idle
bool idle() const
Returns true if no inputs but some interfaces might come up.
Definition: glisteners.cpp:111
GNet::Listeners::noUpdates
bool noUpdates() const
Returns true if some inputs are interfaces but GNet::Interfaces is not active().
Definition: glisteners.cpp:116
GNet::Listeners::fixed
const std::vector< Address > & fixed() const
Exposes the list of address inputs.
Definition: glisteners.cpp:155
GNet::Listeners::dynamic
const std::vector< Address > & dynamic() const
Exposes the list of interface addresses.
Definition: glisteners.cpp:160
GNet::Listeners::badName
std::string badName() const
Returns the first invalid input.
Definition: glisteners.cpp:135
GNet::Listeners::hasBad
bool hasBad() const
Returns true if one or more inputs are invalid.
Definition: glisteners.cpp:130
GNet::MultiServer
A server that listens on more than one address using a facade pattern to multiple GNet::Server instan...
Definition: gmultiserver.h:48
GNet::MultiServer::~MultiServer
~MultiServer() override
Destructor.
Definition: gmultiserver.cpp:83
GNet::MultiServer::hasPeers
bool hasPeers() const
Returns true if peers() is not empty.
Definition: gmultiserver.cpp:208
GNet::MultiServer::peers
std::vector< std::weak_ptr< ServerPeer > > peers()
Returns the list of ServerPeer-derived objects.
Definition: gmultiserver.cpp:220
GNet::MultiServer::doNewPeer
std::unique_ptr< ServerPeer > doNewPeer(EventStateUnbound, ServerPeerInfo &&, const ServerInfo &)
Pseudo-private method used by the pimple class.
Definition: gmultiserver.cpp:202
GNet::MultiServer::serverReport
void serverReport(const std::string &group={}) const
Writes to the system log a summary of the underlying server objects and their addresses.
Definition: gmultiserver.cpp:191
GNet::MultiServer::MultiServer
MultiServer(EventState es_listener, const G::StringArray &listen_list, unsigned int port, const std::string &server_type, ServerPeer::Config server_peer_config, Server::Config server_config)
Constructor.
Definition: gmultiserver.cpp:32
GNet::MultiServer::serverCleanup
void serverCleanup()
Should be called from all derived classes' destructors so that peer objects can use their Server obje...
Definition: gmultiserver.cpp:117
GNet::ServerPeerInfo
A move-only structure used in GNet::Server::newPeer() and containing the new socket.
Definition: gserver.h:142
GNet::Server
A network server class which listens on a specific port and spins off ServerPeer objects for each inc...
Definition: gserver.h:51
GNet::Server::newPeer
virtual std::unique_ptr< ServerPeer > newPeer(EventStateUnbound, ServerPeerInfo &&)=0
A factory method which new()s a ServerPeer-derived object.
G::Str::tail
static std::string tail(std::string_view in, std::size_t pos, std::string_view default_={})
Returns the last part of the string after the given position.
Definition: gstr.cpp:1322
gassert.h
gdatetime.h
glisteners.h
glog.h
gmultiserver.h
gstr.h
gtest.h
GNet
Network classes.
Definition: gdef.h:1243
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
GNet::MultiServer::ServerInfo
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:55
GNet::ServerPeer::Config
A configuration structure for GNet::ServerPeer.
Definition: gserverpeer.h:64
GNet::Server::Config
A configuration structure for GNet::Server.
Definition: gserver.h:56