E-MailRelay
gmultiserver.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "gdatetime.h"
24#include "gstr.h"
25#include "gtest.h"
26#include "glog.h"
27#include "gassert.h"
28#include <list>
29#include <algorithm>
30
31GNet::MultiServer::MultiServer( ExceptionSink es , const G::StringArray & interfaces , unsigned int port ,
32 const std::string & server_type , ServerPeerConfig server_peer_config , ServerConfig server_config ) :
33 m_es(es) ,
34 m_interfaces(interfaces) ,
35 m_port(port) ,
36 m_server_type(server_type) ,
37 m_server_peer_config(server_peer_config) ,
38 m_server_config(server_config) ,
39 m_if(es,*this) ,
40 m_interface_event_timer(*this,&MultiServer::onInterfaceEventTimeout,es)
41{
42 // tidy the interface list
43 std::sort( m_interfaces.begin() , m_interfaces.end() ) ;
44 m_interfaces.erase( std::unique(m_interfaces.begin(),m_interfaces.end()) , m_interfaces.end() ) ;
45
46 // build a listening address list from explicit addresses and/or interface names
47 G::StringArray used_names ; // interface names having one or more addresses
48 G::StringArray empty_names ; // interface names having no addresses
49 G::StringArray bad_names ; // non-address non-interface names
50 AddressList address_list = addresses( port , used_names , empty_names , bad_names ) ;
51
52 // fail if any bad names
53 if( !bad_names.empty() )
54 throw InvalidName( bad_names.at(0) ) ;
55
56 // fail if no addresses and no prospect of getting any
57 if( address_list.empty() && ( empty_names.empty() || !Interfaces::active() ) )
58 throw NoListeningAddresses() ;
59
60 // warn if no addresses from one or more interface names
61 if( !empty_names.empty() )
62 {
63 if( !address_list.empty() )
64 G_WARNING( "GNet::MultiServer::ctor: no addresses bound to named network interface"
65 << (empty_names.size()==1U?"":"s")
66 << " \"" << G::Str::join("\", \"",empty_names) << "\"" ) ;
67 else if( Interfaces::active() )
68 G_WARNING( "GNet::MultiServer::ctor: no listening addresses: waiting for interface"
69 << (empty_names.size()>1U?"s ":" ")
70 << "[" << G::Str::join("] [",empty_names) << "]" ) ;
71 }
72
73 // bind the listening addresses, etc
74 init( address_list ) ;
75
76 // warn if we got addresses from an interface name but won't get dynamic updates
77 if( !used_names.empty() && !Interfaces::active() )
78 G_WARNING_ONCE( "GNet::MultiServer::ctor: named network interfaces are not monitored for updates" ) ;
79}
80
82{
83 serverCleanup() ;
84}
85
87{
88 for( auto & server : m_server_list )
89 {
90 server->cleanup() ;
91 }
92}
93
94std::vector<GNet::Address> GNet::MultiServer::addresses( unsigned int port ) const
95{
96 AddressList result ;
97 G::StringArray empty_names ;
98 G::StringArray used_names ;
99 G::StringArray bad_names ;
100 return addresses( port , used_names , empty_names , bad_names ) ;
101}
102
103std::vector<GNet::Address> GNet::MultiServer::addresses( unsigned int port , G::StringArray & used_names ,
104 G::StringArray & empty_names , G::StringArray & bad_names ) const
105{
106 AddressList result ;
107 if( m_interfaces.empty() )
108 {
109 if( Address::supports(Address::Family::ipv4) )
110 result.push_back( Address(Address::Family::ipv4,port) ) ;
111 if( Address::supports(Address::Family::ipv6) && StreamSocket::supports(Address::Family::ipv6) )
112 result.push_back( Address(Address::Family::ipv6,port) ) ;
113 }
114 else
115 {
116 result = m_if.addresses( m_interfaces , port , used_names , empty_names , bad_names ) ;
117 }
118 return result ;
119}
120
121void GNet::MultiServer::init( const AddressList & address_list )
122{
123 for( const auto & address : address_list )
124 {
125 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es , address , m_server_peer_config , m_server_config ) ) ;
126 }
127}
128
129void GNet::MultiServer::onInterfaceEvent( const std::string & /*description*/ )
130{
131 // notifications can be periodic and/or bursty, so minimal logging here
132 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: network configuration change event" ) ;
133
134 m_if.load() ;
135 m_interface_event_timer.startTimer( 1U , 500000U ) ; // maybe increase for fewer bind warnings
136}
137
138bool GNet::MultiServer::match( const Address & interface_address , const Address & server_address )
139{
140 // both addresses should have a well-defined scope-id, so include
141 // scope-ids in the match -- this allows for multiple interfaces
142 // to have the same link-local address
143 //
144 return interface_address.same( server_address , interface_address.scopeId() && server_address.scopeId() ) ;
145}
146
147void GNet::MultiServer::onInterfaceEventTimeout()
148{
149 AddressList address_list = addresses( m_port ) ;
150
151 // delete old
152 for( auto server_ptr_p = m_server_list.begin() ; server_ptr_p != m_server_list.end() ; )
153 {
154 Address server_address = (*server_ptr_p)->address() ;
155 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: server: "
156 << displayString(server_address) ) ;
157
158 auto address_match_p = std::find_if( address_list.begin() , address_list.end() ,
159 [&](Address & a){return match(a,server_address);} ) ;
160
161 if( address_match_p == address_list.end() )
162 {
163 G_LOG_S( "GNet::MultiServer::onInterfaceEvent: deleting " << m_server_type
164 << " server on " << displayString(server_address) ) ;
165 server_ptr_p = m_server_list.erase( server_ptr_p ) ;
166 }
167 else
168 {
169 ++server_ptr_p ;
170 }
171 }
172
173 // create new
174 for( const auto & address : address_list )
175 {
176 G_DEBUG( "GNet::MultiServer::onInterfaceEvent: address: " << displayString(address) ) ;
177 if( !gotServerFor(address) )
178 {
179 try
180 {
181 m_server_list.emplace_back( std::make_unique<MultiServerImp>( *this , m_es , address , m_server_peer_config , m_server_config ) ) ;
182 G_LOG_S( "GNet::MultiServer::onInterfaceEvent: new " << m_server_type
183 << " server on " << displayString(address) ) ;
184 }
185 catch( Socket::SocketBindError & e )
186 {
187 // (can fail here if notified too soon, but succeeds later)
188 G_LOG( "GNet::MultiServer::onInterfaceEvent: failed to bind " << displayString(address)
189 << " for new " << m_server_type << " server:"
190 << G::Str::tail(e.what(),std::string(e.what()).rfind(':')) ) ;
191 }
192 }
193 }
194}
195
196bool GNet::MultiServer::gotServerFor( const Address & interface_address ) const
197{
198 return std::any_of( m_server_list.begin() , m_server_list.end() ,
199 [&interface_address](const ServerPtr &ptr){return match(interface_address,ptr->address());} ) ;
200}
201
202bool GNet::MultiServer::canBind( const AddressList & address_list , bool do_throw )
203{
204 return std::all_of( address_list.begin() , address_list.end() ,
205 [do_throw](const Address & a){return Server::canBind(a,do_throw);} ) ;
206}
207
208std::string GNet::MultiServer::displayString( const Address & address )
209{
210 return address.displayString( true ) ;
211}
212
214{
215 for( const auto & server : m_server_list )
216 {
217 G_LOG_S( "GNet::MultiServer: " << m_server_type << " server on " << displayString(server->address()) ) ;
218 }
219}
220
221std::unique_ptr<GNet::ServerPeer> GNet::MultiServer::doNewPeer( ExceptionSinkUnbound esu ,
222 const ServerPeerInfo & pi , const ServerInfo & si )
223{
224 return newPeer( esu , pi , si ) ;
225}
226
228{
229 for( const auto & server : m_server_list )
230 {
231 if( server->hasPeers() )
232 return true ;
233 }
234 return false ;
235}
236
237std::vector<std::weak_ptr<GNet::ServerPeer> > GNet::MultiServer::peers()
238{
239 using List = std::vector<std::weak_ptr<ServerPeer>> ;
240 List result ;
241 for( auto & server : m_server_list )
242 {
243 List list = server->peers() ;
244 result.insert( result.end() , list.begin() , list.end() ) ;
245 }
246 return result ;
247}
248
249// ==
250
252 ServerPeerConfig server_peer_config , ServerConfig server_config ) :
253 GNet::Server(es,address,server_peer_config,server_config) ,
254 m_ms(ms) ,
255 m_address(address)
256{
257}
258
260= default;
261
263{
264 serverCleanup() ;
265}
266
267std::unique_ptr<GNet::ServerPeer> GNet::MultiServerImp::newPeer( ExceptionSinkUnbound esu , ServerPeerInfo peer_info )
268{
269 MultiServer::ServerInfo server_info ;
270 server_info.m_address = address() ; // GNet::Server::address()
271 return m_ms.doNewPeer( esu , peer_info , server_info ) ;
272}
273
274// ==
275
276GNet::MultiServer::ServerInfo::ServerInfo() :
277 m_address(Address::defaultAddress())
278{
279}
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:53
static bool supports(Family)
Returns true if the implementation supports the given address family.
Definition: gaddress.cpp:33
std::string displayString(bool with_scope_id=false) const
Returns a string which represents the transport address.
Definition: gaddress.cpp:375
A potential ExceptionSink that is realised by bind()ing an exception source pointer.
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
static bool active()
Returns true if the implementation can raise InterfacesHandler events.
std::unique_ptr< ServerPeer > newPeer(ExceptionSinkUnbound, ServerPeerInfo) final
Called by the base class to create a new ServerPeer.
~MultiServerImp() override
Destructor.
MultiServerImp(MultiServer &, ExceptionSink, const Address &, ServerPeerConfig, ServerConfig)
Constructor.
void cleanup()
Calls GNet::Server::serverCleanup().
A server that listens on more than one address using a facade pattern to multiple GNet::Server instan...
Definition: gmultiserver.h:45
~MultiServer() override
Destructor.
static bool canBind(const AddressList &listening_address_list, bool do_throw)
Checks that all the specified addresses can be bound.
bool hasPeers() const
Returns true if peers() is not empty.
std::vector< std::weak_ptr< ServerPeer > > peers()
Returns the list of ServerPeer-derived objects.
void serverReport() const
Writes to the system log a summary of the underlying server objects and their addresses.
MultiServer(ExceptionSink listener_exception_sink, const G::StringArray &addresses, unsigned int port, const std::string &server_type, ServerPeerConfig server_peer_config, ServerConfig server_config)
Constructor.
void serverCleanup()
Should be called from all derived classes' destructors so that peer objects can use their Server obje...
std::unique_ptr< ServerPeer > doNewPeer(ExceptionSinkUnbound, const ServerPeerInfo &, const ServerInfo &)
Pseudo-private method used by the pimple class.
A structure that GNet::Server uses to configure its ServerPeer objects.
Definition: gserverpeer.h:51
A structure used in GNet::Server::newPeer().
Definition: gserver.h:142
A network server class which listens on a specific port and spins off ServerPeer objects for each inc...
Definition: gserver.h:62
static bool supports(Address::Family)
Returns true if stream sockets can be created with the given the address family.
Definition: gsocket.cpp:424
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
static std::string tail(const std::string &in, std::size_t pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1287
Network classes.
Definition: gdef.h:1115
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
A structure used in GNet::MultiServer::newPeer().
Definition: gmultiserver.h:52
Address m_address
The server address that the peer connected to.
Definition: gmultiserver.h:54
A configuration structure for GNet::Server.
Definition: gserver.h:50