main函数入口在server/main.cpp中
int main(int argc,char *argv[]) {
return start_main(argc,argv);
}
可以看出,它实际上是调用了start_main函数。
start_main又做了什么呢? 第一步,它解析函数入口参数 CMD_main cmd_main;
try {
cmd_main.operator()(argc, argv);
} catch (ExitException &) {
return 0;
} catch (std::exception &ex) {
cout << ex.what() << endl;
return -1;
}
一般都是直接使用默认的(不传参数),如下:
bool bDaemon = cmd_main.hasKey("daemon");
LogLevel logLevel = (LogLevel) cmd_main["level"].as();
logLevel = MIN(MAX(logLevel, LTrace), LError);
g_ini_file = cmd_main["config"];
string ssl_file = cmd_main["ssl"];
int threads = cmd_main["threads"];
第二步:设置日志输出到控制台和文件
Logger::Instance().add(std::make_shared("ConsoleChannel", logLevel)); auto fileChannel = std::make_shared ("FileChannel", exeDir() + "log/", logLevel); fileChannel->setMaxDay(cmd_main["max_day"]); //日志最多保存天数 Logger::Instance().add(fileChannel);
exeDir()为二进制当前程序位置。
感兴趣的话可以看下exeDir()是怎么实现的,如下:
string exePath(bool isExe ) {
char buffer[PATH_MAX * 2 + 1] = {0};
int n = -1;
n = readlink("/proc/self/exe", buffer, sizeof(buffer));
string filePath;
if (n <= 0) {
filePath = "./";
} else {
filePath = buffer;
}
return filePath;
}
原理:linux系统中有个符号链接:/proc/self/exe 它代表当前程序,所以可以用readlink读取它的源路径就可以获取当前程序的绝对路径
第三步: 设置如果崩溃的话就开启捕获崩溃System::systemSetup();
看下它是怎么实现的
(1)首先解开进程资源限制
struct rlimit rlim,rlim_new;
if (getrlimit(RLIMIT_CORE, &rlim)==0) { //core文件的最大字节数
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY; // 不限制
if (setrlimit(RLIMIT_CORE, &rlim_new)!=0) { // 设置为不限制
rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
setrlimit(RLIMIT_CORE, &rlim_new);
}
InfoL << "core文件大小设置为:" << rlim_new.rlim_cur;
}
if (getrlimit(RLIMIT_NOFILE, &rlim)==0) { // 获取进程默认可以打开的最大文件描述符数
rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY; //设置为不限制
if (setrlimit(RLIMIT_NOFILE, &rlim_new)!=0) {
rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
setrlimit(RLIMIT_NOFILE, &rlim_new);
}
InfoL << "文件最大描述符个数设置为:" << rlim_new.rlim_cur;
}
2、然后捕获异常信号:SIGSEGV和SIGABRT
signal(SIGSEGV, sig_crash);
signal(SIGABRT, sig_crash);
当有信号发生时,自动调用sig_crash,这里的效果是
- 第一次发生SIGSEGV或者SIGABRT异常时,就调用sig_crash函数。
- sig_crash函数里面会读取异常的栈帧,并使用addr2line找出是哪一行发生了异常,并压入一个vector里面,然后使用 NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack);通知程序有异常发生了,就会进入下面第三步
- NoticeCenter::Instance().addListener监听异常事件,当异常发生时,就将响应栈帧情况写入一个[程序路径/crash.pid]文件中
NoticeCenter::Instance().addListener(nullptr,kBroadcastOnCrashDump,[](BroadcastOnCrashDumpArgs){
stringstream ss;
ss << "## crash date:" << getTimeStr("%Y-%m-%d %H:%M:%S") << endl;
ss << "## exe: " << exeName() << endl;
ss << "## signal: " << sig << endl;
ss << "## stack: " << endl;
for (size_t i = 0; i < stack.size(); ++i) {
ss << "[" << i << "]: ";
for (auto &str : stack[i]){
ss << str << endl;
}
}
string stack_info = ss.str();
ofstream out(StrPrinter << exeDir() << "/crash." << getpid(), ios::out | ios::binary | ios::trunc);
out << stack_info;
out.flush();
cerr << stack_info << endl;
});
4. 下一次相同异常发生时,不会捕获了,而是直接崩溃。因为异常不会函数sig_crash在第一次被调用时设置了
signal(sig, SIG_DFL); //恢复默认行为第四步:读取配置文件
loadIniConfig(g_ini_file.data());
默认读取:
第五步:加载证书 //不是文件夹,加载证书,证书包含公钥和私钥
SSL_Initor::Instance().loadCertificate(ssl_file.data());
默认加载:
根据刚刚读到的配置开启各个服务,注意,如果某个服务开启失败,就会退出当前程序
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 rtmpsPort = mINI::Instance()[Rtmp::kSSLPort];
uint16_t httpPort = mINI::Instance()[Http::kPort];
uint16_t httpsPort = mINI::Instance()[Http::kSSLPort];
uint16_t rtpPort = mINI::Instance()[RtpProxy::kPort];
//简单的telnet服务器,可用于服务器调试,但是不能使用23端口,否则telnet上了莫名其妙的现象
//测试方法:telnet 127.0.0.1 9000
auto shellSrv = std::make_shared();
//rtsp[s]服务器, 可用于诸如亚马逊echo show这样的设备访问
auto rtspSrv = std::make_shared();;
auto rtspSSLSrv = std::make_shared();;
//rtmp[s]服务器
auto rtmpSrv = std::make_shared();;
auto rtmpsSrv = std::make_shared();;
//http[s]服务器
auto httpSrv = std::make_shared();;
auto httpsSrv = std::make_shared();;
//GB28181 rtp推流端口,支持UDP/TCP
auto rtpServer = std::make_shared();
try {
//rtsp服务器,端口默认554
if (rtspPort) { rtspSrv->start(rtspPort); }
//rtsps服务器,端口默认322
if (rtspsPort) { rtspSSLSrv->start(rtspsPort); }
//rtmp服务器,端口默认1935
if (rtmpPort) { rtmpSrv->start(rtmpPort); }
//rtmps服务器,端口默认19350
if (rtmpsPort) { rtmpsSrv->start(rtmpsPort); }
//http服务器,端口默认80
if (httpPort) { httpSrv->start(httpPort); }
//https服务器,端口默认443
if (httpsPort) { httpsSrv->start(httpsPort); }
//telnet远程调试服务器
if (shellPort) { shellSrv->start(shellPort); }
//创建rtp服务器
if (rtpPort) { rtpServer->start(rtpPort); }
} catch (std::exception &ex) {
WarnL << "端口占用或无权限:" << ex.what() << endl;
ErrorL << "程序启动失败,请修改配置文件中端口号后重试!" << endl;
sleep(1);
#endif
return -1;
}
第七步:设置http相关接口
注意,http服务在第六步就已经打开了
installWebApi();
InfoL << "已启动http api 接口";
installWebHook();
InfoL << "已启动http hook 接口";
第八步:设置一些信号
- 配置文件热更新
signal(SIGHUP, [](int) { mediakit::loadIniConfig(g_ini_file.data()); });
- 等待ctrl + c时,打印SIGINT:exit,然后退出
//设置退出信号处理函数
static semaphore sem;
signal(SIGINT, [](int) {
InfoL << "SIGINT:exit";
signal(SIGINT, SIG_IGN);// 设置退出信号
sem.post();
});// 设置退出信号
sem.wait(); //阻塞等待
第九步:销毁资源
unInstallWebApi();
unInstallWebHook();
//休眠1秒再退出,防止资源释放顺序错误
InfoL << "程序退出中,请等待...";
sleep(1);
InfoL << "程序退出完毕!";
installWebApi()在干啥?
做了三件事:
(1)简单来说就是:就是监听是否有HTTP请求事件到来
addHttpListener()
(2)可以理解为:string api_secret = 从配置文件找到api.secret的内容
#define API_FIELD "api." const string kSecret = API_FIELD"secret"; GET_CONFIG(string,api_secret,API::kSecret);
(3)注册相应API和事件到来时的回调函数
//获取线程负载
//测试url http://127.0.0.1/index/api/getThreadsLoad
api_regist("/index/api/getThreadsLoad", .....
//获取后台工作线程负载
//测试url http://127.0.0.1/index/api/getWorkThreadsLoad
api_regist("/index/api/getWorkThreadsLoad",
//获取服务器配置
//测试url http://127.0.0.1/index/api/getServerConfig
api_regist("/index/api/getServerConfig"
......
实现原理
关于addHttpListener
static inline void addHttpListener(){
GET_CONFIG(bool, api_debug, API::kApiDebug);
//注册监听kBroadcastHttpRequest事件
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastHttpRequest ....
关于GET_CONFIG是怎么实现的
#define GET_CONFIG(type, arg, key)
static type arg = ::toolkit::mINI::Instance()[key];
LISTEN_RELOAD_KEY(arg, key, {
RELOAD_KEY(arg, key);
});
::toolkit::mINI::Instance()可以简单理解为一个map,所以上面第二行相当于:
static std::string api_secret = map[ API::kSecret];
下面两行的功能是: 监听相应key是否有变化,如果变化了,就重新更新api_secret 。 具体原理稍后解释
api_regist怎么实现的?随便找一个
void api_regist(const string &api_path, const function&func) { s_map_api.emplace(api_path, toApi(func)); }
发现它是不断的往s_map_api压数据,那s_map_api又是个什么东西呢?
using HttpApi = function; //http api列表 static map s_map_api;
可以看出它是一个map,其key是字符串,value是一个函数指针。 另外,是一个重载函数,它可以将不同的回调函数都转成HttpAPI格式的指针,作为这个map的value。具体的转换工作是由 toApi(func)干的
#define HTTP_FIELD "http." const string kCharSet = HTTP_FIELD"charSet"; static HttpApi toApi(const functioninstallWebHook的工作? 做了三件事&cb) { return [cb](const Parser &parser, const HttpSession::HttpResponseInvoker &invoker, SockInfo &sender) { ....... }; }
(1)从配置文件中获取hook.enable和hook,admin_params
- hook.enable默认为 false
- hook,admin_params默认为 secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc
GET_CONFIG(bool,hook_enable,Hook::kEnable);
GET_CONFIG(string,hook_adminparams,Hook::kAdminParams);
配置文件内容:
[hook] #在推流时,如果url参数匹对admin_params,那么可以不经过hook鉴权直接推流成功,播放时亦然 #该配置项的目的是为了开发者自己调试测试,该参数暴露后会有泄露隐私的安全隐患 admin_params=secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=0
(2)监听各种事件
//监收到rtsp/rtmp推流事件广播,通过该事件控制推流鉴权 NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPublish, ...... //播放rtsp/rtmp/http-flv事件广播,通过该事件控制播放鉴权 NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPlayed .....
(3)告诉某台主机【我】已经启动了,并定时上报保活
//上报服务器启动
reportServerStarted();
//定时上报保活
reportServerKeepalive();
实现原理
reportServerStarted:上班报前服务器启动
(1)读取相关配置
GET_CONFIG(bool,hook_enable,Hook::kEnable);
GET_CONFIG(string,hook_server_started,Hook::kOnServerStarted);
if(!hook_enable || hook_server_started.empty()){
return;
}
配置文件相关内容如下:
[hook] #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=1 #服务器启动报告,可以用于服务器的崩溃重启事件监听 on_server_started=https://127.0.0.1/index/hook/on_server_started
如果要继续执行,enable必须为1&&on_server_started不为空
(2)将配置文件中所有配置转成一个json
ArgsType body;
for (auto &pr : mINI::Instance()) {
body[pr.first] = (string &) pr.second;
}
(3)执行do_http_hook
do_http_hook(hook_server_started,body, nullptr);
do_http_hook就是把body发送到hook_server_started指定的URL上
do_http_hook实现:发送HTTP请求(1)读取相关配置
GET_CONFIG(string, mediaServerId, General::kMediaServerId);
GET_CONFIG(float, hook_timeoutSec, Hook::kTimeoutSec);
内容如下:
[hook] #hook api最大等待回复时间,单位秒 timeoutSec=10 [general] #流媒体服务器的ID(GUID),用于触发hook时区别是哪台服务器 mediaServerId=your_server_id
值得说明一下的是mediaServerId,它不需要自己去配置,而是在程序启动时会随机生成一个标识当前服务器
#define GENERAL_FIELD "general."
const string kMediaServerId = GENERAL_FIELD"mediaServerId";
......
static onceToken token([](){
.....
mINI::Instance()[kMediaServerId] = makeRandStr(16);
(2)使用刚刚读到的mediaServerId修改body,表示当前是哪台服务器上报
const_cast(body)["mediaServerId"] = mediaServerId;
(3)生成一个Http请求头,开始上报
(4)具体是怎么上报的我们先不管,也就是不关心requester->startRequester的实现原理。这里我们需要知道的是如果这个请求发送完毕之后(成功、失败、超时),就会自己去执行startRequester的第二个参数,它是一个lambda回调
requester->startRequester(....., [url, func, bodyStr, requester, pTicker](const SockException &ex,
它做了两件事情:
(4.1)一些清理工作
onceToken token(nullptr, [&]() mutable{
requester.reset();
});
(4.2)解析响应,并打印
parse_http_response(ex, res, [&](const Value &obj, const string &err) {
if (!err.empty()) {
WarnL << "hook " << url << " " << pTicker->elapsedTime() << "ms,failed" << err << ":" << bodyStr;
} else if (pTicker->elapsedTime() > 500) {
DebugL << "hook " << url << " " << pTicker->elapsedTime() << "ms,success:" << bodyStr;
}
});
reportServerKeepalive:保活
(1)读取配置相关
GET_CONFIG(bool, hook_enable, Hook::kEnable);
GET_CONFIG(string, hook_server_keepalive, Hook::kOnServerKeepalive);
if (!hook_enable || hook_server_keepalive.empty()) {
return;
}
配置文件相关内容如下:
[hook] #是否启用hook事件,启用后,推拉流都将进行鉴权 enable=1 #服务器启动报告,可以用于服务器的崩溃重启事件监听 on_server_started=https://127.0.0.1/index/hook/on_server_started
如果要继续执行,enable必须为1&&on_server_started不为空
(2)生成一个定时任务
GET_CONFIG(float, alive_interval, Hook::kAliveInterval);
g_keepalive_timer = std::make_shared(alive_interval, []() {
getStatisticJson([](const Value &data) mutable {
ArgsType body;
body["data"] = data;
//执行hook
do_http_hook(hook_server_keepalive, body, nullptr);
});
return true;
}, nullptr);
配置文件相关内容如下:
[hook] #keepalive hook触发间隔,单位秒,float类型 alive_interval=10.0
当定时任务触发时,会定时发送一些统计数据到指定URL
void getStatisticJson(const function&cb) { auto obj = std::make_shared (objectValue); auto &val = *obj; val["MediaSource"] = (Json::UInt64)(ObjectStatistic ::count()); val["MultiMediaSourceMuxer"] = (Json::UInt64)(ObjectStatistic ::count()); val["TcpServer"] = (Json::UInt64)(ObjectStatistic ::count()); val["TcpSession"] = (Json::UInt64)(ObjectStatistic ::count()); val["UdpServer"] = (Json::UInt64)(ObjectStatistic ::count()); val["UdpSession"] = (Json::UInt64)(ObjectStatistic ::count()); val["TcpClient"] = (Json::UInt64)(ObjectStatistic ::count()); val["Socket"] = (Json::UInt64)(ObjectStatistic ::count()); val["FrameImp"] = (Json::UInt64)(ObjectStatistic ::count()); val["Frame"] = (Json::UInt64)(ObjectStatistic::count()); val["Buffer"] = (Json::UInt64)(ObjectStatistic ::count()); val["BufferRaw"] = (Json::UInt64)(ObjectStatistic ::count()); val["BufferLikeString"] = (Json::UInt64)(ObjectStatistic ::count()); val["BufferList"] = (Json::UInt64)(ObjectStatistic ::count()); val["RtpPacket"] = (Json::UInt64)(ObjectStatistic ::count()); val["RtmpPacket"] = (Json::UInt64)(ObjectStatistic ::count()); cb(*obj); }



