// The MAIN function for the server software, which starts up the XmlRpc (http server), // as well as Open Transactions. // int main(int argc, char* argv[]) { OTLog::vOutput(0, "\n\nWelcome to Open Transactions... Test Server -- version %s\n" "(transport build: OTMessage -> OTEnvelope -> ZMQ )\n\n", OTLog::Version()); // ----------------------------------------------------------------------- #ifdef _WIN32 WSADATA wsaData; WORD wVersionRequested = MAKEWORD( 2, 2 ); int nWSA = WSAStartup( wVersionRequested, &wsaData ); OT_ASSERT_MSG(0 != nWSA, "Error calling WSAStartup.\n"); #endif // ----------------------------------------------------------------------- // I instantiate this here (instead of globally) so that I am assured all the globals // are ready to go before the server is created. I still have it as a global pointer, though, // so I can get to it wherever I need to. g_pServer = new OTServer; // (This file you are reading is a wrapper for OTServer, which adds the transport layer.) OT_ASSERT_MSG(NULL != g_pServer, "Unable to instantiate OT server...\n"); // ----------------------------------------------------------------------- // The beginnings of an INI file!! OTString strIniFileDefault; OTLog::TransformFilePath(OT_INI_FILE_DEFAULT, strIniFileDefault); OTString strPath, strRawPath(SERVER_PATH_DEFAULT); { CSimpleIniA ini; SI_Error rc = ini.LoadFile(strIniFileDefault.Get()); if (rc >=0) { { const char * pVal = ini.GetValue("paths", "server_path", SERVER_PATH_DEFAULT); // todo stop hardcoding. if (NULL != pVal) { strRawPath.Set(pVal); OTLog::vOutput(0, "Reading ini file (%s). \n Found Server data_folder path: %s \n", strIniFileDefault.Get(), strRawPath.Get()); } else { strRawPath.Set(SERVER_PATH_DEFAULT); OTLog::vOutput(0, "Reading ini file (%s): \n Failed reading Server data_folder path. Using: %s \n", strIniFileDefault.Get(), strRawPath.Get()); } } } else { strRawPath.Set(SERVER_PATH_DEFAULT); OTLog::vOutput(0, "Unable to load ini file (%s) to find data_folder path\n Will assume that server data_folder is at path: %s \n", strIniFileDefault.Get(), strRawPath.Get()); } } // ----------------------------------------------------------------------- OTString strCAFile, strDHFile, strKeyFile; //, strSSLPassword; OTLog::TransformFilePath(strRawPath.Get(), strPath); OTLog::SetMainPath(strPath.Get()); OTLog::vOutput(0, "Using data_folder path: %s\n", OTLog::Path()); strCAFile. Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), CA_FILE); strDHFile. Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), DH_FILE); strKeyFile.Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), KEY_FILE); // ----------------------------------------------------------------------- // Initialize SSL -- This MUST occur before any Private Keys are loaded! SSL_library_init(); SSL_load_error_strings(); // Init loads up server's nym so it can decrypt messages sent in envelopes. // It also does various other initialization work. // // (Envelopes prove that ONLY someone who actually had the server contract, // and had loaded it into his wallet, could ever connect to the server or // communicate with it. And if that person is following the contract, there // is only one server he can connect to, and one key he can use to talk to it.) OTLog::vOutput(0, "\nNow loading the server nym, which will also ask you for a password, to unlock\n" "its private key. (Default password is \"%s\".)\n", KEY_PASSWORD); g_pServer->Init(); // Keys, etc are loaded here. // ----------------------------------------------------------------------- // We're going to listen on the same port that is listed in our server contract. // // OTString strHostname; // The hostname of this server, according to its own contract. int nPort=0; // The port of this server, according to its own contract. OT_ASSERT_MSG(g_pServer->GetConnectInfo(strHostname, nPort), "Unable to find my own connect info (which is in my server contract BTW.)\n"); const int nServerPort = nPort; // int nSFSocketInit = SFSocketInit(socket, // strCAFile.Get(), // strDHFile.Get(), // strKeyFile.Get(), // strSSLPassword.Get(), // NULL); // ----------------------------------------------------------------------- // For re-occuring actions (like markets and payment plans.) // g_pServer->ActivateCron(); // ----------------------------------- // Prepare our context and socket zmq::context_t context(1); zmq::socket_t socket(context, ZMQ_REP); OTString strBindPath; strBindPath.Format("%s%d", "tcp://*:", nServerPort); socket.bind(strBindPath.Get()); // ----------------------------------------------------------------------- // Let's get the HTTP server up and running... // Switching out XmlRpc for 0MQ (ZeroMQ) // // XmlRpc::setVerbosity(1); // // // Create the server socket on the specified port // theXmlRpcServer.bindAndListen(nServerPort); // // // Enable introspection, so clients can see what services this server supports. (Open Transactions...) // theXmlRpcServer.enableIntrospection(true); // ----------------------------------------------------------------------- // Initialize poll set zmq::pollitem_t items [] = { { socket, 0, ZMQ_POLLIN, 0 }, }; // ---------------------------------------------------------- do // THE HEARTBEAT LOOP FOR THE OPEN-TRANSACTIONS SERVER! { // The Server now processes certain things on a regular basis. // ProcessCron is what gives it the opportunity to do that. // All of the Cron Items (including market trades, and payment plans...) have their hooks here... // g_pServer->ProcessCron(); // ----------------------------------------------------------------------- // Wait for client http requests (and process replies out to them.) // // theXmlRpcServer.work(10.0); // supposedly milliseconds -- but it's actually seconds. // Loop: process up to 10 client requests, then sleep for 1/10th second. // // Then: check for shutdown. // // Then: go back to the top and repeat.... process cron, loop 10 client requests, sleep, check for shutdown, etc. // // for (int i = 0; i < /*10*/OTServer::GetHeartbeatNoRequests(); i++) { // Switching to ZeroMQ library. zmq::message_t message; zmq::poll(&items[0], 1, 0); // non-blocking // zmq::poll(&items[0], 1, -1); if ((items[0].revents & ZMQ_POLLIN) && socket.recv(&message, ZMQ_NOBLOCK)) { // socket.recv(&message); // Convert the ZMQ message to a std::string std::string str_Message; str_Message.reserve(message.size()); str_Message.append(static_cast<const char *>(message.data()), message.size()); // Process task std::string str_Reply; // Output. ProcessMessage_ZMQ(str_Message, str_Reply); // ----------------------------------------------- // Convert the std::string (reply) into a ZMQ message zmq::message_t reply (str_Reply.length()); if (str_Reply.length() > 0) memcpy((void *) reply.data(), str_Reply.c_str(), str_Reply.length()); // -------------------------------- // Send reply back to client int nSendTries = 0; bool bSuccessSending = false; // HALF-SECOND DELAY IF FAILURE SENDING REPLY... // (While it tries to re-send 5 times.) // while ((nSendTries++ < OTLog::GetLatencySendNoTries()/*5*/) && (false == (bSuccessSending = socket.send(reply, ZMQ_NOBLOCK)))) OTLog::SleepMilliseconds(/*100*/OTLog::GetLatencySendMs()); if (false == bSuccessSending) OTLog::vError("Socket error: failed while trying to send reply back to client! \n\n MESSAGE:\n%s\n\nREPLY:\n%s\n\n", str_Message.c_str(), str_Reply.c_str()); } } // for // ----------------------------------------------------------------------- // Now go to sleep for a tenth of a second. // (The main loop processes ten times per second, currently.) OTLog::SleepMilliseconds(/*100*/OTServer::GetHeartbeatMsBetweenBeats()); // 100 ms == (1 second / 10) // ----------------------------------------------------------------------- // ARTIFICIAL LIMIT: // 10 requests per heartbeat, 10 rounds per second == 100 requests per second. // // *** ONE HUNDRED CLIENT MESSAGES PER SECOND is the same as: // // 6000 PER MINUTE == 360,000 PER HOUR == 8,640,000 PER DAY*** // // Speeding it up is just a matter of adjusting the above numbers, and TESTING to see if OT can handle it. // (Not counting optimization of course.) // // ----------------------------------------------------------------------- if (g_pServer->IsFlaggedForShutdown()) { OTLog::Output(0, "Server is shutting down gracefully....\n"); break; } } while (1); // TODO: cleanup OpenSSL here. #ifdef _WIN32 WSACleanup(); #endif return 0; }
// ********************************************************************************************************* // // // *** SERVER MAIN *** // // // The MAIN function for the server software, which starts up the ZMQ listener, as well // as well as the Open Transactions library and the OT Server object. // // After initialization, this function becomes the "main loop" of OT server. // int main(int argc, char* argv[]) { OTLog::vOutput(0, "\n\nWelcome to Open Transactions... Test Server -- version %s\n" "(transport build: OTMessage -> OTEnvelope -> ZMQ )\n\n", OTLog::Version()); // WINSOCK WINDOWS // ----------------------------------------------------------------------- #ifdef _WIN32 WORD wVersionRequested; WSADATA wsaData; int err; /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { /* Tell the user that we could not find a usable */ /* Winsock DLL. */ printf("WSAStartup failed with error: %d\n", err); return 1; } /* Confirm that the WinSock DLL supports 2.2. */ /* Note that if the DLL supports versions greater */ /* than 2.2 in addition to 2.2, it will still return */ /* 2.2 in wVersion since that is the version we */ /* requested. */ if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { /* Tell the user that we could not find a usable */ /* WinSock DLL. */ printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return 1; } else printf("The Winsock 2.2 dll was found okay\n"); /* The Winsock DLL is acceptable. Proceed to use it. */ /* Add network programming using Winsock here */ /* then call WSACleanup when done using the Winsock dll */ #endif // *********************************************************************** // INITIALIZATION and CLEANUP (for the OT library, and for this server application.) // class __ot_server_ { OTServer * m_pServer; public: OTServer * GetServer() { return m_pServer; } // ----------------------------------- __ot_server_() : m_pServer(NULL) // INIT { // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // OTLog class exists on both client and server sides. // #define OT_NO_SIGNAL_HANDLING if you want to turn off OT's signal handling. // #if defined(OT_SIGNAL_HANDLING) OTLog::SetupSignalHandler(); // This is optional! (I, of course, am using it in this test app...) #endif // ----------------------------------------------------------------------- // I instantiate this here (instead of globally) so that I am assured that any globals and other // setup is already done before we instantiate the server object itself. // OT_ASSERT_MSG(NULL == m_pServer, "server main(): ASSERT: NULL == m_pServer."); m_pServer = new OTServer; // // (This .cpp file you are currently reading is a wrapper for OTServer, // which adds the transport layer.) // OT_ASSERT_MSG(NULL != m_pServer, "server main(): ASSERT: Unable to instantiate OT server.\n"); OTString pathUserAppDataPath, pathIniFileLocation; pathUserAppDataPath = GetRoamingAppDataLocation(); pathIniFileLocation.Format("%s%s%s", pathUserAppDataPath.Get(), OTLog::PathSeparator(), SERVER_INI_FILE_DEFAULT); OTString pathOTServerDataLocation; OTLog::vOutput(0, "\nFound ot_init.cfg in: \n %s \nNow checking to see if it contains the OT Server path...", pathIniFileLocation.Get()); // Read the File, If successful use result if (false == GetOTAppDataFolderLocation(pathIniFileLocation, pathOTServerDataLocation)) { OTLog::vOutput(0, "Path not found... Will attempt default!... \n"); // Not successfull will will assume it is in default location: pathOTServerDataLocation.Format("%s%s%s", pathUserAppDataPath.Get(), OTLog::PathSeparator(), SERVER_PATH_DEFAULT); }; OTLog::vOutput(0, " %s \n", pathOTServerDataLocation.Get()); OTLog::SetMainPath(pathOTServerDataLocation.Get()); // <============ SET MAIN PATH OTLog::vOutput(0, "Using server_data path: %s\n", OTLog::Path()); // ----------------------------------------------------------------------- OTCrypto::It()->Init(); // <========== (OpenSSL gets initialized here.) } // **************************************** // ~__ot_server_() // CLEANUP { OTLog::vOutput(0, "\n\n OT version %s, shutting down and cleaning up.\n", OTLog::Version()); // ------------------------------ if (NULL != m_pServer) delete m_pServer; m_pServer = NULL; // ------------------------------ // We clean these up in reverse order from the Init function, which just seems // like the best default, in absence of any brighter ideas. // OTCrypto::It()->Cleanup(); // <======= (OpenSSL gets cleaned up here.) // ------------------------- // (This is at the bottom, since we do the cleanup in the // reverse order from initialization.) #ifdef _WIN32 WSACleanup(); #endif } }; // *********************************************************************** // // INSTANTIATE and INITIALIZE... // // (Cleanup happens automatically when this object goes out of scope.) // __ot_server_ the_server_obj; OTServer * pServer = the_server_obj.GetServer(); OT_ASSERT(NULL != pServer); // ----------------------------------------------------------------------- // OTString strCAFile, strDHFile, strKeyFile; //, strSSLPassword; // strCAFile. Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), CA_FILE); // strDHFile. Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), DH_FILE); // strKeyFile.Format("%s%s%s", OTLog::Path(), OTLog::PathSeparator(), KEY_FILE); // ----------------------------------------------------------------------- // // UPDATE: This was moved to OTLog::OT_Init(), which is called above, by the // nested cleanup class. // // Initialize SSL -- This MUST occur before any Private Keys are loaded! // SSL_library_init(); // SSL_load_error_strings(); // ----------------------------------------------------------------------- // OTServer::Init loads up server's nym so it can decrypt messages sent in // envelopes. It also does various other initialization work. // // (Envelopes prove that ONLY someone who actually had the server contract, // and had loaded it into his wallet, could ever connect to the server or // communicate with it. And if that person is following the contract, there // is only one server he can connect to, and one key he can use to talk to it.) // OTLog::vOutput(0, "\nNow loading the server nym, which will also ask you for a password, to unlock\n" "its private key. (Default password is \"%s\".)\n", KEY_PASSWORD); pServer->Init(); // Keys, etc are loaded here. ===> Assumes main path is set! <=== // ----------------------------------------------------------------------- // We're going to listen on the same port that is listed in our server contract. // // OTString strHostname; // The hostname of this server, according to its own contract. int nPort=0; // The port of this server, according to its own contract. const bool bConnectInfo = pServer->GetConnectInfo(strHostname, nPort); OT_ASSERT_MSG(bConnectInfo, "server main: Unable to find my own connect info (which SHOULD be in my server contract, BTW.) Perhaps you failed trying to open that contract? Have you tried the test password? (\"test\")\n"); const int nServerPort = nPort; // ----------------------------------------------------------------------- // OT CRON // // A heartbeat for recurring transactions, such as markets, payment plans, // and smart contracts. pServer->ActivateCron(); // NOTE: Currently we trigger OT Cron's processing internally, but there's no reason why, in the // future, we can't make an actual cron job that triggers a script, that fires a message // to OT, causing OT to process its Cron (even if we were single-threaded we could do this...) // // Have to put some thought into it... // // Wouldn't require much security, since OT can still be smart enough not to process cron any // more often than X minutes, no matter HOW many times the ProcessCron script fires. // Thing is, though, that with this setup, we can't really guarantee that cron will EVER be // triggered -- whereas the way OT is now, at least we know it WILL fire every X seconds. // // -------------------------------------- // // NETWORK // // Prepare our context and listening socket... OTSocket theSocket; OTString strBindPath; strBindPath.Format("%s%d", "tcp://*:", nServerPort); theSocket.Listen(strBindPath); // ****************************************************************************************** // // *** MAIN LOOP *** // do // --------------------------- { // =-=-=- HEARTBEAT -=-=-= // // The Server now processes certain things on a regular basis. // ProcessCron is what gives it the opportunity to do that. // All of the Cron Items (including market trades, payment plans, smart contracts...) // they all have their hooks here... // pServer->ProcessCron(); // Internally this is smart enough to know how often to actually activate itself. // Most often it just returns doing nothing (waiting for its timer.) // ----------------------------------------------------------------------- // Wait for client http requests (and process replies out to them.) // ---------------------------------------------------------------------- // Number of requests to process per heartbeat: OTServer::GetHeartbeatNoRequests() // // Loop: process up to 10 client requests, then sleep for 1/10th second. // That's a total of 100 requests per second. Can the computers handle it? // Is it too much or too little? Todo: load testing. // // Then: check for shutdown flag. // // Then: go back to the top ("do") and repeat the loop.... process cron, // process 10 client requests, sleep, check for shutdown, etc. // // Timer t; // start timer t.start(); const double tick1 = t.getElapsedTimeInMilliSec(); // ----------------------------------------------------- // // PROCESS X NUMBER OF REQUESTS (THIS PULSE.) // // Theoretically the "number of requests" that we process EACH PULSE. // (The timing code here is still pretty new, need to do some load testing.) // for (int i = 0; i < /*10*/OTServer::GetHeartbeatNoRequests(); i++) { std::string str_Message; // With 100ms heartbeat, receive will try 100 ms, then 200 ms, then 400 ms, total of 700. // That's about 15 Receive() calls every 10 seconds. Therefore if I want the ProcessCron() // to trigger every 10 seconds, I need to set the cron interval to roll over every 15 heartbeats. // Therefore I will be using a real Timer for Cron, instead of the damn intervals. // bool bReceived = theSocket.Receive(str_Message); if (bReceived) { std::string str_Reply; // Output. if (str_Message.length() <= 0) { OTLog::Error("server main: Received a message, but of 0 length or less. Weird. (Skipping it.)\n"); } else // ------------------------------------ { // true == YES, DISCONNECT m_pSocket, something must have gone wrong. // false == NO, do NOT disconnect m_pSocket, everything went wonderfully! // const bool bShouldDisconnect = ProcessMessage_ZMQ(*pServer, str_Message, str_Reply); // <================== PROCESS the message! // -------------------------------------------------- if ((str_Reply.length() <= 0) || bShouldDisconnect) { OTLog::vOutput(0, "server main: ERROR: Unfortunately, not every client request is " "legible or worthy of a server response. :-) " "Msg:\n\n%s\n\n", str_Message.c_str()); theSocket.Listen(); } else { bool bSuccessSending = theSocket.Send(str_Reply); // <===== SEND THE REPLY if (false == bSuccessSending) OTLog::vError("server main: Socket ERROR: failed while trying to send reply " "back to client! \n\n MESSAGE:\n%s\n\nREPLY:\n%s\n\n", str_Message.c_str(), str_Reply.c_str()); // -------------------------------------------------- } } } } // for // ----------------------------------------------------------------------- // // IF the time we had available wasn't all used up -- if some of it is still // available, then SLEEP until we reach the NEXT PULSE. (In practice, we will // probably use TOO MUCH time, not too little--but then again OT isn't ALWAYS // processing a message. There could be plenty of dead time in between...) // const double tick2 = t.getElapsedTimeInMilliSec(); const long elapsed = static_cast<long>(tick2 - tick1); long lSleepMS = 0; if (elapsed < /*100*/OTServer::GetHeartbeatMsBetweenBeats()) { lSleepMS = OTServer::GetHeartbeatMsBetweenBeats() - elapsed; // Now go to sleep. // (The main loop processes ten times per second, currently.) OTLog::SleepMilliseconds(lSleepMS); // 100 ms == (1 second / 10) } // ----------------------------------------------------------------------- // ARTIFICIAL LIMIT: // 10 requests per heartbeat, 10 rounds per second == 100 requests per second. // // *** ONE HUNDRED CLIENT MESSAGES PER SECOND is the same as: // // 6000 PER MINUTE == 360,000 PER HOUR == 8,640,000 PER DAY*** // // Speeding it up is just a matter of adjusting the above numbers, and LOAD TESTING, // to see if OT can handle it. (Not counting optimization of course.) // // ----------------------------------------------------------------------- if (pServer->IsFlaggedForShutdown()) { OTLog::Output(0, "main: OT Server is shutting down gracefully....\n"); break; } } while (1); // (MAIN LOOP) // ------------------------------------ return 0; }