server.hpp 5.96 KB
Newer Older
1 2 3 4 5
/** @addtogroup Network
 *  @{
 */

/**
6 7 8 9 10 11 12
 * The Server is the part doing the accept on a socket and spawning one
 * "remote client" each time, and adding them to a list. Each client will
 * then read and write on their own socket.
 * The server is specified using the type of the remote client. Either this is
 * a RemoteClient and we then do authentication, we handle a database, friends,
 * chat, statistics, history etc. Or this is a GameRemoteClient and we just handle
 * one single game.
13 14
 * @class Server
 */
louiz’'s avatar
louiz’ committed
15

16 17 18
#ifndef __SERVER_HPP__
# define __SERVER_HPP__

19
#include <istream>
20
#include <vector>
21
#include <memory>
22
#include <functional>
louiz’'s avatar
louiz’ committed
23

24 25
#include <signal.h>

26
#include <boost/asio.hpp>
27
#include <boost/asio/steady_timer.hpp>
louiz’'s avatar
louiz’ committed
28
#include <boost/chrono.hpp>
louiz’'s avatar
louiz’ committed
29

louiz’'s avatar
louiz’ committed
30
#include <logging/logging.hpp>
31
#include <network/tcp_socket.hpp>
32
#include <network/message.hpp>
33
#include <utils/time.hpp>
louiz’'s avatar
louiz’ committed
34

35
template <class T>
36
class Server: public TCPSocket
louiz’'s avatar
louiz’ committed
37 38
{
public:
39 40 41 42
  /**
   * Create the server instance. Use run() to start its loop.
   * @param port The port on which the servers accepts new connections.
   */
43
  Server(short port):
44
    TCPSocket(),
45
    port(port),
46
    timeout(IoService::get()),
47 48
    acceptor(IoService::get(), boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
    accepting_client(nullptr)
49 50
  {
  }
louiz’'s avatar
louiz’ committed
51
  virtual ~Server()
52 53 54
  {
    log_debug("closing socket");
  }
55
  /**
56
   * Starts the server
57
   */
58
  void start()
59 60
  {
    this->accept();
61
  }
62

63
  /**
64 65 66
   * Checks for network or timed events readiness.
   * The timeout argument makes this call block for that amount
   * of milliseconds.
67
   */
68
  void poll(long t)
69
  {
70
    if (t == 0)
71
      {
72
        while (IoService::get().poll())
73
          ;
74
        return ;
75
      }
76

77
    if (this->timeout.expires_from_now(boost::asio::steady_timer::duration(t)) == 0)
78 79 80 81
      // The last run_one() call returned because the timeout expired, so
      // we reinstall it. If that's not the case
      // (something actually happened on the socket)
      // we just need to reset the time of expiration, but not reinstall it.
louiz’'s avatar
louiz’ committed
82
      this->timeout.async_wait([](const boost::system::error_code&){});
83 84
    // Wait for one event to happen (either a timeout or something
    // on the socket).
85 86
    IoService::get().run_one();
    while (IoService::get().poll() != 0)
87
      ; // Execute all other available handlers, if any
88
  }
89 90 91 92
  /**
   * To be called by the a RemoteClient instance, to delete itself from
   * the RemoteClient list.
   */
93 94
  void remove_client(T* client)
  {
95
    this->on_client_left(client);
96 97 98 99 100 101 102 103

    auto it = std::find_if(this->clients.begin(), this->clients.end(),
                        [client](const std::unique_ptr<T>& c) -> bool
                        {
                          return c.get() == client;
                        });
    assert(it != this->clients.end());
    this->clients.erase(it);
104
  }
105 106 107 108
  /**
   * Search for a connected client with this login
   * @return RemoteClient* can be NULL if nothing is found
   */
109 110
  T* find_client_by_login(const std::string& login)
  {
111 112 113
    for (const auto& client: this->clients)
      if (client->get_user() && client->get_user()->get("login") == login)
	return client.get();
louiz’'s avatar
louiz’ committed
114
    return nullptr;
115
  }
louiz’'s avatar
louiz’ committed
116

117 118 119
  /**
   * Called after a client just connected to us.
   */
120
  virtual void on_new_client(T*)
121 122 123 124 125 126 127
  {
  }
  /**
   * Called after the client has been disconnected.
   * Must NOT try to send anything to it, and should not assume
   * it's still in the clients' list.
   */
128
  virtual void on_client_left(T*)
129 130 131 132
  {
  }

  /**
133
   * Sends a message to the list of the given remote clients (using their id).
134
   */
135
  void send_to_list_of_clients(Message* message,
136 137
			       std::vector<unsigned long int> ids)
  {
138 139
    for (const auto& id: ids)
      this->send_to_client(new Message(*message), id);
140
    // delete the message ourself, because we made a copy for each
141
    // client, and the original is still there and will not be deleted
142
    // by a client sending it.
143
    delete message;
144 145 146
  }

  /**
147
   * Sends a message to all clients.
148
   */
149
  void send_to_all_clients(Message* message)
150
  {
151 152
    for (const auto& client: this->clients)
      client->send(new Message(*message));
153
    delete message;
154 155 156
  }

  /**
157
   * Sends a message to a client specified by its id.
158
   */
159
  void send_to_client(Message* message, unsigned long int id)
160
  {
161 162 163 164 165 166
    for (const auto& client: this->clients)
      if (client->number == id)
        {
          client->send(message);
          return ;
        }
167
    // The message MUST be sent by now. If the corresponding client
168 169 170 171 172
    // was not in the list, something was wrong before calling this
    // function.
    assert(false);
  }

173 174 175 176 177
  bool is_started() const
  {
    return this->started;
  }

louiz’'s avatar
louiz’ committed
178
private:
179 180
  void install_accept_handler(void)
  {
181
    this->accepting_client = std::make_unique<T>();
182

183
    this->acceptor.async_accept(this->accepting_client->get_socket(),
184
                                std::bind(&Server<T>::handle_accept,
185
                                          this, std::placeholders::_1));
186 187
  }

188
  void handle_accept(const boost::system::error_code& error)
189 190 191
  {
    if (error)
      {
louiz’'s avatar
louiz’ committed
192
	log_error("handle_accept failed: " << error);
193 194
	exit(1);
      }
195 196 197 198 199
    auto new_client = this->accepting_client.get();
    this->clients.push_back(std::move(this->accepting_client));
    this->on_new_client(new_client);
    new_client->start();

200
    this->install_accept_handler();
201 202 203 204 205
  }

  void accept(void)
  {
    boost::system::error_code error;
206
    error = this->acceptor.listen(512, error);
207 208 209 210 211 212 213
    if (error)
      {
	log_error("Error on listen() [" << error << "]. Exiting.");
	return;
      }
    this->install_accept_handler();
  }
louiz’'s avatar
louiz’ committed
214

215 216 217
protected:
  std::vector<std::unique_ptr<T>> clients;
private:
218
  const short port;
219
  boost::asio::steady_timer timeout;
220
  boost::asio::ip::tcp::acceptor acceptor;
221 222 223 224 225 226
  /**
   * This client is created BEFORE we accept a new connection. When a new
   * client is accepted, the new socket is inserted in it, and we can then
   * insert that client in the clients vector.
   */
  std::unique_ptr<T> accepting_client;
louiz’'s avatar
louiz’ committed
227 228 229
};

#endif /*__SERVER_HPP__ */
230
/**#@}*/