diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 5bc42a8d..f600f358 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -4,9 +4,9 @@ add_library(jsoncpp STATIC ${jsoncpp_src_list}) if (CMAKE_SYSTEM_NAME MATCHES "Windows") - set(MediaServer_src_list ./WebApi.cpp ./WebHook.cpp main.cpp) + set(MediaServer_src_list ./MiniDumper.cpp ./WebApi.cpp ./WebHook.cpp win32main.cpp) else() - file(GLOB MediaServer_src_list ./*.cpp ./*.h) + file(GLOB MediaServer_src_list ./WebApi.cpp ./WebHook.cpp ./System.cpp ./Process.cpp ./FFmpegSource.cpp main.cpp) endif() #message(STATUS ${MediaServer_src_list}) diff --git a/server/MediaServer.h b/server/MediaServer.h new file mode 100644 index 00000000..18aee275 --- /dev/null +++ b/server/MediaServer.h @@ -0,0 +1,20 @@ +#ifndef Server_MediaServer_H +#define Server_MediaServer_H + + +//## package Server + +enum +{ + + MediaServerStartingUpState = 0, + MediaServerRunningState = 1, + MediaServerRefusingConnectionsState = 2, + MediaServerFatalErrorState = 3,// a fatal error has occurred, not shutting down yet + MediaServerShuttingDownState = 4, + MediaServerIdleState = 5 // Like refusing connections state, but will also kill any currently connected clients +}; +typedef int MediaServer_ServerState; + +#endif + diff --git a/server/MiniDumper.cpp b/server/MiniDumper.cpp new file mode 100644 index 00000000..4480b017 --- /dev/null +++ b/server/MiniDumper.cpp @@ -0,0 +1,325 @@ +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "miniDumper.h" + +#ifdef UNICODE +#define _tcssprintf wsprintf +#define tcsplitpath _wsplitpath +#else +#define _tcssprintf sprintf +#define tcsplitpath _splitpath +#endif + +const int USER_DATA_BUFFER_SIZE = 4096; + +//----------------------------------------------------------------------------- +// GLOBALS +//----------------------------------------------------------------------------- +CMiniDumper* CMiniDumper::s_pMiniDumper = NULL; +LPCRITICAL_SECTION CMiniDumper::s_pCriticalSection = NULL; + +// Based on dbghelp.h +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + +//----------------------------------------------------------------------------- +// Name: CMiniDumper() +// Desc: Constructor +//----------------------------------------------------------------------------- +CMiniDumper::CMiniDumper( bool bPromptUserForMiniDump ) +{ + // Our CMiniDumper should act alone as a singleton. + assert( !s_pMiniDumper ); + + s_pMiniDumper = this; + m_bPromptUserForMiniDump = bPromptUserForMiniDump; + + // The SetUnhandledExceptionFilter function enables an application to + // supersede the top-level exception handler of each thread and process. + // After calling this function, if an exception occurs in a process + // that is not being debugged, and the exception makes it to the + // unhandled exception filter, that filter will call the exception + // filter function specified by the lpTopLevelExceptionFilter parameter. + ::SetUnhandledExceptionFilter( unhandledExceptionHandler ); + + // Since dbghelp.dll is not inherently thread-safe, making calls into it + // from more than one thread simultaneously may yield undefined behavior. + // This means that if your application has multiple threads, or is + // called by multiple threads in a non-synchronized manner, you need to + // make sure that all calls into dbghelp.dll are isolated via a global + // critical section. + s_pCriticalSection = new CRITICAL_SECTION; + + if( s_pCriticalSection ) + InitializeCriticalSection( s_pCriticalSection ); +} + +//----------------------------------------------------------------------------- +// Name: ~CMiniDumper() +// Desc: Destructor +//----------------------------------------------------------------------------- +CMiniDumper::~CMiniDumper( void ) +{ + if( s_pCriticalSection ) + { + DeleteCriticalSection( s_pCriticalSection ); + delete s_pCriticalSection; + } +} + +//----------------------------------------------------------------------------- +// Name: unhandledExceptionHandler() +// Desc: Call-back filter function for unhandled exceptions +//----------------------------------------------------------------------------- +LONG CMiniDumper::unhandledExceptionHandler( _EXCEPTION_POINTERS *pExceptionInfo ) +{ + if( !s_pMiniDumper ) + return EXCEPTION_CONTINUE_SEARCH; + + return s_pMiniDumper->writeMiniDump( pExceptionInfo ); +} + +//----------------------------------------------------------------------------- +// Name: setMiniDumpFileName() +// Desc: +//----------------------------------------------------------------------------- +void CMiniDumper::setMiniDumpFileName( void ) +{ + time_t currentTime; + time(¤tTime); + + sprintf( m_szMiniDumpPath, + "%s%s.%ld.dmp", + m_szAppPath, + m_szAppBaseName, + currentTime); +} + +//----------------------------------------------------------------------------- +// Name: getImpersonationToken() +// Desc: The method acts as a potential workaround for the fact that the +// current thread may not have a token assigned to it, and if not, the +// process token is received. +//----------------------------------------------------------------------------- +bool CMiniDumper::getImpersonationToken( HANDLE* phToken ) +{ + *phToken = NULL; + + if( !OpenThreadToken( GetCurrentThread(), + TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, + TRUE, + phToken) ) + { + if( GetLastError() == ERROR_NO_TOKEN ) + { + // No impersonation token for the current thread is available. + // Let's go for the process token instead. + if( !OpenProcessToken( GetCurrentProcess(), + TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, + phToken) ) + return false; + } + else + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: enablePrivilege() +// Desc: Since a MiniDump contains a lot of meta-data about the OS and +// application state at the time of the dump, it is a rather privileged +// operation. This means we need to set the SeDebugPrivilege to be able +// to call MiniDumpWriteDump. +//----------------------------------------------------------------------------- +BOOL CMiniDumper::enablePrivilege( LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld ) +{ + BOOL bOk = FALSE; + + TOKEN_PRIVILEGES tp; + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + bOk = LookupPrivilegeValue( 0, pszPriv, &tp.Privileges[0].Luid ); + + if( bOk ) + { + DWORD cbOld = sizeof(*ptpOld); + bOk = AdjustTokenPrivileges( hToken, FALSE, &tp, cbOld, ptpOld, &cbOld ); + } + + return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError())); +} + +//----------------------------------------------------------------------------- +// Name: restorePrivilege() +// Desc: +//----------------------------------------------------------------------------- +BOOL CMiniDumper::restorePrivilege( HANDLE hToken, TOKEN_PRIVILEGES* ptpOld ) +{ + BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, NULL, NULL); + return ( bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()) ); +} + +//----------------------------------------------------------------------------- +// Name: writeMiniDump() +// Desc: +//----------------------------------------------------------------------------- +LONG CMiniDumper::writeMiniDump( _EXCEPTION_POINTERS *pExceptionInfo ) +{ + LONG retval = EXCEPTION_CONTINUE_SEARCH; + m_pExceptionInfo = pExceptionInfo; + + HANDLE hImpersonationToken = NULL; + if( !getImpersonationToken( &hImpersonationToken ) ) + return FALSE; + + // You have to find the right dbghelp.dll. + // Look next to the EXE first since the one in System32 might be old (Win2k) + + HMODULE hDll = NULL; + char szDbgHelpPath[MAX_PATH]; + + if( GetModuleFileNameA( NULL, m_szAppPath, _MAX_PATH ) ) + { + char* pSlash =strrchr((char*)m_szAppPath, '\\' ); + + if( pSlash ) + { + _tcscpy( (char*)m_szAppBaseName, (const char*)pSlash + 1); + *(pSlash+1) = 0; + } + + wcscpy( (wchar_t*)szDbgHelpPath, (const wchar_t*)m_szAppPath ); + wcscat( (wchar_t*)szDbgHelpPath, (const wchar_t*)"dbghelp.dll"); + + hDll = ::LoadLibraryA( szDbgHelpPath ); + } + + if( hDll == NULL ) + { + // If we haven't found it yet - try one more time. + hDll = ::LoadLibraryA( "dbghelp.dll"); + } + + const char* szResult = NULL; + + if( hDll ) + { + // Get the address of the MiniDumpWriteDump function, which writes + // user-mode mini-dump information to a specified file. + MINIDUMPWRITEDUMP MiniDumpWriteDump = + (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" ); + + if( MiniDumpWriteDump != NULL ) + { + char szScratch[USER_DATA_BUFFER_SIZE]; + + setMiniDumpFileName(); + + // Ask the user if he or she wants to save a mini-dump file... + sprintf( szScratch, + "There was an unexpected error:\n\nWould you " + "like to create a mini-dump file?\n\n%s ", + m_szMiniDumpPath); + + // Create the mini-dump file... + HANDLE hFile = ::CreateFileA( m_szMiniDumpPath, + GENERIC_WRITE, + FILE_SHARE_WRITE, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if( hFile != INVALID_HANDLE_VALUE ) + { + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionInfo; + ExInfo.ClientPointers = NULL; + + // We need the SeDebugPrivilege to be able to run MiniDumpWriteDump + TOKEN_PRIVILEGES tp; + BOOL bPrivilegeEnabled = enablePrivilege( SE_DEBUG_NAME, hImpersonationToken, &tp ); + + BOOL bOk; + + // dbghelp.dll is not thread-safe, so we need to restrict access... + EnterCriticalSection( s_pCriticalSection ); + { + // Write out the mini-dump data to the file... + bOk = MiniDumpWriteDump( GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + MiniDumpNormal, + &ExInfo, + NULL, + NULL ); + } + LeaveCriticalSection( s_pCriticalSection ); + + // Restore the privileges when done + if( bPrivilegeEnabled ) + restorePrivilege( hImpersonationToken, &tp ); + + if( bOk ) + { + szResult = NULL; + retval = EXCEPTION_EXECUTE_HANDLER; + } + else + { + sprintf( szScratch, + "Failed to save the mini-dump file to '%s' (error %d)", + m_szMiniDumpPath, + GetLastError() ); + + szResult = szScratch; + } + + ::CloseHandle( hFile ); + } + else + { + sprintf( szScratch, + "Failed to create the mini-dump file '%s' (error %d)", + m_szMiniDumpPath, + GetLastError() ); + + szResult = szScratch; + } + } + else + { + szResult = "Call to GetProcAddress failed to find MiniDumpWriteDump. " + "The dbghelp.dll is possibly outdated." ; + } + } + else + { + szResult = "Call to LoadLibrary failed to find dbghelp.dll."; + } + + if( szResult && m_bPromptUserForMiniDump ) + ::MessageBoxA( NULL, szResult, NULL, MB_OK ); + + TerminateProcess( GetCurrentProcess(), 0 ); + + return retval; +} +#endif + diff --git a/server/MiniDumper.h b/server/MiniDumper.h new file mode 100644 index 00000000..0962926f --- /dev/null +++ b/server/MiniDumper.h @@ -0,0 +1,33 @@ +#ifndef MINIDUMPER_H +#define MINIDUMPER_H + +#ifdef _WIN32 +#include + +class CMiniDumper +{ + public: + + CMiniDumper(bool bPromptUserForMiniDump); + ~CMiniDumper(void); + + private: + + static LONG WINAPI unhandledExceptionHandler(struct _EXCEPTION_POINTERS *pExceptionInfo); + void setMiniDumpFileName(void); + bool getImpersonationToken(HANDLE* phToken); + BOOL enablePrivilege(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld); + BOOL restorePrivilege(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld); + LONG writeMiniDump(_EXCEPTION_POINTERS *pExceptionInfo ); + + _EXCEPTION_POINTERS *m_pExceptionInfo; + char m_szMiniDumpPath[MAX_PATH]; + char m_szAppPath[MAX_PATH]; + char m_szAppBaseName[MAX_PATH]; + bool m_bPromptUserForMiniDump; + + static CMiniDumper* s_pMiniDumper; + static LPCRITICAL_SECTION s_pCriticalSection; +}; +#endif +#endif // MINIDUMPER_H diff --git a/server/win32main.cpp b/server/win32main.cpp new file mode 100644 index 00000000..31ad714b --- /dev/null +++ b/server/win32main.cpp @@ -0,0 +1,667 @@ +/* + * MIT License + * + * Copyright (c) 2016-2019 xiongziliang <771730766@qq.com> + * + * This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include "Util/MD5.h" +#include "Util/File.h" +#include "Util/logger.h" +#include "Util/SSLBox.h" +#include "Util/onceToken.h" +#include "Util/CMD.h" +#include "Network/TcpServer.h" +#include "Poller/EventPoller.h" +#include "Common/config.h" +#include "Rtsp/UDPServer.h" +#include "Rtsp/RtspSession.h" +#include "Rtmp/RtmpSession.h" +#include "Shell/ShellSession.h" +#include "Rtmp/FlvMuxer.h" +#include "Player/PlayerProxy.h" +#include "Http/WebSocketSession.h" +#include "WebApi.h" +#include "WebHook.h" + +#include "MiniDumper.h" +#include "MediaServer.h" + +using namespace std; +using namespace toolkit; +using namespace mediakit; + +namespace mediakit { +typedef enum { rConsole = 0, rServer, rInstall, rUninstall} RunMode; +////////////HTTP配置/////////// +namespace Http { +#define HTTP_FIELD "http." +#define HTTP_PORT 10080 +const string kPort = HTTP_FIELD"port"; +#define HTTPS_PORT 10443 +const string kSSLPort = HTTP_FIELD"sslport"; +onceToken token1([](){ + mINI::Instance()[kPort] = HTTP_PORT; + mINI::Instance()[kSSLPort] = HTTPS_PORT; +},nullptr); +}//namespace Http + +////////////SHELL配置/////////// +namespace Shell { +#define SHELL_FIELD "shell." +#define SHELL_PORT 9000 +const string kPort = SHELL_FIELD"port"; +onceToken token1([](){ + mINI::Instance()[kPort] = SHELL_PORT; +},nullptr); +} //namespace Shell + +////////////RTSP服务器配置/////////// +namespace Rtsp { +#define RTSP_FIELD "rtsp." +#define RTSP_PORT 10554 +#define RTSPS_PORT 322 +const string kPort = RTSP_FIELD"port"; +const string kSSLPort = RTSP_FIELD"sslport"; +onceToken token1([](){ + mINI::Instance()[kPort] = RTSP_PORT; + mINI::Instance()[kSSLPort] = RTSPS_PORT; +},nullptr); + +} //namespace Rtsp + +////////////RTMP服务器配置/////////// +namespace Rtmp { +#define RTMP_FIELD "rtmp." +#define RTMP_PORT 10935 +const string kPort = RTMP_FIELD"port"; +onceToken token1([](){ + mINI::Instance()[kPort] = RTMP_PORT; +},nullptr); +} //namespace RTMP +} // namespace mediakit + +class CMD_main; + +static MediaServer_ServerState sServerState = MediaServerStartingUpState; +const char* theXMLFilePath = 0; +char* theServerName = ""; +static SERVICE_STATUS_HANDLE sServiceStatusHandle = 0; + +static void ReportStatus(DWORD inCurrentState, DWORD inExitCode); +static void InstallService(char* inServiceName); +static void RemoveService(char *inServiceName); +static void RunAsService(char* inServiceName); +void WINAPI ServiceControl(DWORD); +void WINAPI ServiceMain(DWORD argc, LPTSTR *argv); + +MediaServer_ServerState StartServer(CMD_main& cmd_main); +void RunServer(); + +static void signalHandler(int signo) { + std::cerr << "Shutting down" << std::endl; + sServerState = MediaServerShuttingDownState; +} + +class CMD_main : public CMD { +public: + CMD_main() { + _parser.reset(new OptionParser(nullptr)); + + (*_parser) << Option('r',/*该选项简称,如果是\x00则说明无简称*/ + "runmode",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(rConsole).data(),/*该选项默认值*/ + true,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "控制台运行、服务方式运行、安装到服务中,从服务中卸载(0~3)",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/ + "level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(LTrace).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('m',/*该选项简称,如果是\x00则说明无简称*/ + "max_day",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + "30",/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "日志最多保存天数",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('c',/*该选项简称,如果是\x00则说明无简称*/ + "config",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + (exeDir() + "Config.ini").data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "配置文件路径",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('s',/*该选项简称,如果是\x00则说明无简称*/ + "ssl",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + (exeDir() + "ssl.p12").data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "ssl证书文件或文件夹,支持p12/pem类型",/*该选项说明文字*/ + nullptr); + + (*_parser) << Option('t',/*该选项简称,如果是\x00则说明无简称*/ + "threads",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/ + Option::ArgRequired,/*该选项后面必须跟值*/ + to_string(2*thread::hardware_concurrency()).data(),/*该选项默认值*/ + false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/ + "启动事件触发线程数",/*该选项说明文字*/ + nullptr); + } + + virtual ~CMD_main() {} + virtual const char *description() const { + return "主程序命令参数"; + } +}; + +//全局变量,在WebApi中用于保存配置文件用 +string g_ini_file; +CMD_main cmd_main; + +int main(int argc,char *argv[]) { + bool notAService = false; + + try { + cmd_main.operator()(argc, argv); + } catch (std::exception &ex) { + cout << ex.what() << endl; + return -1; + } + + g_ini_file = cmd_main["config"]; + RunMode rMode = (RunMode)cmd_main["runmode"].as(); + + switch (rMode) { + case rConsole: + notAService = true; + break; + case rServer: + break; + case rInstall: + ::InstallService("MediaServer"); + printf("Starting the MediaServer service...\n"); + ::RunAsService("MediaServer"); + ::exit(0); + break; + case rUninstall: + printf("Removing the MediaServer service...\n"); + ::RemoveService("MediaServer"); + ::exit(0); + break; + default: + ::exit(-1); + break; + + } + + if (notAService) { + if (signal(SIGINT, signalHandler) == SIG_ERR) { + std::cerr << "Couldn't install signal handler for SIGINT" << std::endl; + ::exit(-1); + } + if (signal(SIGTERM, signalHandler) == SIG_ERR) { + std::cerr << "Couldn't install signal handler for SIGTERM" << std::endl; + ::exit(-1); + } + + // If we're running off the command-line, don't do the service initiation crap.^M + sServerState = ::StartServer(cmd_main); // No stats update interval for now + if (sServerState != MediaServerFatalErrorState) { + ::RunServer(); + ::exit(0); + } + ::exit(-1); + } + SERVICE_TABLE_ENTRY dispatchTable[] = + { + { "", ServiceMain }, + { NULL, NULL } + }; + + // In case someone runs the server improperly, print out a friendly message. + printf("MediaServer must either be started from the DOS Console\n"); + printf("using the -r command-line option, or using the Service Control Manager\n\n"); + printf("Waiting for the Service Control Manager to start MediaServer...\n"); + BOOL theErr = ::StartServiceCtrlDispatcher(dispatchTable); + if (!theErr) + { + printf("Fatal Error: Couldn't start Service\n"); + ::exit(-1); + } + return 0; +} + +void __stdcall ServiceMain(DWORD /*argc*/, LPTSTR *argv) +{ + theServerName = argv[0]; + + sServiceStatusHandle = ::RegisterServiceCtrlHandler(theServerName, &ServiceControl); + if (sServiceStatusHandle == 0) + { + printf("Failure registering service handler"); + ::exit(-1); + } + + // + // Report our status + ::ReportStatus(SERVICE_START_PENDING, NO_ERROR); + + // Start & Run the server - no stats update interval for now + sServerState = ::StartServer(cmd_main); + if (sServerState != MediaServerFatalErrorState) + { + ::ReportStatus(SERVICE_RUNNING, NO_ERROR); + ::RunServer(); // This function won't return until the server has died + + // + // Ok, server is done... + ::ReportStatus(SERVICE_STOPPED, NO_ERROR); + ::exit(0); + } + else { + ::ReportStatus(SERVICE_STOPPED, ERROR_BAD_COMMAND); // I dunno... report some error + ::exit(-1); + } +} + +void WINAPI ServiceControl(DWORD inControlCode) +{ + DWORD theStatusReport = SERVICE_START_PENDING; + + switch (inControlCode) + { + // Stop the service. + // + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + { + if (sServerState == MediaServerStartingUpState) + break; + + // Signal the server to shut down. + sServerState = MediaServerShuttingDownState; + break; + } + case SERVICE_CONTROL_PAUSE: + { +#if 0 + if (sServerState != RunningState) + break; + + // Signal the server to refuse new connections. + theState = qtssRefusingConnectionsState; + if (theServer != NULL) + theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); +#endif + break; + } + case SERVICE_CONTROL_CONTINUE: + { +#if 0 + if (theState != qtssRefusingConnectionsState) + break; + + // Signal the server to refuse new connections. + theState = qtssRefusingConnectionsState; + if (theServer != NULL) + theServer->SetValue(qtssSvrState, 0, &theState, sizeof(theState)); +#endif + break; + } + case SERVICE_CONTROL_INTERROGATE: + break; // Just update our status + default: + break; + } + + + + + // Convert a QTSS state to a Win32 Service state^M + switch (sServerState) + { + case MediaServerStartingUpState: theStatusReport = SERVICE_START_PENDING; break; + case MediaServerRunningState: theStatusReport = SERVICE_RUNNING; break; + case MediaServerRefusingConnectionsState: theStatusReport = SERVICE_PAUSED; break; + case MediaServerFatalErrorState: theStatusReport = SERVICE_STOP_PENDING; break; + case MediaServerShuttingDownState: theStatusReport = SERVICE_STOP_PENDING; break; + default: theStatusReport = SERVICE_RUNNING; break; + } + + printf("Reporting status from ServiceControl function\n"); + ::ReportStatus(theStatusReport, NO_ERROR); +} + +void ReportStatus(DWORD inCurrentState, DWORD inExitCode) +{ + static bool sFirstTime = true; + static unsigned long sCheckpoint = 0; + static SERVICE_STATUS sStatus; + + if (sFirstTime) + { + sFirstTime = false; + + // Setup the status structure + sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + sStatus.dwCurrentState = SERVICE_START_PENDING; + sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN; + //sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + sStatus.dwWin32ExitCode = 0; + sStatus.dwServiceSpecificExitCode = 0; + sStatus.dwCheckPoint = 0; + sStatus.dwWaitHint = 0; + } + + if (sStatus.dwCurrentState == SERVICE_START_PENDING) + sStatus.dwCheckPoint = ++sCheckpoint; + else + sStatus.dwCheckPoint = 0; + + sStatus.dwCurrentState = inCurrentState; + sStatus.dwServiceSpecificExitCode = inExitCode; + BOOL theErr = SetServiceStatus(sServiceStatusHandle, &sStatus); + if (theErr == 0) + { + DWORD theerrvalue = ::GetLastError(); + } +} + +void RunAsService(char* inServiceName) +{ + SC_HANDLE theService; + SC_HANDLE theSCManager; + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + return; + + theService = ::OpenService( + theSCManager, // SCManager database + inServiceName, // name of service + SERVICE_ALL_ACCESS); + + SERVICE_STATUS lpServiceStatus; + + if (theService) + { + const int kNotRunning = 1062; + bool stopped = ::ControlService(theService, SERVICE_CONTROL_STOP, &lpServiceStatus); + if (!stopped && ((int) ::GetLastError() != kNotRunning)) + printf("Stopping Service Error: %d\n", ::GetLastError()); + + bool started = ::StartService(theService, 0, NULL); + if (!started) + printf("Starting Service Error: %d\n", ::GetLastError()); + + ::CloseServiceHandle(theService); + } + + ::CloseServiceHandle(theSCManager); +} + +void InstallService(char* inServiceName) +{ + SC_HANDLE theService; + SC_HANDLE theSCManager; + + TCHAR thePath[512]; + TCHAR theAppBaseName[512]; + TCHAR theQuotedPath[522]; + + BOOL theErr = ::GetModuleFileName(NULL, thePath, 512); + if (!theErr) + return; + + char* pSlash = strrchr((char*)thePath, '\\'); + if (pSlash) { + strcpy((char*)theAppBaseName, (const char*)pSlash + 1); + *(pSlash + 1) = 0; + } + + sprintf(theQuotedPath, "\"%s%s\" -r 1 -c \"%s\"", thePath, theAppBaseName, g_ini_file.c_str()); + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + { + printf("Failed to install Service\n"); + return; + } + + theService = CreateService( + theSCManager, // SCManager database + inServiceName, // name of service + inServiceName, // name to display + SERVICE_ALL_ACCESS, // desired access + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + theQuotedPath, // service's binary + NULL, // no load ordering group + NULL, // no tag identifier + NULL, // dependencies + NULL, // LocalSystem account + NULL); // no password + + if (theService) + { + SERVICE_DESCRIPTION desc; + desc.lpDescription = "流媒体"; + if (ChangeServiceConfig2(theService, SERVICE_CONFIG_DESCRIPTION, &desc)) + { + printf("Added MediaServer Service Description\n"); + } + + SERVICE_DELAYED_AUTO_START_INFO info = { true }; + + if (ChangeServiceConfig2(theService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &info)) + { + printf("Added MediaServer Service Delayed Auto Start\n"); + } + + SERVICE_FAILURE_ACTIONS failact = { 0 }; + SC_ACTION act[3]; + act[0].Delay = act[1].Delay = act[2].Delay = 1 * 1000; + act[0].Type = act[1].Type = act[2].Type = SC_ACTION_RESTART; + failact.cActions = 3; + failact.lpsaActions = act; + failact.dwResetPeriod = 0; + if (ChangeServiceConfig2(theService, SERVICE_CONFIG_FAILURE_ACTIONS, &failact)) + { + printf("Seted MediaServer Service failure actions\n"); + } + ::CloseServiceHandle(theService); + printf("Installed MediaServer Service\n"); + } + else + printf("Failed to install MediaServer Service\n"); + + ::CloseServiceHandle(theSCManager); +} + +void RemoveService(char *inServiceName) +{ + SC_HANDLE theSCManager; + SC_HANDLE theService; + SERVICE_STATUS sStatus; + + theSCManager = ::OpenSCManager( + NULL, // machine (NULL == local) + NULL, // database (NULL == default) + SC_MANAGER_ALL_ACCESS // access required + ); + if (!theSCManager) + { + printf("Failed to remove MediaServer Service\n"); + return; + } + + theService = ::OpenService(theSCManager, inServiceName, SERVICE_ALL_ACCESS); + if (theService) { + if (::ControlService(theService, SERVICE_CONTROL_STOP, &sStatus)) { + printf("Stopping Service: %s", inServiceName); + Sleep(1000); + + while (::QueryServiceStatus(theService, &sStatus)) { + if (SERVICE_STOP_PENDING == sStatus.dwCurrentState) { + printf("."); + Sleep(1000); + } + else + break; + } + if (SERVICE_STOPPED == sStatus.dwCurrentState) + printf("%s stopped.\n", inServiceName); + else + printf("%s failed to stopp.\n", inServiceName); + } + if (::DeleteService(theService)) + printf("Removed MediaServer Service!\n"); + else + printf("Remove MediaServer Service failed!\n"); + ::CloseServiceHandle(theService); + } + else + printf("Failed to remove iDAS Service\n"); + ::CloseServiceHandle(theSCManager); +} + +MediaServer_ServerState StartServer(CMD_main& cmd_main) { + MediaServer_ServerState theServerState = MediaServerStartingUpState; + LogLevel logLevel = (LogLevel)cmd_main["level"].as(); + logLevel = MIN(MAX(logLevel, LTrace), LError); + string ssl_file = cmd_main["ssl"]; + int threads = cmd_main["threads"]; + + //设置日志 + Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); +#ifndef ANDROID + auto fileChannel = std::make_shared("FileChannel", exeDir() + "log/", logLevel); + //日志最多保存天数 + fileChannel->setMaxDay(cmd_main["max_day"]); + Logger::Instance().add(fileChannel); +#endif// + + //启动异步日志线程 + Logger::Instance().setWriter(std::make_shared()); + //加载配置文件,如果配置文件不存在就创建一个 + loadIniConfig(g_ini_file.data()); + + if (!File::is_dir(ssl_file.data())) { + //不是文件夹,加载证书,证书包含公钥和私钥 + SSL_Initor::Instance().loadCertificate(ssl_file.data()); + } + else { + //加载文件夹下的所有证书 + File::scanDir(ssl_file, [](const string &path, bool isDir) { + if (!isDir) { + //最后的一个证书会当做默认证书(客户端ssl握手时未指定主机) + SSL_Initor::Instance().loadCertificate(path.data()); + } + return true; + }); + } + + uint16_t shellPort = mINI::Instance()[Shell::kPort]; + uint16_t rtspPort = mINI::Instance()[Rtsp::kPort]; + uint16_t rtspsPort = mINI::Instance()[Rtsp::kSSLPort]; + uint16_t rtmpPort = mINI::Instance()[Rtmp::kPort]; + uint16_t httpPort = mINI::Instance()[Http::kPort]; + uint16_t httpsPort = mINI::Instance()[Http::kSSLPort]; + + //设置poller线程数,该函数必须在使用ZLToolKit网络相关对象之前调用才能生效 + EventPollerPool::setPoolSize(threads); + + //简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象 + //测试方法:telnet 127.0.0.1 9000 + TcpServer::Ptr shellSrv(new TcpServer()); + TcpServer::Ptr rtspSrv(new TcpServer()); + TcpServer::Ptr rtmpSrv(new TcpServer()); + TcpServer::Ptr httpSrv(new TcpServer()); + + shellSrv->start(shellPort); + rtspSrv->start(rtspPort);//默认10554 + rtmpSrv->start(rtmpPort);//默认10935 + //http服务器 + httpSrv->start(httpPort);//默认80 + + //如果支持ssl,还可以开启https服务器 + TcpServer::Ptr httpsSrv(new TcpServer()); + //https服务器,支持websocket + httpsSrv->start(httpsPort);//默认443 + + //支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问 + TcpServer::Ptr rtspSSLSrv(new TcpServer()); + rtspSSLSrv->start(rtspsPort);//默认322 + + installWebApi(); + InfoL << "已启动http api 接口"; + installWebHook(); + InfoL << "已启动http hook 接口"; + theServerState = MediaServerRunningState; + + return theServerState; +} + +void RunServer() { + +#ifdef WIN32 + CMiniDumper _miniDumper(true); +#endif + + while ((sServerState != MediaServerShuttingDownState) && + (sServerState != MediaServerFatalErrorState)) { +#ifdef WIN32 + Sleep(1000); +#else + usleep(1000 * 1000); +#endif + } + + unInstallWebApi(); + unInstallWebHook(); + //休眠1秒再退出,防止资源释放顺序错误 + InfoL << "程序退出中,请等待..."; + sleep(1); + InfoL << "程序退出完毕!"; +} \ No newline at end of file