├── KATCPClientBase.h └── KATCPClientBase.cpp /KATCPClientBase.h: -------------------------------------------------------------------------------- 1 | #ifndef KATCP_CLIENT_BASE_H 2 | #define KATCP_CLIENT_BASE_H 3 | 4 | //System includes 5 | #include 6 | #include 7 | 8 | //Library includes 9 | #ifndef Q_MOC_RUN //Qt's MOC and Boost have some issues don't let MOC process boost headers 10 | #include 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | //Local includes 17 | #include "../../AVNUtilLibs/Sockets/InterruptibleBlockingSockets/InterruptibleBlockingTCPSocket.h" 18 | 19 | // This KATCP client is implemented manually using a client TCP socket and not the KATCP library. 20 | // This makes portability a bit better especially with Windows. 21 | // This is a base class which provideds the basic socket reading and writing and received text tokenising. 22 | // There is a message send queue with thread safe write access (for adding new messages to send). 23 | // There is a callback interface which can be extended for push-based behaviour to other classes deriving the callback interface. 24 | // Implement the processKATCPMessage function according to requirements. This will typically either involved calling functions 25 | // in the callback interface or storing values to member and providing accessor function (pull-based behavior). 26 | // Note that a vector of callback handler pointers is provided. When deriving the callback interface, the callback handler pointer 27 | // will need to be dynamically cast to the derived callback interface type before calling new derived callback functions as the 28 | // vector stores pointers of the this base class type and not your new derived type. 29 | // The send and receive are handled by seperate threads. This is because KATCP typically responds with a message type identifier 30 | // and a reponse therefore needn't be tied to a initial message for most use cases. 31 | // If this is not desired behavior the thread functions will need to be altered. They are left virtual for this reason. 32 | 33 | class cKATCPClientBase 34 | { 35 | public: 36 | //Generic callback interface class (can be extended with other callback functions) 37 | class cCallbackInterface 38 | { 39 | public: 40 | virtual void connected_callback(bool bConnected, const std::string &strHostAddress, uint16_t u16Port, const std::string &strDescription) = 0; 41 | }; 42 | 43 | //Callback interface specifically for handling only connection / disconnections 44 | class cConnectionCallbackInterface 45 | { 46 | public: 47 | virtual void connected_callback(bool bConnected, const std::string &strHostAddress, uint16_t u16Port, const std::string &strDescription) = 0; 48 | }; 49 | 50 | cKATCPClientBase(); 51 | virtual ~cKATCPClientBase(); 52 | 53 | bool connect(const std::string &strServerAddress, uint16_t u16Port, const std::string &strDescription = std::string(""), 54 | bool bAutoReconnect = false); 55 | void disconnect(); 56 | 57 | //Client requests 58 | void sendKATCPMessage(const std::string &strMessage); //Send a custom KATCP message to the connected peer 59 | virtual void onConnected(){;} //Overload with things to do once to connection 60 | 61 | //Callback handler registration 62 | void registerCallbackHandler(cCallbackInterface *pNewHandler); 63 | void registerCallbackHandler(boost::shared_ptr pNewHandler); 64 | void deregisterCallbackHandler(cCallbackInterface *pHandler); 65 | void deregisterCallbackHandler(boost::shared_ptr pHandler); 66 | 67 | void registerConnectionCallbackHandler(cConnectionCallbackInterface *pNewHandler); 68 | void registerConnectionCallbackHandler(boost::shared_ptr pNewHandler); 69 | void deregisterConnectionCallbackHandler(cConnectionCallbackInterface *pHandler); 70 | void deregisterConnectionCallbackHandler(boost::shared_ptr pHandler); 71 | 72 | //Functions used by the reading thread by left public as they may be usefull externally 73 | std::vector tokeniseString(const std::string &strInputString, const std::string &strSeperators); 74 | std::vector readNextKATCPMessage(uint32_t u32Timeout_ms = 0); 75 | 76 | protected: 77 | virtual void threadReadFunction(); 78 | virtual void threadWriteFunction(); 79 | virtual void processKATCPMessage(const std::vector &vstrMessageTokens) = 0; 80 | 81 | void threadAutoReconnectFunction(); 82 | bool socketConnectFunction(); 83 | 84 | //Send calls to all callback handlers: 85 | void sendConnected(bool bConnected, const std::string &strHostAddress = std::string(""), 86 | uint16_t u16Port = 0, const std::string &strDescription = std::string("")); 87 | 88 | //Threads 89 | boost::scoped_ptr m_pSocketReadThread; 90 | boost::scoped_ptr m_pSocketWriteThread; 91 | boost::scoped_ptr m_pConnectThread; 92 | 93 | //Sockets 94 | boost::scoped_ptr m_pSocket; 95 | 96 | //Members description operation state 97 | std::string m_strServerAddress; 98 | uint16_t m_u16Port; 99 | std::string m_strDescription; 100 | bool m_bAutoReconnect; 101 | 102 | //Other variables 103 | bool m_bDisconnectFlag; 104 | boost::shared_mutex m_oFlagMutex; 105 | bool disconnectRequested(); 106 | 107 | std::queue m_qstrWriteQueue; 108 | boost::condition_variable m_oConditionWriteQueueNoLongerEmpty; 109 | boost::mutex m_oWriteQueueMutex; 110 | 111 | //Callback handlers 112 | std::vector m_vpCallbackHandlers; 113 | std::vector > m_vpCallbackHandlers_shared; 114 | std::vector m_vpConnectionCallbackHandlers; 115 | std::vector > m_vpConnectionCallbackHandlers_shared; 116 | boost::shared_mutex m_oCallbackHandlersMutex; 117 | 118 | boost::mutex m_oKATCPMutex; 119 | }; 120 | 121 | #endif // KATCP_CLIENT_BASE_H 122 | -------------------------------------------------------------------------------- /KATCPClientBase.cpp: -------------------------------------------------------------------------------- 1 | 2 | //System includes 3 | #include 4 | #include 5 | #include 6 | 7 | //Library includes 8 | #ifndef Q_MOC_RUN //Qt's MOC and Boost have some issues don't let MOC process boost headers 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | //Local includes 17 | #include "KATCPClientBase.h" 18 | 19 | using namespace std; 20 | 21 | cKATCPClientBase::cKATCPClientBase() : 22 | m_bDisconnectFlag(false) 23 | { 24 | //Note don't call connect in base constructor as it may call derived versions of virtual thread functions which may be 25 | //correctly populated at the time of the call here. 26 | 27 | //Calling code should call connect after construction of derived class. 28 | } 29 | 30 | cKATCPClientBase::~cKATCPClientBase() 31 | { 32 | disconnect(); 33 | } 34 | 35 | bool cKATCPClientBase::connect(const string &strServerAddress, uint16_t u16Port, const string &strDescription, bool bAutoReconnect) 36 | { 37 | 38 | //Store config parameters in members 39 | m_strServerAddress = strServerAddress; 40 | m_u16Port = u16Port; 41 | m_strDescription = strDescription; 42 | m_bAutoReconnect = bAutoReconnect; 43 | 44 | if(m_bAutoReconnect) 45 | { 46 | //Launch in a new thread to prevent blocking 47 | m_pConnectThread.reset(new boost::thread(&cKATCPClientBase::threadAutoReconnectFunction, this)); 48 | return true; 49 | } 50 | else 51 | { 52 | return socketConnectFunction(); 53 | } 54 | } 55 | 56 | void cKATCPClientBase::threadAutoReconnectFunction() 57 | { 58 | // PJP 59 | m_bDisconnectFlag = false; // reset flag for reconnection 60 | // PJP 61 | while(!disconnectRequested()) 62 | { 63 | if(socketConnectFunction()) 64 | break; 65 | 66 | cout << "cKATCPClientBase::threadAutoReconnectFunction() Reached timeout attempting to connect to server " << m_strServerAddress << ":" << m_u16Port << ". Retrying in 1 second..." << endl; 67 | boost::this_thread::sleep(boost::posix_time::milliseconds(1000)); 68 | } 69 | } 70 | 71 | bool cKATCPClientBase::socketConnectFunction() 72 | { 73 | cout << "cKATCPClientBase::socketConnectFunction() Connecting to KATCP server: " << m_strServerAddress << ":" << m_u16Port << endl; 74 | 75 | //Connect the socket 76 | m_pSocket.reset(new cInterruptibleBlockingTCPSocket()); 77 | 78 | if(!m_pSocket->openAndConnect(m_strServerAddress, m_u16Port, 1000)) 79 | { 80 | sendConnected(false, m_strServerAddress, m_u16Port, m_strDescription); 81 | return false; 82 | } 83 | 84 | cout << "cKATCPClientBase::socketConnectFunction() successfully connected KATCP server " << m_strServerAddress << ":" << m_u16Port << "." << endl; 85 | 86 | //Launch KATCP client processing. A thead for sending from the send queue and another for receiving and processing 87 | m_pSocketReadThread.reset(new boost::thread(&cKATCPClientBase::threadReadFunction, this)); 88 | m_pSocketWriteThread.reset(new boost::thread(&cKATCPClientBase::threadWriteFunction, this)); 89 | 90 | //Notify handlers of socket connection 91 | sendConnected(true, m_strServerAddress, m_u16Port, m_strDescription); 92 | 93 | //Function that can be overloaded to performs task on connection to the KATCP server. 94 | onConnected(); 95 | 96 | cout << "cKATCPClientBase::socketConnectFunction() successfully connected KATCP server " << m_strServerAddress << ":" << m_u16Port << "." << endl; 97 | 98 | return true; 99 | } 100 | 101 | 102 | void cKATCPClientBase::disconnect() 103 | { 104 | cout << "cKATCPClientBase::disconnect() Disconnecting KATCP client..." << endl; 105 | 106 | { 107 | boost::unique_lock oLock(m_oFlagMutex); 108 | 109 | m_bDisconnectFlag = true; 110 | if(m_pSocket.get()) 111 | m_pSocket->cancelCurrrentOperations(); 112 | 113 | m_oConditionWriteQueueNoLongerEmpty.notify_all(); 114 | } 115 | 116 | /* PJP if(m_pSocketReadThread.get()) 117 | m_pSocketReadThread->join(); 118 | m_pSocketReadThread.reset();*/ 119 | 120 | if(m_pSocketWriteThread.get()) 121 | m_pSocketWriteThread->join(); 122 | m_pSocketWriteThread.reset(); 123 | 124 | sendConnected(false, m_strServerAddress, m_u16Port, m_strDescription); 125 | 126 | cout << "cKATCPClientBase::disconnect() KATCP disconnected." << endl; 127 | } 128 | 129 | bool cKATCPClientBase::disconnectRequested() 130 | { 131 | //Thread safe function to check the disconnect flag 132 | boost::shared_lock oLock(m_oFlagMutex); 133 | 134 | return m_bDisconnectFlag; 135 | } 136 | 137 | void cKATCPClientBase::sendKATCPMessage(const std::string &strMessage) //Send a custom KATCP message to the connected peer 138 | { 139 | //Note: Remeber to add '\n' to the end of the string when using the function! 140 | 141 | std::cout << "cKATCPClientBase::sendKATCPMessage(): " << strMessage << std::endl; 142 | 143 | //Safely add to queue 144 | boost::unique_lock oLock(m_oWriteQueueMutex); 145 | 146 | m_qstrWriteQueue.push(strMessage); 147 | 148 | //Wake sending thread if it is waiting on an empty queue 149 | if(m_qstrWriteQueue.size() == 1) 150 | m_oConditionWriteQueueNoLongerEmpty.notify_all(); 151 | } 152 | 153 | vector cKATCPClientBase::readNextKATCPMessage(uint32_t u32Timeout_ms) 154 | { 155 | //Read KATCP message from connected socket and return 156 | //a vector of tokens making up the string 157 | //tokens are space delimted in the KATCP telnet-like protocol 158 | 159 | bool bFullMessage = false; 160 | string strKATCPMessage; 161 | 162 | do 163 | { 164 | bFullMessage = m_pSocket->readUntil( strKATCPMessage, string("\n"), u32Timeout_ms); 165 | 166 | //Return an empty vector if disconnect requested or if no characters have been received. 167 | if(disconnectRequested() || !strKATCPMessage.length()) 168 | { 169 | //Check if socket is disconnected 170 | /* PJP if(m_pSocket->getLastReadError().message().find("End of file") != string::npos 171 | || m_pSocket->getLastReadError().message().find("Bad file descriptor") != string::npos ) */ 172 | if ((boost::asio::error::eof == m_pSocket->getLastReadError()) || 173 | (boost::asio::error::connection_reset == m_pSocket->getLastReadError())) 174 | { 175 | cout << "cKATCPClientBase::readNextKATCPMessage() Got socket disconnect." << endl; 176 | 177 | //If it is perform disconnect routine 178 | disconnect(); 179 | 180 | //And then attempt to reconnect if the reconnection flag is set 181 | if(m_bAutoReconnect) 182 | { 183 | cout << "cKATCPClientBase::readNextKATCPMessage() Auto reconnect flag is set attempting to reconnect." << endl; 184 | m_pConnectThread.reset(new boost::thread(&cKATCPClientBase::threadAutoReconnectFunction, this)); 185 | } 186 | } 187 | 188 | return vector(); 189 | } 190 | 191 | //readUntil function will append to the message string if each iteration if the stop character is not reached. 192 | } 193 | while(!bFullMessage); 194 | 195 | return tokeniseString(strKATCPMessage, string(" ")); 196 | } 197 | 198 | void cKATCPClientBase::threadReadFunction() 199 | { 200 | cout << "cKATCPClientBase::threadReadFunction(): Entered thread read function." << endl; 201 | 202 | vector vstrMessageTokens; 203 | 204 | while(!disconnectRequested()) 205 | { 206 | vstrMessageTokens = readNextKATCPMessage(200); 207 | 208 | if(!vstrMessageTokens.size()) 209 | { 210 | continue; 211 | } 212 | 213 | processKATCPMessage(vstrMessageTokens); 214 | } 215 | 216 | cout << "cKATCPClientBase::threadReadFunction(): Leaving thread read function." << endl; 217 | } 218 | 219 | void cKATCPClientBase::threadWriteFunction() 220 | { 221 | cout << "cKATCPClientBase::threadWriteFunction(): Entered thread write function." << endl; 222 | 223 | string strMessageToSend; 224 | 225 | while(!disconnectRequested()) 226 | { 227 | { 228 | boost::unique_lock oLock(m_oWriteQueueMutex); 229 | 230 | //If the queue is empty wait for data 231 | if(!m_qstrWriteQueue.size()) 232 | { 233 | if(!m_oConditionWriteQueueNoLongerEmpty.timed_wait(oLock, boost::posix_time::milliseconds(500)) ) 234 | { 235 | //Timeout after 500 ms then check again (Loop restarts) 236 | continue; 237 | } 238 | } 239 | 240 | //Check size again 241 | if(!m_qstrWriteQueue.size()) 242 | continue; 243 | 244 | //Make a of copy of the string and pop it from the queue 245 | strMessageToSend = m_qstrWriteQueue.front(); 246 | m_qstrWriteQueue.pop(); 247 | } 248 | 249 | //Write the data to the socket 250 | //reattempt to send on timeout or failure 251 | while(!m_pSocket->write(strMessageToSend, 5000)) 252 | { 253 | //Check for shutdown in between attempts 254 | if(disconnectRequested()) 255 | return; 256 | } 257 | } 258 | 259 | cout << "cKATCPClientBase::threadWriteFunction(): Leaving thread write function." << endl; 260 | } 261 | 262 | void cKATCPClientBase::sendConnected(bool bConnected, const string &strHostAddress, uint16_t u16Port, const string &strDescription) 263 | { 264 | boost::shared_lock oLock(m_oCallbackHandlersMutex); 265 | 266 | for(uint32_t ui = 0; ui < m_vpCallbackHandlers.size(); ui++) 267 | { 268 | m_vpCallbackHandlers[ui]->connected_callback(bConnected, strHostAddress, u16Port, strDescription); 269 | } 270 | 271 | for(uint32_t ui = 0; ui < m_vpCallbackHandlers_shared.size(); ui++) 272 | { 273 | m_vpCallbackHandlers_shared[ui]->connected_callback(bConnected, strHostAddress, u16Port, strDescription); 274 | } 275 | 276 | for(uint32_t ui = 0; ui < m_vpConnectionCallbackHandlers.size(); ui++) 277 | { 278 | m_vpConnectionCallbackHandlers[ui]->connected_callback(bConnected, strHostAddress, u16Port, strDescription); 279 | } 280 | 281 | for(uint32_t ui = 0; ui < m_vpConnectionCallbackHandlers_shared.size(); ui++) 282 | { 283 | m_vpConnectionCallbackHandlers_shared[ui]->connected_callback(bConnected, strHostAddress, u16Port, strDescription); 284 | } 285 | } 286 | 287 | void cKATCPClientBase::registerCallbackHandler(cCallbackInterface *pNewHandler) 288 | { 289 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 290 | 291 | m_vpCallbackHandlers.push_back(pNewHandler); 292 | 293 | cout << "cKATCPClientBase::registerCallbackHandler(): Successfully registered callback handler: " << pNewHandler << endl; 294 | } 295 | 296 | void cKATCPClientBase::registerCallbackHandler(boost::shared_ptr pNewHandler) 297 | { 298 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 299 | 300 | m_vpCallbackHandlers_shared.push_back(pNewHandler); 301 | 302 | cout << "cKATCPClientBase::registerCallbackHandler(): Successfully registered callback handler: " << pNewHandler.get() << endl; 303 | } 304 | 305 | void cKATCPClientBase::deregisterCallbackHandler(cCallbackInterface *pHandler) 306 | { 307 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 308 | bool bSuccess = false; 309 | 310 | //Search for matching pointer values and erase 311 | for(uint32_t ui = 0; ui < m_vpCallbackHandlers.size();) 312 | { 313 | if(m_vpCallbackHandlers[ui] == pHandler) 314 | { 315 | m_vpCallbackHandlers.erase(m_vpCallbackHandlers.begin() + ui); 316 | 317 | cout << "cKATCPClientBase::deregisterCallbackHandler(): Deregistered callback handler: " << pHandler << endl; 318 | bSuccess = true; 319 | } 320 | else 321 | { 322 | ui++; 323 | } 324 | } 325 | 326 | if(!bSuccess) 327 | { 328 | cout << "cKATCPClientBase::deregisterCallbackHandler(): Warning: Deregistering callback handler: " << pHandler << " failed. Object instance not found." << endl; 329 | } 330 | } 331 | 332 | void cKATCPClientBase::deregisterCallbackHandler(boost::shared_ptr pHandler) 333 | { 334 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 335 | bool bSuccess = false; 336 | 337 | //Search for matching pointer values and erase 338 | for(uint32_t ui = 0; ui < m_vpCallbackHandlers_shared.size();) 339 | { 340 | if(m_vpCallbackHandlers_shared[ui].get() == pHandler.get()) 341 | { 342 | m_vpCallbackHandlers_shared.erase(m_vpCallbackHandlers_shared.begin() + ui); 343 | 344 | cout << "cKATCPClientBase::deregisterCallbackHandler(): Deregistered callback handler: " << pHandler.get() << endl; 345 | bSuccess = true; 346 | } 347 | else 348 | { 349 | ui++; 350 | } 351 | } 352 | 353 | if(!bSuccess) 354 | { 355 | cout << "cKATCPClientBase::deregisterCallbackHandler(): Warning: Deregistering callback handler: " << pHandler.get() << " failed. Object instance not found." << endl; 356 | } 357 | } 358 | 359 | void cKATCPClientBase::registerConnectionCallbackHandler(cConnectionCallbackInterface *pNewHandler) 360 | { 361 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 362 | 363 | m_vpConnectionCallbackHandlers.push_back(pNewHandler); 364 | 365 | cout << "cKATCPClientBase::registerConnectionCallbackHandler(): Successfully registered callback handler: " << pNewHandler << endl; 366 | } 367 | 368 | void cKATCPClientBase::registerConnectionCallbackHandler(boost::shared_ptr pNewHandler) 369 | { 370 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 371 | 372 | m_vpConnectionCallbackHandlers_shared.push_back(pNewHandler); 373 | 374 | cout << "cKATCPClientBase::registerConnectionCallbackHandler(): Successfully registered callback handler: " << pNewHandler.get() << endl; 375 | } 376 | 377 | void cKATCPClientBase::deregisterConnectionCallbackHandler(cConnectionCallbackInterface *pHandler) 378 | { 379 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 380 | bool bSuccess = false; 381 | 382 | //Search for matching pointer values and erase 383 | for(uint32_t ui = 0; ui < m_vpConnectionCallbackHandlers.size();) 384 | { 385 | if(m_vpConnectionCallbackHandlers[ui] == pHandler) 386 | { 387 | m_vpConnectionCallbackHandlers.erase(m_vpConnectionCallbackHandlers.begin() + ui); 388 | 389 | cout << "cKATCPClientBase::deregisterConnectionCallbackHandler(): Deregistered callback handler: " << pHandler << endl; 390 | bSuccess = true; 391 | } 392 | else 393 | { 394 | ui++; 395 | } 396 | } 397 | 398 | if(!bSuccess) 399 | { 400 | cout << "cKATCPClientBase::deregisterConnectionCallbackHandler(): Warning: Deregistering callback handler: " << pHandler << " failed. Object instance not found." << endl; 401 | } 402 | } 403 | 404 | void cKATCPClientBase::deregisterConnectionCallbackHandler(boost::shared_ptr pHandler) 405 | { 406 | boost::unique_lock oLock(m_oCallbackHandlersMutex); 407 | bool bSuccess = false; 408 | 409 | //Search for matching pointer values and erase 410 | for(uint32_t ui = 0; ui < m_vpConnectionCallbackHandlers_shared.size();) 411 | { 412 | if(m_vpConnectionCallbackHandlers_shared[ui].get() == pHandler.get()) 413 | { 414 | m_vpConnectionCallbackHandlers_shared.erase(m_vpConnectionCallbackHandlers_shared.begin() + ui); 415 | 416 | cout << "cKATCPClientBase::deregisterConnectionCallbackHandler(): Deregistered callback handler: " << pHandler.get() << endl; 417 | bSuccess = true; 418 | } 419 | else 420 | { 421 | ui++; 422 | } 423 | } 424 | 425 | if(!bSuccess) 426 | { 427 | cout << "cKATCPClientBase::deregisterConnectionCallbackHandler(): Warning: Deregistering callback handler: " << pHandler.get() << " failed. Object instance not found." << endl; 428 | } 429 | } 430 | 431 | std::vector cKATCPClientBase::tokeniseString(const std::string &strInputString, const std::string &strSeparators) 432 | { 433 | //This funciton is not complete efficient due to extra memory copies of filling the std::vector 434 | //It will also be copied again on return. 435 | //It does simply the calling code and should be adequate in the context of most KATCP control clients. 436 | 437 | boost::char_separator oSeparators(strSeparators.c_str()); 438 | boost::tokenizer< boost::char_separator > oTokens(strInputString, oSeparators); 439 | 440 | vector vstrTokens; 441 | 442 | for(boost::tokenizer< boost::char_separator >::iterator it = oTokens.begin(); it != oTokens.end(); ++it) 443 | { 444 | vstrTokens.push_back(*it); 445 | boost::trim(vstrTokens.back()); //Remove any possible whitespace etc from sides of token 446 | } 447 | 448 | return vstrTokens; 449 | } 450 | --------------------------------------------------------------------------------