URY playd
C++ minimalist audio player
IoCore Class Reference

The IO core, which services input, routes responses, and executes the Player update routine periodically. More...

#include <io.hpp>

+ Inheritance diagram for IoCore:
+ Collaboration diagram for IoCore:

Public Member Functions

 IoCore (Player &player)
 Constructs an IoCore. More...
 
 IoCore (const IoCore &)=delete
 Deleted copy constructor.
 
IoCoreoperator= (const IoCore &)=delete
 Deleted copy-assignment.
 
void Run (const std::string &host, const std::string &port)
 Runs the reactor. More...
 
void Accept (uv_stream_t *server)
 Accepts a new connection. More...
 
void Remove (size_t id)
 Removes a connection. More...
 
void UpdatePlayer ()
 Performs a player update cycle. More...
 
void Respond (size_t id, const Response &response) const override
 Outputs a response. More...
 
void Shutdown ()
 Shuts down the IoCore by terminating all IO loop tasks.
 
- Public Member Functions inherited from ResponseSink
virtual ~ResponseSink ()=default
 Empty virtual destructor for ResponseSink.
 

Private Member Functions

void InitAcceptor (const std::string &address, const std::string &port)
 Initialises a TCP acceptor on the given address and port. More...
 
void InitUpdateTimer ()
 Sets up a periodic timer to run the playd update loop.
 
void InitSignals ()
 Initialises playd's signal handling. More...
 
size_t NextConnectionID ()
 Acquires the next available connection ID. More...
 
void ExpandPool ()
 Adds a new connection slot to the connection pool. More...
 
void Broadcast (const Response &response) const
 Sends the given response to all connections. More...
 
void Unicast (size_t id, const Response &response) const
 Sends the given response to the identified connection. More...
 

Private Attributes

uv_loop_t * loop
 The loop this IoCore is using.
 
uv_signal_t sigint
 The libuv handle for the Ctrl-C signal.
 
uv_tcp_t server
 The libuv handle for the TCP server.
 
uv_timer_t updater
 The libuv handle for the update timer.
 
Playerplayer
 The player.
 
std::vector< std::shared_ptr< Connection > > pool
 The set of connections inside this IoCore.
 
std::vector< size_t > free_list
 A list of free 1-indexed slots inside pool. More...
 

Static Private Attributes

static const uint16_t PLAYER_UPDATE_PERIOD = 5
 The period between player updates.
 

Detailed Description

The IO core, which services input, routes responses, and executes the Player update routine periodically.

The IO core also maintains a pool of connections which can be sent responses via their IDs inside the pool. It ensures that each connection is given an ID that is unique up until the removal of said connection.

Definition at line 39 of file io.hpp.

Constructor & Destructor Documentation

§ IoCore()

IoCore::IoCore ( Player player)
explicit

Constructs an IoCore.

Parameters
playerThe player to which update requests, commands, and new connection state dump requests shall be sent.

Definition at line 170 of file io.cpp.

170  : loop(nullptr), player(player)
171 {
172 }
Player & player
The player.
Definition: io.hpp:111
uv_loop_t * loop
The loop this IoCore is using.
Definition: io.hpp:106

Member Function Documentation

§ Accept()

void IoCore::Accept ( uv_stream_t *  server)

Accepts a new connection.

This accepts the connection, and adds it to this IoCore's connection pool.

This should be called with a server that has just received a new connection.

Parameters
serverPointer to the libuv server accepting connections.

Definition at line 190 of file io.cpp.

References Response::IAMA, loop, MSG_OHAI_BIFROST, MSG_OHAI_PLAYD, NextConnectionID(), Response::NOREQUEST, Response::OHAI, player, pool, Respond(), Response::Success(), UvAlloc(), UvCloseCallback(), and UvReadCallback().

191 {
192  assert(server != nullptr);
193  assert(this->loop != nullptr);
194 
195  auto client = new uv_tcp_t();
196  uv_tcp_init(this->loop, client);
197 
198  // libuv does the 'nonzero is error' thing here
199  if (uv_accept(server, (uv_stream_t *)client)) {
200  uv_close((uv_handle_t *)client, UvCloseCallback);
201  return;
202  }
203 
204  auto id = this->NextConnectionID();
205  auto conn = std::make_shared<Connection>(*this, client, this->player, id);
206  client->data = static_cast<void *>(conn.get());
207  this->pool[id - 1] = std::move(conn);
208 
209  // Begin initial responses
211  .AddArg(std::to_string(id))
212  .AddArg(MSG_OHAI_BIFROST)
213  .AddArg(MSG_OHAI_PLAYD));
215  .AddArg("player/file"));
216  this->player.Dump(id, Response::NOREQUEST);
218  // End initial responses
219 
220  uv_read_start((uv_stream_t *)client, UvAlloc, UvReadCallback);
221 }
size_t NextConnectionID()
Acquires the next available connection ID.
Definition: io.cpp:223
A response.
Definition: response.hpp:23
void UvAlloc(uv_handle_t *, size_t suggested_size, uv_buf_t *buf)
The function used to allocate and initialise buffers for client reading.
Definition: io.cpp:51
static const std::string NOREQUEST
The tag for unsolicited messages (not from responses).
Definition: response.hpp:27
static Response Success(const std::string &tag)
Shortcut for constructing a final response to a successful request.
Definition: response.cpp:49
Player & player
The player.
Definition: io.hpp:111
void Respond(size_t id, const Response &response) const override
Outputs a response.
Definition: io.cpp:305
void UvReadCallback(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
The callback fired when some bytes are read from a client connection.
Definition: io.cpp:65
uv_tcp_t server
The libuv handle for the TCP server.
Definition: io.hpp:108
void UvCloseCallback(uv_handle_t *handle)
The callback fired when a client connection closes.
Definition: io.cpp:58
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114
const std::string MSG_OHAI_BIFROST
The protocol name and version.
Definition: messages.h:25
Server sending its role.
uv_loop_t * loop
The loop this IoCore is using.
Definition: io.hpp:106
Server starting up.
const std::string MSG_OHAI_PLAYD
The playd name and version.
Definition: messages.h:22

§ Broadcast()

void IoCore::Broadcast ( const Response response) const
private

Sends the given response to all connections.

Parameters
responseThe response to broadcast.

Definition at line 316 of file io.cpp.

References Response::Pack(), and pool.

Referenced by Respond().

317 {
318  Debug() << "broadcast:" << response.Pack() << std::endl;
319 
320  // Copy the connection by value, so that there's at least one
321  // active reference to it throughout.
322  for (const auto c : this->pool) {
323  if (c) c->Respond(response);
324  }
325 }
std::string Pack() const
Packs the Response, converting it to a BAPS3 protocol message.
Definition: response.cpp:44
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114
Class for telling the human what playd is doing.
Definition: errors.hpp:133

§ ExpandPool()

void IoCore::ExpandPool ( )
private

Adds a new connection slot to the connection pool.

This is called by NextConnectionID when the number of currently running connections is larger than the number of existing pool slots, and will eventually fail (when the number of simultaneous connections reaches absurd proportions).

Definition at line 243 of file io.cpp.

References free_list, MSG_TOO_MANY_CONNS, and pool.

Referenced by NextConnectionID().

244 {
245  // If we already have SIZE_MAX-1 simultaneous connections, we bail out.
246  // Since this is at least 65,534, and likely to be 2^32-2 or 2^64-2,
247  // this is incredibly unlikely to happen and probably means someone's
248  // trying to denial-of-service an audio player.
249  //
250  // Why -1? Because slot 0 in the connection pool is reserved for
251  // broadcasts.
252  bool full = this->pool.size() == (SIZE_MAX - 1);
253  if (full) throw InternalError(MSG_TOO_MANY_CONNS);
254 
255  this->pool.emplace_back(nullptr);
256  // This isn't an off-by-one error; slots index from 1.
257  this->free_list.push_back(this->pool.size());
258 }
std::vector< size_t > free_list
A list of free 1-indexed slots inside pool.
Definition: io.hpp:118
const std::string MSG_TOO_MANY_CONNS
Message shown when too many simultaneous connections are launched.
Definition: messages.h:121
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114
An Error signifying that playd has hit an internal snag.
Definition: errors.hpp:60

§ InitAcceptor()

void IoCore::InitAcceptor ( const std::string &  address,
const std::string &  port 
)
private

Initialises a TCP acceptor on the given address and port.

Parameters
addressThe IPv4 address on which the TCP server should listen.
portThe TCP port on which the TCP server should listen.

Definition at line 349 of file io.cpp.

References loop, MSG_IO_CANNOT_ALLOC, server, and UvListenCallback().

Referenced by Run().

350 {
351  assert(this->loop != nullptr);
352 
353  if (uv_tcp_init(this->loop, &this->server)) {
355  }
356  this->server.data = static_cast<void *>(this);
357  assert(this->server.data != nullptr);
358 
359  struct sockaddr_in bind_addr;
360  uv_ip4_addr(address.c_str(), std::stoi(port), &bind_addr);
361  uv_tcp_bind(&this->server, (const sockaddr *)&bind_addr, 0);
362 
363  int r = uv_listen((uv_stream_t *)&this->server, 128, UvListenCallback);
364  if (r) {
365  throw NetError("Could not listen on " + address + ":" + port +
366  " (" + std::string(uv_err_name(r)) + ")");
367  }
368 
369  Debug() << "Listening at" << address << "on" << port << std::endl;
370 }
const std::string MSG_IO_CANNOT_ALLOC
Message shown when allocating an IO object fails.
Definition: messages.h:118
void UvListenCallback(uv_stream_t *server, int status)
The callback fired when a new client connection is acquired by the listener.
Definition: io.cpp:80
uv_tcp_t server
The libuv handle for the TCP server.
Definition: io.hpp:108
A network error.
Definition: errors.hpp:105
Class for telling the human what playd is doing.
Definition: errors.hpp:133
An Error signifying that playd has hit an internal snag.
Definition: errors.hpp:60
uv_loop_t * loop
The loop this IoCore is using.
Definition: io.hpp:106

§ InitSignals()

void IoCore::InitSignals ( )
private

Initialises playd's signal handling.

We trap SIGINT, and the equivalent emulated signal on Windows, to make playd close gracefully when Ctrl-C is sent.

Definition at line 372 of file io.cpp.

References loop, MSG_IO_CANNOT_ALLOC, player, sigint, and UvSigintCallback().

Referenced by Run().

373 {
374  int r = uv_signal_init(this->loop, &this->sigint);
375  if (r) {
376  throw InternalError(MSG_IO_CANNOT_ALLOC + ": " +
377  std::string(uv_err_name(r)));
378  }
379 
380  // We pass the player, not the IoCore.
381  // This is so the SIGINT handler can tell the player to quit,
382  // which then tells us to shutdown
383  this->sigint.data = static_cast<void *>(&this->player);
384  assert(this->sigint.data != nullptr);
385  uv_signal_start(&this->sigint, UvSigintCallback, SIGINT);
386 }
const std::string MSG_IO_CANNOT_ALLOC
Message shown when allocating an IO object fails.
Definition: messages.h:118
void UvSigintCallback(uv_signal_t *handle, int signum)
The callback fired when SIGINT occurs.
Definition: io.cpp:129
uv_signal_t sigint
The libuv handle for the Ctrl-C signal.
Definition: io.hpp:107
Player & player
The player.
Definition: io.hpp:111
An Error signifying that playd has hit an internal snag.
Definition: errors.hpp:60
uv_loop_t * loop
The loop this IoCore is using.
Definition: io.hpp:106

§ NextConnectionID()

size_t IoCore::NextConnectionID ( )
private

Acquires the next available connection ID.

This ID may have been assigned to a connection in the past, but is guaranteed not to match any currently assigned IDs.

Returns
size_t A fresh ID for use.

Definition at line 223 of file io.cpp.

References ExpandPool(), free_list, and pool.

Referenced by Accept().

224 {
225  // We'll want to try and use an existing, empty ID in the connection
226  // pool. If there aren't any (we've exceeded the maximum-so-far number
227  // of simultaneous connections), we expand the pool.
228  if (this->free_list.empty()) this->ExpandPool();
229  assert(!this->free_list.empty());
230 
231  // Acquire some free ID, and ensure that the ID cannot be re-used until
232  // replaced onto the free list by the connection's removal.
233  size_t id = this->free_list.back();
234  this->free_list.pop_back();
235 
236  // client_slot should be at least 1, because of the above.
237  assert(0 < id);
238  assert(id <= this->pool.size());
239 
240  return id;
241 }
std::vector< size_t > free_list
A list of free 1-indexed slots inside pool.
Definition: io.hpp:118
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114
void ExpandPool()
Adds a new connection slot to the connection pool.
Definition: io.cpp:243

§ Remove()

void IoCore::Remove ( size_t  id)

Removes a connection.

As the IoCore owns the Connection, it will be destroyed by this operation.

Parameters
idThe ID of the connection to remove.

Definition at line 260 of file io.cpp.

References free_list, and pool.

Referenced by Connection::Depool().

261 {
262  assert(0 < slot && slot <= this->pool.size());
263 
264  // Don't remove if it's already a nullptr, because we'd end up with the
265  // slot on the free list twice.
266  if (this->pool.at(slot - 1)) {
267  this->pool[slot - 1] = nullptr;
268  this->free_list.push_back(slot);
269  }
270 
271  assert(!this->pool.at(slot - 1));
272 }
std::vector< size_t > free_list
A list of free 1-indexed slots inside pool.
Definition: io.hpp:118
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114

§ Respond()

void IoCore::Respond ( size_t  id,
const Response response 
) const
overridevirtual

Outputs a response.

Parameters
idThe ID of the client of the ResponseSink receiving this response. Use 0 for broadcasts.
responseThe Response to output.

Reimplemented from ResponseSink.

Definition at line 305 of file io.cpp.

References Broadcast(), pool, and Unicast().

Referenced by Accept().

306 {
307  if (this->pool.empty()) return;
308 
309  if (id == 0) {
310  this->Broadcast(response);
311  } else {
312  this->Unicast(id, response);
313  }
314 }
void Broadcast(const Response &response) const
Sends the given response to all connections.
Definition: io.cpp:316
void Unicast(size_t id, const Response &response) const
Sends the given response to the identified connection.
Definition: io.cpp:327
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114

§ Run()

void IoCore::Run ( const std::string &  host,
const std::string &  port 
)

Runs the reactor.

It will block until it terminates.

Parameters
hostThe IP host to which IoCore will bind.
portThe TCP port to which IoCore will bind.
Exceptions
NetErrorThrown if IoCore cannot bind to host or port.

Definition at line 174 of file io.cpp.

References InitAcceptor(), InitSignals(), InitUpdateTimer(), loop, and MSG_IO_CANNOT_ALLOC.

Referenced by main().

175 {
176  this->loop = uv_default_loop();
177  if (this->loop == nullptr) throw InternalError(MSG_IO_CANNOT_ALLOC);
178 
179  this->InitAcceptor(host, port);
180  this->InitSignals();
181  this->InitUpdateTimer();
182 
183  uv_run(this->loop, UV_RUN_DEFAULT);
184 
185  // We presume all of the open handles have been closed in Shutdown().
186  // We need only close the loop.
187  uv_loop_close(this->loop);
188 }
const std::string MSG_IO_CANNOT_ALLOC
Message shown when allocating an IO object fails.
Definition: messages.h:118
void InitSignals()
Initialises playd&#39;s signal handling.
Definition: io.cpp:372
void InitAcceptor(const std::string &address, const std::string &port)
Initialises a TCP acceptor on the given address and port.
Definition: io.cpp:349
void InitUpdateTimer()
Sets up a periodic timer to run the playd update loop.
Definition: io.cpp:338
An Error signifying that playd has hit an internal snag.
Definition: errors.hpp:60
uv_loop_t * loop
The loop this IoCore is using.
Definition: io.hpp:106

§ Unicast()

void IoCore::Unicast ( size_t  id,
const Response response 
) const
private

Sends the given response to the identified connection.

Parameters
idThe ID of the recipient connection.
responseThe response to broadcast.

Definition at line 327 of file io.cpp.

References Response::Pack(), and pool.

Referenced by Respond().

328 {
329  assert(0 < id && id <= this->pool.size());
330 
331  Debug() << "unicast @" << std::to_string(id) << ":" << response.Pack()
332  << std::endl;
333 
334  auto c = this->pool.at(id - 1);
335  if (c) c->Respond(response);
336 }
std::string Pack() const
Packs the Response, converting it to a BAPS3 protocol message.
Definition: response.cpp:44
std::vector< std::shared_ptr< Connection > > pool
The set of connections inside this IoCore.
Definition: io.hpp:114
Class for telling the human what playd is doing.
Definition: errors.hpp:133

§ UpdatePlayer()

void IoCore::UpdatePlayer ( )

Performs a player update cycle.

If the player is closing, IoCore will announce this fact to all current connections, close them, and end the I/O loop.

Definition at line 274 of file io.cpp.

References player, Shutdown(), and Player::Update().

275 {
276  bool running = this->player.Update();
277  if (!running) this->Shutdown();
278 }
bool Update()
Instructs the Player to perform a cycle of work.
Definition: player.cpp:41
Player & player
The player.
Definition: io.hpp:111
void Shutdown()
Shuts down the IoCore by terminating all IO loop tasks.
Definition: io.cpp:280

Member Data Documentation

§ free_list

std::vector<size_t> IoCore::free_list
private

A list of free 1-indexed slots inside pool.

These slots may be re-used instead of creating a new slot.

Definition at line 118 of file io.hpp.

Referenced by ExpandPool(), NextConnectionID(), and Remove().


The documentation for this class was generated from the following files: