3.2. Monitoring¶
The goal is to create a C++ console application which connects to multiple turrets and prints the recieved notifications.
Like the Logging in/out example, we can also start this project from the connect example.
3.2.1. Creating the new project¶
The steps for creating the new project are the same as the logging in/out naming this one for example monitoring.
3.2.2. Making the monitoring server¶
To create a server we will need a apache::thrift::TProcessor. Thrift generates one from the interface definition file (TurretNotificationServiceProcessor), but to create it we will need a realization of the TurretApi.TurretNotificationService interface.
There is a generated TurretNotificationServiceNull class which is exactly that: a realization of the interface which does nothing, and that is suitable for now.
#include <TurretRequestService.h>
#include <TurretNotificationService.h>
namespace turretapi = osxpert::turret::api;
namespace osxthrift = osxpert::thrift;
int main(int argc, char *argv[])
{
std::string serverAddress = "192.168.9.10";
auto service = std::make_shared<turretapi::TurretNotificationServiceNull>();
auto processor = std::make_shared<turretapi::TurretNotificationServiceProcessor>(service);
osxthrift::ServerOptions serverOptions;
serverOptions.ListenAddress = "0.0.0.0";
serverOptions.ListenPort = 10052;
osxthrift::Server server(processor, serverOptions);
server.listen();
Now the server is up and running but there is no one connecting to it.
3.2.3. Connecting to Turrets and requesting notifications¶
Since we started from the connect example, a simple connection is already there. We just need to generalize it for optionally multiple turrets and add the notification request.
Collecting a few turret IP addresses into a vector
std::vector<std::string> ips{"192.168.8.69", "192.168.8.70", "192.168.8.71"};
And then iterating over these, connecting to each
for (std::string ip : ips)
{
osxthrift::ClientOptions options{ip, 9007};
std::shared_ptr<turretapi::TurretRequestServiceClient> client =
osxthrift::makeClient<turretapi::TurretRequestServiceClient>(options);
try
{
osxthrift::ClientConnection<turretapi::TurretRequestServiceClient> connection =
osxthrift::connect(client);
std::cout << "Connection successful to "
<< options.Host << ":" << options.Port << std::endl;
}
catch (const std::exception &ex)
{
std::cout << ex.what() << std::endl;
}
}
Now when the temporary connection objects go out of scope, they get deleted and the destructor closes the connection. We could get around this by saving them into container so they do not get destructed, but for this example this is fine. We just have to request the notifications before disconnecting.
try
{
osxthrift::ClientConnection<turretapi::TurretRequestServiceClient> connection =
osxthrift::connect(client);
std::cout << "Connection successful to "
<< options.Host << ":" << options.Port << std::endl;
turretapi::SubscriberInfo subscriberInfo;
subscriberInfo.ipAddress = serverAddress;
subscriberInfo.port = serverOptions.ListenPort;
subscriberInfo.turretName = ip;
connection->requestNotifications(subscriberInfo);
}
3.2.4. Adding the monitoring to the server¶
Now the server is up and running with turrets connected to it. What is left is to handle the notifications which are being sent.
For that we will need to define the methods in TurretApi.TurretNotificationService.
...
namespace turretapi = osxpert::turret::api;
...
class NotificationService : public turretapi::TurretNotificationServiceIf
{
public:
void onLineStatesChanged(const std::string &turretName,
const turretapi::LineStatesData &states) override {}
void onCallDataChanged(const std::string &turretName,
const turretapi::LineName &lineName,
const std::string &partyNumber,
const std::string &partyName,
const std::string &contactName) override {}
void onLoginStateChanged(const std::string &turretName,
const turretapi::TurretState::type state) override {}
void onLoginResponse(const std::string &turretName,
const turretapi::TurretErrorCode errorCode,
const std::string &errorDescription) override {}
void onTopOfCallQueueChanged(const std::string &turretName,
const turretapi::LineName &lineName) override {}
void onRingTransferStateChanged(const std::string &turretName,
const turretapi::RingTransferId &id,
const turretapi::RingTransferState::type state) override {}
void onRingTransferStateChangeError(const std::string &turretName,
const turretapi::RingTransferId &id,
const turretapi::TurretErrorCode errorCode,
const std::string &errorDescription) override {}
void onRingTransferSequenceStateChanged(const std::string &turretName,
const turretapi::RingTransferId &id,
const turretapi::RingTransferSequenceState::type state) override {}
void onRingTransferSequenceStateChangeError(const std::string &turretName,
const turretapi::RingTransferId &id,
const turretapi::TurretErrorCode errorCode,
const std::string &errorDescription) override {}
void onInterfaceActionStateChanged(const std::string &turretName,
const std::string &apiName,
const turretapi::InterfaceActionState &state) override {}
void onInterfaceActionKeyChanged(const std::string &turretName,
const std::string &apiName,
const turretapi::InterfaceActionKeyState::type keyState) override {}
};
...
This might seem like a lot, but some IDEs have a function like “implement virtual methods” which can generate something like this.
This class is functionally the same as TurretNotificationServiceNull but now we can expand it - after we tell the service processor to use ours of course.
int main(int argc, char *argv[])
{
std::string serverAddress = "192.168.9.10";
auto service = std::make_shared<NotificationService>();
auto processor = std::make_shared<turretapi::TurretNotificationServiceProcessor>(service);
Since we only want to print to the console, the body of the functions are fairly simple. For example:
void onLineStatesChanged(const std::string &turretName, const osxpert::turret::api::LineStatesData &states) override
{
std::cout << turretName << ": new line states for line: " << states.lineName << " | states: ";
for (const auto &state : states.basicLineStates)
std::cout << state << ", ";
std::cout << std::endl;
}
void onCallDataChanged(const std::string &turretName, const osxpert::turret::api::LineName &lineName, const std::string &partyNumber, const std::string &partyName, const std::string &contactName) override
{
std::cout << turretName << ": conversing with " << partyNumber;
if (!partyName.empty())
std::cout << " " << partyName;
if (!contactName.empty())
std::cout << " " << contactName;
std::cout << " on " << lineName << std::endl;
}
void onLoginStateChanged(const std::string &turretName, const osxpert::turret::api::TurretState::type state) override
{
std::cout << turretName << ": new login state: " << state << std::endl;
}
...
3.2.5. Trying out our application¶
When we try out our monitoring application what we notice is that we only recieve notifications from the first turret we could connect to. This is because the underlying apache::thrift::server::ThreadPoolServer is set up to only spawn one worker thread, so we can only accept one connection. Now we can solve this by modifying the osxpert::thrift::Server to accept multiple or we can create the server ourselves.
Server::Server(std::shared_ptr<apache::thrift::TProcessor> processor, const ServerOptions& options)
: ThreadFactory(std::make_shared<apache::thrift::concurrency::StdThreadFactory>()),
ThreadManager(apache::thrift::concurrency::ThreadManager::newSimpleThreadManager(1))
{
ThreadManager->threadFactory(ThreadFactory);
...
3.2.6. Fixing the server¶
The Thrift C++ tutorial has an example for the usage of the different server types. The TThreadedServer seems to be good enough for our application. Bearing in mind that we need to use TFramedTransport and TBinaryProtocol.
#define NOMINMAX
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/protocol/TBinaryProtocol.h>
serverOptions.ListenAddress = "0.0.0.0";
serverOptions.ListenPort = 10052;
apache::thrift::server::TThreadedServer server{
processor,
std::make_shared<apache::thrift::transport::TServerSocket>(serverOptions.ListenPort),
std::make_shared<apache::thrift::transport::TFramedTransportFactory>(),
std::make_shared<apache::thrift::protocol::TBinaryProtocolFactory>()};
Note
The #define NOMINMAX line is there because on my environment winapi has a macro for min and max and it causes a compilation error for the similarly named members of std::numeric_limits which some thrift header uses.
3.2.7. Starting the server¶
An other thing we lose is the automatic thread creation for starting the server because calling TThreadedServer::serve blocks execution until the server is stopped. And if we start it after we connect to the turrets they will not be able to connect to us.
Creating a separate thread for the server we can try out the application for real this time.
...
int main(int argc, char *argv[])
{
...
apache::thrift::server::TThreadedServer server{
processor,
std::make_shared<apache::thrift::transport::TServerSocket>(serverOptions.ListenPort),
std::make_shared<apache::thrift::transport::TFramedTransportFactory>(),
std::make_shared<apache::thrift::protocol::TBinaryProtocolFactory>()};
std::cout << "Starting the server thread..." << std::endl;
std::thread serverThread {
[&server] {
try
{
server.serve();
}
catch (const std::exception &ex)
{
std::cout << ex.what() << std::endl;
}
}};
std::vector<std::string> ips{"192.168.8.69", "192.168.8.70", "192.168.8.71"};
...
std::cin.ignore();
std::cout << "Stopping server..." << std::endl;
server.stop();
serverThread.join();
return 0;
}
Starting the server thread...
Connection successful to 192.168.8.69:9007
Thrift: Fri Jan 17 19:07:42 2020 TSocket::open() connect() <Host: 192.168.8.70 Port: 9007>errno = 10061
connect() failed: errno = 10061
Connection successful to 192.168.8.71:9007
192.168.8.69: new login state: Login
192.168.8.69: new line states for line: 5643 | states: Preseized,
192.168.8.69: conversing with 1239 SIPeti on 5643
192.168.8.69: conversing with 1239 SIPeti on 5643
192.168.8.69: new line states for line: 5643 | states: Conversation,
192.168.8.69: conversing with 3619001239 Whitaker Harvey on 5643
192.168.8.69: conversing with 3619001239 Whitaker Harvey on 5643
192.168.8.69: new line states for line: 5643 | states: Connected, Conversation,
192.168.8.71: new login state: Login
192.168.8.71: new line states for line: 5643 | states: Connected, ConversationByAnother,
192.168.8.71: conversing with 3619001239 Whitaker Harvey Peti SIP on 5643
192.168.8.71: conversing with 3619001239 Whitaker Harvey Peti SIP on 5643
192.168.8.69: new line states for line: 5644 | states: PreseizedByAnother,
192.168.8.71: new line states for line: 5644 | states: Preseized,
192.168.8.69: new line states for line: 5644 | states: Idle,
192.168.8.71: new line states for line: 5644 | states: Idle,
192.168.8.69: new line states for line: 5644 | states: PreseizedByAnother,
192.168.8.71: new line states for line: 5644 | states: Preseized,
192.168.8.69: conversing with 1259 on 5644
192.168.8.69: conversing with 1259 on 5644
192.168.8.69: new line states for line: 5644 | states: ConversationByAnother,
192.168.8.71: conversing with 1259 on 5644
192.168.8.71: conversing with 1259 on 5644
192.168.8.71: new line states for line: 5644 | states: Conversation,
192.168.8.69: conversing with 3619001259 Lamm Harold on 5644
192.168.8.69: conversing with 3619001259 Lamm Harold on 5644
192.168.8.71: conversing with 3619001259 Lamm Harold on 5644
192.168.8.71: conversing with 3619001259 Lamm Harold on 5644
192.168.8.69: new line states for line: 5644 | states: Connected, ConversationByAnother,
192.168.8.71: new line states for line: 5644 | states: Connected, Conversation,
192.168.8.69: new line states for line: 5643 | states: Idle,
192.168.8.71: new line states for line: 5643 | states: Idle,
192.168.8.69: new login state: Logout
192.168.8.69: new line states for line: 3619001280 | states: Inactive,
192.168.8.69: new line states for line: 3619005642 | states: Inactive,
192.168.8.69: new line states for line: 5643 | states: Inactive,
192.168.8.69: new line states for line: 5644 | states: Inactive,
192.168.8.71: new line states for line: 5644 | states: Connected,
192.168.8.71: new line states for line: 5644 | states: Idle,
192.168.8.71: new login state: Logout
192.168.8.71: new line states for line: 3619001280 | states: Inactive,
192.168.8.71: new line states for line: 3619001285 | states: Inactive,
192.168.8.71: new line states for line: 3619001288 | states: Inactive,
192.168.8.71: new line states for line: 3619001289 | states: Inactive,
192.168.8.71: new line states for line: 3619001286 | states: Inactive,
192.168.8.71: new line states for line: 36190012873619001287 | states: Inactive,
192.168.8.71: new line states for line: 3619005642 | states: Inactive,
192.168.8.71: new line states for line: 5643 | states: Inactive,
192.168.8.71: new line states for line: 5644 | states: Inactive,
192.168.8.71: new line states for line: 5645 | states: Inactive,
Stopping server...