增加windows系统服务
This commit is contained in:
parent
3554fe920f
commit
5bc7116e2a
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tchar.h>
|
||||
#include <strsafe.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <strsafe.h>
|
||||
#include <dbghelp.h>
|
||||
#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
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef MINIDUMPER_H
|
||||
#define MINIDUMPER_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
||||
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
|
||||
|
|
@ -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 <map>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#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<int>();
|
||||
|
||||
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<int>();
|
||||
logLevel = MIN(MAX(logLevel, LTrace), LError);
|
||||
string ssl_file = cmd_main["ssl"];
|
||||
int threads = cmd_main["threads"];
|
||||
|
||||
//设置日志
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel", logLevel));
|
||||
#ifndef ANDROID
|
||||
auto fileChannel = std::make_shared<FileChannel>("FileChannel", exeDir() + "log/", logLevel);
|
||||
//日志最多保存天数
|
||||
fileChannel->setMaxDay(cmd_main["max_day"]);
|
||||
Logger::Instance().add(fileChannel);
|
||||
#endif//
|
||||
|
||||
//启动异步日志线程
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
//加载配置文件,如果配置文件不存在就创建一个
|
||||
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<ShellSession>(shellPort);
|
||||
rtspSrv->start<RtspSession>(rtspPort);//默认10554
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);//默认10935
|
||||
//http服务器
|
||||
httpSrv->start<HttpSession>(httpPort);//默认80
|
||||
|
||||
//如果支持ssl,还可以开启https服务器
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
//https服务器,支持websocket
|
||||
httpsSrv->start<HttpsSession>(httpsPort);//默认443
|
||||
|
||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(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 << "程序退出完毕!";
|
||||
}
|
||||
Loading…
Reference in New Issue