mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-03-06 06:00:54 +08:00
添加完整的服务器用例
This commit is contained in:
9
server/CMakeLists.txt
Normal file
9
server/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
include_directories(../3rdpart)
|
||||
file(GLOB jsoncpp_src_list ../3rdpart/jsoncpp/*.cpp ../3rdpart/jsoncpp/*.h )
|
||||
add_library(jsoncpp STATIC ${jsoncpp_src_list})
|
||||
|
||||
file(GLOB MediaServer_src_list ./*.cpp ./*.h)
|
||||
add_executable(MediaServer ${MediaServer_src_list})
|
||||
target_link_libraries(MediaServer jsoncpp ${LINK_LIB_LIST})
|
||||
|
||||
|
||||
132
server/Process.cpp
Normal file
132
server/Process.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Created by xzl on 2018/5/24.
|
||||
//
|
||||
#include <unistd.h>
|
||||
#include <stdexcept>
|
||||
#include <signal.h>
|
||||
#include "Util/util.h"
|
||||
#include "Util/File.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "Util/TimeTicker.h"
|
||||
#include "Process.h"
|
||||
#include "Poller/Timer.h"
|
||||
using namespace toolkit;
|
||||
|
||||
void Process::run(const string &cmd, const string &log_file_tmp) {
|
||||
kill(2000);
|
||||
_pid = fork();
|
||||
if (_pid < 0) {
|
||||
throw std::runtime_error(StrPrinter << "fork child process falied,err:" << get_uv_errmsg());
|
||||
}
|
||||
if (_pid == 0) {
|
||||
//子进程
|
||||
// ignore the SIGINT and SIGTERM
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
|
||||
string log_file ;
|
||||
if(log_file_tmp.empty()){
|
||||
log_file = "/dev/null";
|
||||
}else{
|
||||
log_file = StrPrinter << log_file_tmp << "." << getpid();
|
||||
}
|
||||
|
||||
int log_fd = -1;
|
||||
int flags = O_CREAT | O_WRONLY | O_APPEND;
|
||||
mode_t mode = S_IRWXO | S_IRWXG | S_IRWXU;// S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
|
||||
File::createfile_path(log_file.data(), mode);
|
||||
if ((log_fd = ::open(log_file.c_str(), flags, mode)) < 0) {
|
||||
fprintf(stderr, "open log file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||
} else {
|
||||
// dup to stdout and stderr.
|
||||
if (dup2(log_fd, STDOUT_FILENO) < 0) {
|
||||
fprintf(stderr, "dup2 stdout file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||
}
|
||||
if (dup2(log_fd, STDERR_FILENO) < 0) {
|
||||
fprintf(stderr, "dup2 stderr file %s failed:%d(%s)\r\n", log_file.data(), errno, strerror(errno));
|
||||
}
|
||||
// close log fd
|
||||
::close(log_fd);
|
||||
}
|
||||
fprintf(stderr, "\r\n\r\n#### pid=%d,cmd=%s #####\r\n\r\n", getpid(), cmd.data());
|
||||
|
||||
// close other fds
|
||||
// TODO: do in right way.
|
||||
for (int i = 3; i < 1024; i++) {
|
||||
::close(i);
|
||||
}
|
||||
|
||||
auto params = split(cmd, " ");
|
||||
// memory leak in child process, it's ok.
|
||||
char **charpv_params = new char *[params.size() + 1];
|
||||
for (int i = 0; i < (int) params.size(); i++) {
|
||||
std::string &p = params[i];
|
||||
charpv_params[i] = (char *) p.data();
|
||||
}
|
||||
// EOF: NULL
|
||||
charpv_params[params.size()] = NULL;
|
||||
|
||||
// TODO: execv or execvp
|
||||
auto ret = execv(params[0].c_str(), charpv_params);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "fork process failed, errno=%d(%s)\r\n", errno, strerror(errno));
|
||||
}
|
||||
exit(ret);
|
||||
}
|
||||
|
||||
InfoL << "start child proces " << _pid;
|
||||
}
|
||||
|
||||
void Process::kill(int max_delay) {
|
||||
if (_pid <= 0) {
|
||||
return;
|
||||
}
|
||||
if (::kill(_pid, SIGTERM) == -1) {
|
||||
WarnL << "kill process " << _pid << " falied,err:" << get_uv_errmsg();
|
||||
} else {
|
||||
//等待子进程退出
|
||||
auto pid = _pid;
|
||||
EventPollerPool::Instance().getPoller()->doDelayTask(max_delay,[pid](){
|
||||
//最多等待2秒,2秒后强制杀掉程序
|
||||
if (waitpid(pid, NULL, WNOHANG) == 0) {
|
||||
::kill(pid, SIGKILL);
|
||||
WarnL << "force kill process " << pid;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
_pid = -1;
|
||||
}
|
||||
|
||||
Process::~Process() {
|
||||
kill(2000);
|
||||
}
|
||||
|
||||
Process::Process() {
|
||||
}
|
||||
|
||||
bool Process::wait(bool block) {
|
||||
if (_pid <= 0) {
|
||||
return false;
|
||||
}
|
||||
int status = 0;
|
||||
pid_t p = waitpid(_pid, &status, block ? 0 : WNOHANG);
|
||||
|
||||
_exit_code = (status & 0xFF00) >> 8;
|
||||
if (p < 0) {
|
||||
WarnL << "waitpid failed, pid=" << _pid << ", err=" << get_uv_errmsg();
|
||||
return false;
|
||||
}
|
||||
if (p > 0) {
|
||||
InfoL << "process terminated, pid=" << _pid << ", exit code=" << _exit_code;
|
||||
return false;
|
||||
}
|
||||
|
||||
//WarnL << "process is running, pid=" << _pid;
|
||||
return true;
|
||||
}
|
||||
|
||||
int Process::exit_code() {
|
||||
return _exit_code;
|
||||
}
|
||||
27
server/Process.h
Normal file
27
server/Process.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by xzl on 2018/5/24.
|
||||
//
|
||||
|
||||
#ifndef IPTV_PROCESS_H
|
||||
#define IPTV_PROCESS_H
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
class Process {
|
||||
public:
|
||||
Process();
|
||||
~Process();
|
||||
void run(const string &cmd,const string &log_file);
|
||||
void kill(int max_delay);
|
||||
bool wait(bool block = true);
|
||||
int exit_code();
|
||||
private:
|
||||
pid_t _pid = -1;
|
||||
int _exit_code = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif //IPTV_PROCESS_H
|
||||
377
server/System.cpp
Normal file
377
server/System.cpp
Normal file
@@ -0,0 +1,377 @@
|
||||
//
|
||||
// Created by xzl on 2018/9/5.
|
||||
//
|
||||
|
||||
#include "System.h"
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <limits.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <execinfo.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "Util/mini.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "System.h"
|
||||
#include "Util/uv_errno.h"
|
||||
#include "Util/CMD.h"
|
||||
#include "Util/MD5.h"
|
||||
using namespace toolkit;
|
||||
|
||||
const int MAX_STACK_FRAMES = 128;
|
||||
#define BroadcastOnCrashDumpArgs int &sig,const vector<vector<string> > &stack
|
||||
const char kBroadcastOnCrashDump[] = "kBroadcastOnCrashDump";
|
||||
|
||||
//#if defined(__MACH__) || defined(__APPLE__)
|
||||
//#define TEST_LINUX
|
||||
//#endif
|
||||
|
||||
vector<string> splitWithEmptyLine(const string &s, const char *delim) {
|
||||
vector<string> ret;
|
||||
int last = 0;
|
||||
int index = s.find(delim, last);
|
||||
while (index != string::npos) {
|
||||
ret.push_back(s.substr(last, index - last));
|
||||
last = index + strlen(delim);
|
||||
index = s.find(delim, last);
|
||||
}
|
||||
if (s.size() - last > 0) {
|
||||
ret.push_back(s.substr(last));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
map<string, mINI> splitTopStr(const string &cmd_str) {
|
||||
map<string, mINI> ret;
|
||||
auto lines = splitWithEmptyLine(cmd_str, "\n");
|
||||
int i = 0;
|
||||
for (auto &line : lines) {
|
||||
if(i++ < 1 && line.empty()){
|
||||
continue;
|
||||
}
|
||||
if (line.empty()) {
|
||||
break;
|
||||
}
|
||||
auto line_vec = split(line, ":");
|
||||
|
||||
if (line_vec.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
trim(line_vec[0], " \r\n\t");
|
||||
auto args_vec = split(line_vec[1], ",");
|
||||
for (auto &arg : args_vec) {
|
||||
auto arg_vec = split(trim(arg, " \r\n\t."), " ");
|
||||
if (arg_vec.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
ret[line_vec[0]].emplace(arg_vec[1], arg_vec[0]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool System::getSystemUsage(SystemUsage &usage) {
|
||||
try {
|
||||
#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX)
|
||||
string cmd_str;
|
||||
#if !defined(TEST_LINUX)
|
||||
cmd_str = System::execute("top -b -n 1");
|
||||
#else
|
||||
cmd_str = "top - 07:21:31 up 5:48, 2 users, load average: 0.03, 0.62, 0.54\n"
|
||||
"Tasks: 80 total, 1 running, 78 sleeping, 0 stopped, 1 zombie\n"
|
||||
"%Cpu(s): 0.8 us, 0.4 sy, 0.0 ni, 98.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\n"
|
||||
"KiB Mem: 2058500 total, 249100 used, 1809400 free, 19816 buffers\n"
|
||||
"KiB Swap: 1046524 total, 0 used, 1046524 free. 153012 cached Mem\n"
|
||||
"\n";
|
||||
#endif
|
||||
if (cmd_str.empty()) {
|
||||
WarnL << "System::execute(\"top -b -n 1\") return empty";
|
||||
return false;
|
||||
}
|
||||
auto topMap = splitTopStr(cmd_str);
|
||||
|
||||
usage.task_total = topMap["Tasks"]["total"];
|
||||
usage.task_running = topMap["Tasks"]["running"];
|
||||
usage.task_sleeping = topMap["Tasks"]["sleeping"];
|
||||
usage.task_stopped = topMap["Tasks"]["stopped"];
|
||||
|
||||
usage.cpu_user = topMap["%Cpu(s)"]["us"];
|
||||
usage.cpu_sys = topMap["%Cpu(s)"]["sy"];
|
||||
usage.cpu_idle = topMap["%Cpu(s)"]["id"];
|
||||
|
||||
usage.mem_total = topMap["KiB Mem"]["total"];
|
||||
usage.mem_free = topMap["KiB Mem"]["free"];
|
||||
usage.mem_used = topMap["KiB Mem"]["used"];
|
||||
return true;
|
||||
|
||||
#elif defined(__MACH__) || defined(__APPLE__)
|
||||
/*
|
||||
"Processes: 275 total, 2 running, 1 stuck, 272 sleeping, 1258 threads \n"
|
||||
"2018/09/12 10:41:32\n"
|
||||
"Load Avg: 2.06, 2.88, 2.86 \n"
|
||||
"CPU usage: 14.54% user, 25.45% sys, 60.0% idle \n"
|
||||
"SharedLibs: 117M resident, 37M data, 15M linkedit.\n"
|
||||
"MemRegions: 46648 total, 3654M resident, 62M private, 714M shared.\n"
|
||||
"PhysMem: 7809M used (1906M wired), 381M unused.\n"
|
||||
"VM: 751G vsize, 614M framework vsize, 0(0) swapins, 0(0) swapouts.\n"
|
||||
"Networks: packets: 502366/248M in, 408957/87M out.\n"
|
||||
"Disks: 349435/6037M read, 78622/2577M written.";
|
||||
*/
|
||||
|
||||
string cmd_str = System::execute("top -l 1");
|
||||
if(cmd_str.empty()){
|
||||
WarnL << "System::execute(\"top -n 1\") return empty";
|
||||
return false;
|
||||
}
|
||||
auto topMap = splitTopStr(cmd_str);
|
||||
usage.task_total = topMap["Processes"]["total"];
|
||||
usage.task_running = topMap["Processes"]["running"];
|
||||
usage.task_sleeping = topMap["Processes"]["sleeping"];
|
||||
usage.task_stopped = topMap["Processes"]["stuck"];
|
||||
|
||||
usage.cpu_user = topMap["CPU usage"]["user"];
|
||||
usage.cpu_sys = topMap["CPU usage"]["sys"];
|
||||
usage.cpu_idle = topMap["CPU usage"]["idle"];
|
||||
|
||||
usage.mem_free = topMap["PhysMem"]["unused"].as<uint32_t>() * 1024 * 1024;
|
||||
usage.mem_used = topMap["PhysMem"]["used"].as<uint32_t>() * 1024 * 1024;
|
||||
usage.mem_total = usage.mem_free + usage.mem_used;
|
||||
return true;
|
||||
#else
|
||||
WarnL << "System not supported";
|
||||
return false;
|
||||
#endif
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool System::getNetworkUsage(vector<NetworkUsage> &usage) {
|
||||
try {
|
||||
#if defined(__linux) || defined(__linux__) || defined(TEST_LINUX)
|
||||
string cmd_str;
|
||||
#if !defined(TEST_LINUX)
|
||||
cmd_str = System::execute("cat /proc/net/dev");
|
||||
#else
|
||||
cmd_str =
|
||||
"Inter-| Receive | Transmit\n"
|
||||
" face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n"
|
||||
" lo: 475978 7546 0 0 0 0 0 0 475978 7546 0 0 0 0 0 0\n"
|
||||
"enp3s0: 151747818 315136 0 0 0 0 0 145 1590783447 1124616 0 0 0 0 0 0";
|
||||
#endif
|
||||
if (cmd_str.empty()) {
|
||||
return false;
|
||||
}
|
||||
auto lines = split(cmd_str, "\n");
|
||||
int i = 0;
|
||||
vector<string> column_name_vec;
|
||||
vector<string> category_prefix_vec;
|
||||
for (auto &line : lines) {
|
||||
switch (i++) {
|
||||
case 0: {
|
||||
category_prefix_vec = split(line, "|");
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
auto category_suffix_vec = split(line, "|");
|
||||
int j = 0;
|
||||
for (auto &category_suffix : category_suffix_vec) {
|
||||
auto column_suffix_vec = split(category_suffix, " ");
|
||||
for (auto &column_suffix : column_suffix_vec) {
|
||||
column_name_vec.emplace_back(trim(category_prefix_vec[j]) + "-" + trim(column_suffix));
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
mINI valMap;
|
||||
auto vals = split(line, " ");
|
||||
int j = 0;
|
||||
for (auto &val : vals) {
|
||||
valMap[column_name_vec[j++]] = trim(val, " \r\n\t:");
|
||||
}
|
||||
usage.emplace_back(NetworkUsage());
|
||||
auto &ifrUsage = usage.back();
|
||||
ifrUsage.interface = valMap["Inter--face"];
|
||||
ifrUsage.recv_bytes = valMap["Receive-bytes"];
|
||||
ifrUsage.recv_packets = valMap["Receive-packets"];
|
||||
ifrUsage.snd_bytes = valMap["Transmit-bytes"];
|
||||
ifrUsage.snd_packets = valMap["Transmit-packets"];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
WarnL << "System not supported";
|
||||
return false;
|
||||
#endif
|
||||
} catch (std::exception &ex) {
|
||||
WarnL << ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool System::getTcpUsage(System::TcpUsage &usage) {
|
||||
usage.established = atoi(trim(System::execute("netstat -na|grep ESTABLISHED|wc -l")).data());
|
||||
usage.syn_recv = atoi(trim(System::execute("netstat -na|grep SYN_RECV|wc -l")).data());
|
||||
usage.time_wait = atoi(trim(System::execute("netstat -na|grep TIME_WAIT|wc -l")).data());
|
||||
usage.close_wait = atoi(trim(System::execute("netstat -na|grep CLOSE_WAIT|wc -l")).data());
|
||||
return true;
|
||||
}
|
||||
|
||||
string System::execute(const string &cmd) {
|
||||
// DebugL << cmd;
|
||||
FILE *fPipe = popen(cmd.data(), "r");
|
||||
if(!fPipe){
|
||||
return "";
|
||||
}
|
||||
string ret;
|
||||
char buff[1024] = {0};
|
||||
while(fgets(buff, sizeof(buff), fPipe)){
|
||||
ret.append(buff);
|
||||
}
|
||||
pclose(fPipe);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static string addr2line(const string &address) {
|
||||
string cmd = StrPrinter << "addr2line -e " << exePath() << " " << address;
|
||||
return System::execute(cmd);
|
||||
}
|
||||
|
||||
static void sig_crash(int sig) {
|
||||
signal(sig, SIG_DFL);
|
||||
void *array[MAX_STACK_FRAMES];
|
||||
int size = backtrace(array, MAX_STACK_FRAMES);
|
||||
char ** strings = backtrace_symbols(array, size);
|
||||
vector<vector<string> > stack(size);
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
auto &ref = stack[i];
|
||||
std::string symbol(strings[i]);
|
||||
ref.emplace_back(symbol);
|
||||
#if defined(__linux) || defined(__linux__)
|
||||
size_t pos1 = symbol.find_first_of("[");
|
||||
size_t pos2 = symbol.find_last_of("]");
|
||||
std::string address = symbol.substr(pos1 + 1, pos2 - pos1 - 1);
|
||||
ref.emplace_back(addr2line(address));
|
||||
#endif//__linux
|
||||
}
|
||||
free(strings);
|
||||
|
||||
NoticeCenter::Instance().emitEvent(kBroadcastOnCrashDump,sig,stack);
|
||||
}
|
||||
|
||||
|
||||
void System::startDaemon() {
|
||||
signal(SIGTTOU,SIG_IGN);
|
||||
signal(SIGTTIN,SIG_IGN);
|
||||
signal(SIGHUP,SIG_IGN);
|
||||
signal(SIGINT, [](int) {
|
||||
InfoL << "SIGINT:exit";
|
||||
signal(SIGINT,SIG_IGN);
|
||||
throw ExitException();
|
||||
});
|
||||
|
||||
do{
|
||||
auto pid = fork();
|
||||
if(pid == -1){
|
||||
WarnL << "fork失败:" << get_uv_errmsg();
|
||||
//休眠1秒再试
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
if(pid == 0){
|
||||
//子进程
|
||||
return;
|
||||
}
|
||||
|
||||
//父进程,监视子进程是否退出
|
||||
DebugL << "启动子进程:" << pid;
|
||||
do{
|
||||
try {
|
||||
int status = 0;
|
||||
if(waitpid(pid, &status, 0) >= 0) {
|
||||
WarnL << "子进程退出";
|
||||
//休眠1秒再启动子进程
|
||||
sleep(1);
|
||||
break;
|
||||
}
|
||||
DebugL << "waitpid被中断:" << get_uv_errmsg();
|
||||
}catch (ExitException &ex){
|
||||
WarnL << "收到主动退出信号,关闭父进程与子进程";
|
||||
//通知子进程主动退出
|
||||
kill(pid,SIGINT);
|
||||
//父进程主动退出
|
||||
exit(0);
|
||||
}
|
||||
}while (true);
|
||||
}while (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string currentDateTime(){
|
||||
time_t ts = time(NULL);
|
||||
std::tm tm_snapshot;
|
||||
localtime_r(&ts, &tm_snapshot);
|
||||
|
||||
char buffer[1024] = {0};
|
||||
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm_snapshot);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void System::systemSetup(){
|
||||
struct rlimit rlim,rlim_new;
|
||||
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
|
||||
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;
|
||||
}
|
||||
|
||||
signal(SIGSEGV, sig_crash);
|
||||
signal(SIGABRT, sig_crash);
|
||||
NoticeCenter::Instance().addListener(nullptr,kBroadcastOnCrashDump,[](BroadcastOnCrashDumpArgs){
|
||||
stringstream ss;
|
||||
ss << "## crash date:" << currentDateTime() << endl;
|
||||
ss << "## exe: " << exeName() << endl;
|
||||
ss << "## signal: " << sig << endl;
|
||||
ss << "## stack: " << endl;
|
||||
for (int 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;
|
||||
});
|
||||
}
|
||||
|
||||
68
server/System.h
Normal file
68
server/System.h
Normal file
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// Created by xzl on 2018/9/5.
|
||||
//
|
||||
|
||||
#ifndef IPTV_BASH_H
|
||||
#define IPTV_BASH_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Util/NoticeCenter.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
|
||||
class System {
|
||||
public:
|
||||
typedef struct {
|
||||
uint32_t task_total;
|
||||
uint32_t task_running;
|
||||
uint32_t task_sleeping;
|
||||
uint32_t task_stopped;
|
||||
|
||||
uint64_t mem_total;
|
||||
uint64_t mem_free;
|
||||
uint64_t mem_used;
|
||||
|
||||
float cpu_user;
|
||||
float cpu_sys;
|
||||
float cpu_idle;
|
||||
} SystemUsage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t recv_bytes;
|
||||
uint64_t recv_packets;
|
||||
uint64_t snd_bytes;
|
||||
uint64_t snd_packets;
|
||||
string interface;
|
||||
} NetworkUsage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t available;
|
||||
uint64_t used;
|
||||
float used_per;
|
||||
string mounted_on;
|
||||
string filesystem;
|
||||
bool mounted;
|
||||
} DiskUsage;
|
||||
|
||||
typedef struct {
|
||||
uint32_t established;
|
||||
uint32_t syn_recv;
|
||||
uint32_t time_wait;
|
||||
uint32_t close_wait;
|
||||
} TcpUsage;
|
||||
|
||||
static bool getSystemUsage(SystemUsage &usage);
|
||||
static bool getNetworkUsage(vector<NetworkUsage> &usage);
|
||||
static bool getTcpUsage(TcpUsage &usage);
|
||||
static string execute(const string &cmd);
|
||||
static void startDaemon();
|
||||
static void systemSetup();
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //IPTV_BASH_H
|
||||
324
server/WebApi.cpp
Normal file
324
server/WebApi.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
#include <signal.h>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include "jsoncpp/json.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Util/SqlPool.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Http/HttpSession.h"
|
||||
#include "Network/TcpServer.h"
|
||||
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
#define API_ARGS HttpSession::KeyValue &headerIn, \
|
||||
HttpSession::KeyValue &headerOut, \
|
||||
HttpSession::KeyValue &allArgs, \
|
||||
Json::Value &val
|
||||
|
||||
#define API_REGIST(field, name, ...) \
|
||||
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker){ \
|
||||
static auto lam = [&](API_ARGS) __VA_ARGS__ ; \
|
||||
lam(headerIn, headerOut, allArgs, val); \
|
||||
invoker("200 OK", headerOut, val.toStyledString()); \
|
||||
});
|
||||
|
||||
#define API_REGIST_INVOKER(field, name, ...) \
|
||||
s_map_api.emplace("/index/"#field"/"#name,[](API_ARGS,const HttpSession::HttpResponseInvoker &invoker) __VA_ARGS__);
|
||||
|
||||
//异步http api lambad定义
|
||||
typedef std::function<void(API_ARGS,const HttpSession::HttpResponseInvoker &invoker)> AsyncHttpApi;
|
||||
//api列表
|
||||
static map<string, AsyncHttpApi> s_map_api;
|
||||
|
||||
namespace API {
|
||||
typedef enum {
|
||||
SqlFailed = -200,
|
||||
AuthFailed = -100,
|
||||
OtherFailed = -1,
|
||||
Success = 0
|
||||
} ApiErr;
|
||||
|
||||
#define API_FIELD "api."
|
||||
const char kApiDebug[] = API_FIELD"apiDebug";
|
||||
static onceToken token([]() {
|
||||
mINI::Instance()[kApiDebug] = "0";
|
||||
});
|
||||
}//namespace API
|
||||
|
||||
class ApiRetException: public std::runtime_error {
|
||||
public:
|
||||
ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
|
||||
_code = code;
|
||||
}
|
||||
~ApiRetException() = default;
|
||||
int code(){ return _code; }
|
||||
private:
|
||||
int _code;
|
||||
};
|
||||
|
||||
class AuthException : public ApiRetException {
|
||||
public:
|
||||
AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
|
||||
~AuthException() = default;
|
||||
};
|
||||
|
||||
|
||||
//获取HTTP请求中url参数、content参数
|
||||
static HttpSession::KeyValue getAllArgs(const Parser &parser) {
|
||||
HttpSession::KeyValue allArgs;
|
||||
{
|
||||
//TraceL << parser.FullUrl() << "\r\n" << parser.Content();
|
||||
auto &urlArgs = parser.getUrlArgs();
|
||||
auto contentArgs = parser.parseArgs(parser.Content());
|
||||
for (auto &pr : contentArgs) {
|
||||
allArgs.emplace(pr.first, HttpSession::urlDecode(pr.second));
|
||||
}
|
||||
for (auto &pr : urlArgs) {
|
||||
allArgs.emplace(pr.first, HttpSession::urlDecode(pr.second));
|
||||
}
|
||||
}
|
||||
return allArgs;
|
||||
}
|
||||
|
||||
static inline void addHttpListener(){
|
||||
GET_CONFIG_AND_REGISTER(bool, api_debug, API::kApiDebug);
|
||||
//注册监听kBroadcastHttpRequest事件
|
||||
NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastHttpRequest, [](BroadcastHttpRequestArgs) {
|
||||
auto it = s_map_api.find(parser.Url());
|
||||
if (it == s_map_api.end()) {
|
||||
consumed = false;
|
||||
return;
|
||||
}
|
||||
//该api已被消费
|
||||
consumed = true;
|
||||
AsyncHttpApi api = it->second;
|
||||
//异步执行该api,防止阻塞NoticeCenter
|
||||
EventPollerPool::Instance().getExecutor()->async([api, parser, invoker]() {
|
||||
//执行API
|
||||
Json::Value val;
|
||||
val["code"] = API::Success;
|
||||
HttpSession::KeyValue &headerIn = parser.getValues();
|
||||
HttpSession::KeyValue headerOut;
|
||||
HttpSession::KeyValue allArgs = getAllArgs(parser);
|
||||
headerOut["Content-Type"] = "application/json; charset=utf-8";
|
||||
if(api_debug){
|
||||
auto newInvoker = [invoker,parser,allArgs](const string &codeOut,
|
||||
const HttpSession::KeyValue &headerOut,
|
||||
const string &contentOut){
|
||||
stringstream ss;
|
||||
for(auto &pr : allArgs ){
|
||||
ss << pr.first << " : " << pr.second << "\r\n";
|
||||
}
|
||||
|
||||
DebugL << "request:\r\n" << parser.Method() << " " << parser.FullUrl() << "\r\n"
|
||||
<< "content:\r\n" << parser.Content() << "\r\n"
|
||||
<< "args:\r\n" << ss.str()
|
||||
<< "response:\r\n"
|
||||
<< contentOut << "\r\n";
|
||||
|
||||
invoker(codeOut,headerOut,contentOut);
|
||||
};
|
||||
((HttpSession::HttpResponseInvoker &)invoker) = newInvoker;
|
||||
}
|
||||
|
||||
try {
|
||||
api(headerIn, headerOut, allArgs, val, invoker);
|
||||
} catch(AuthException &ex){
|
||||
val["code"] = API::AuthFailed;
|
||||
val["msg"] = ex.what();
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
} catch(ApiRetException &ex){
|
||||
val["code"] = ex.code();
|
||||
val["msg"] = ex.what();
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
} catch(SqlException &ex){
|
||||
val["code"] = API::SqlFailed;
|
||||
val["msg"] = StrPrinter << "操作数据库失败:" << ex.what() << ":" << ex.getSql();
|
||||
WarnL << ex.what() << ":" << ex.getSql();
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
} catch (std::exception &ex) {
|
||||
val["code"] = API::OtherFailed;
|
||||
val["msg"] = ex.what();
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//安装api接口
|
||||
void installWebApi() {
|
||||
addHttpListener();
|
||||
|
||||
/**
|
||||
* 获取线程负载
|
||||
*/
|
||||
API_REGIST_INVOKER(api, getThreadsLoad, {
|
||||
EventPollerPool::Instance().getExecutorDelay([invoker, headerOut](const vector<int> &vecDelay) {
|
||||
Value val;
|
||||
auto vec = EventPollerPool::Instance().getExecutorLoad();
|
||||
int i = 0;
|
||||
for (auto load : vec) {
|
||||
Value obj(objectValue);
|
||||
obj["load"] = load;
|
||||
obj["delay"] = vecDelay[i++];
|
||||
val["data"].append(obj);
|
||||
}
|
||||
invoker("200 OK", headerOut, val.toStyledString());
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取服务器配置
|
||||
*/
|
||||
API_REGIST(api, getServerConfig, {
|
||||
Value obj;
|
||||
for (auto &pr : mINI::Instance()) {
|
||||
obj[pr.first] = (string &) pr.second;
|
||||
}
|
||||
val["data"].append(obj);
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置服务器配置
|
||||
*/
|
||||
API_REGIST(api, setServerConfig, {
|
||||
auto &ini = mINI::Instance();
|
||||
int changed = 0;
|
||||
for (auto &pr : allArgs) {
|
||||
if (ini.find(pr.first) == ini.end()) {
|
||||
//没有这个key
|
||||
continue;
|
||||
}
|
||||
if (ini[pr.first] == pr.second) {
|
||||
continue;
|
||||
}
|
||||
ini[pr.first] = pr.second;
|
||||
//替换成功
|
||||
++changed;
|
||||
}
|
||||
if (changed > 0) {
|
||||
NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
|
||||
ini.dumpFile();
|
||||
}
|
||||
val["changed"] = changed;
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取服务器api列表
|
||||
*/
|
||||
API_REGIST(api,getApiList,{
|
||||
for(auto &pr : s_map_api){
|
||||
val["data"].append(pr.first);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 重启服务器
|
||||
*/
|
||||
API_REGIST(api,restartServer,{
|
||||
EventPollerPool::Instance().getPoller()->doDelayTask(1000,[](){
|
||||
//尝试正常退出
|
||||
::kill(getpid(), SIGINT);
|
||||
|
||||
//3秒后强制退出
|
||||
EventPollerPool::Instance().getPoller()->doDelayTask(3000,[](){
|
||||
exit(0);
|
||||
return 0;
|
||||
});
|
||||
|
||||
return 0;
|
||||
});
|
||||
val["msg"] = "服务器将在一秒后自动重启";
|
||||
});
|
||||
|
||||
|
||||
API_REGIST(api,getMediaList,{
|
||||
//获取所有MediaSource列表
|
||||
val["code"] = 0;
|
||||
val["msg"] = "success";
|
||||
MediaSource::for_each_media([&](const string &schema,
|
||||
const string &vhost,
|
||||
const string &app,
|
||||
const string &stream,
|
||||
const MediaSource::Ptr &media){
|
||||
if(!allArgs["schema"].empty() && allArgs["schema"] != schema){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["vhost"].empty() && allArgs["vhost"] != vhost){
|
||||
return;
|
||||
}
|
||||
if(!allArgs["app"].empty() && allArgs["app"] != app){
|
||||
return;
|
||||
}
|
||||
Value item;
|
||||
item["schema"] = schema;
|
||||
item["vhost"] = vhost;
|
||||
item["app"] = app;
|
||||
item["stream"] = stream;
|
||||
val["data"]["array"].append(item);
|
||||
});
|
||||
});
|
||||
|
||||
API_REGIST(api,kick_pusher,{
|
||||
//踢掉推流器
|
||||
auto src = MediaSource::find(allArgs["schema"],
|
||||
allArgs["vhost"],
|
||||
allArgs["app"],
|
||||
allArgs["stream"]);
|
||||
if(src){
|
||||
bool flag = src->close();
|
||||
val["code"] = flag ? 0 : -1;
|
||||
val["msg"] = flag ? "success" : "kick failed";
|
||||
}else{
|
||||
val["code"] = -2;
|
||||
val["msg"] = "can not find the pusher";
|
||||
}
|
||||
});
|
||||
|
||||
API_REGIST(api,kick_session,{
|
||||
//踢掉tcp会话
|
||||
auto id = allArgs["id"];
|
||||
if(id.empty()){
|
||||
val["code"] = -1;
|
||||
val["msg"] = "illegal parameter:id";
|
||||
return;
|
||||
}
|
||||
auto session = SessionMap::Instance().get(id);
|
||||
if(!session){
|
||||
val["code"] = -2;
|
||||
val["msg"] = "can not find the target";
|
||||
return;
|
||||
}
|
||||
session->safeShutdown();
|
||||
val["code"] = 0;
|
||||
val["msg"] = "success";
|
||||
});
|
||||
|
||||
|
||||
////////////以下是注册的Hook API////////////
|
||||
API_REGIST(hook,on_publish,{
|
||||
//开始推流事件
|
||||
val["code"] = 0;
|
||||
val["msg"] = "success";
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_play,{
|
||||
//开始播放事件
|
||||
val["code"] = 0;
|
||||
val["msg"] = "success";
|
||||
});
|
||||
|
||||
API_REGIST(hook,on_flow_report,{
|
||||
//流量统计hook api
|
||||
val["code"] = 0;
|
||||
val["msg"] = "success";
|
||||
});
|
||||
}
|
||||
169
server/WebHook.cpp
Normal file
169
server/WebHook.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include "System.h"
|
||||
#include "jsoncpp/json.h"
|
||||
#include "Util/logger.h"
|
||||
#include "Util/util.h"
|
||||
#include "Util/onceToken.h"
|
||||
#include "Util/NoticeCenter.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
#include "Http/HttpRequester.h"
|
||||
#include "Network/TcpSession.h"
|
||||
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
namespace Hook {
|
||||
#define HOOK_FIELD "hook."
|
||||
|
||||
const char kEnable[] = HOOK_FIELD"enable";
|
||||
const char kTimeoutSec[] = HOOK_FIELD"timeoutSec";
|
||||
const char kOnPublish[] = HOOK_FIELD"on_publish";
|
||||
const char kOnPlay[] = HOOK_FIELD"on_play";
|
||||
const char kOnFlowReport[] = HOOK_FIELD"on_flow_report";
|
||||
const char kAdminParams[] = HOOK_FIELD"admin_params";
|
||||
|
||||
onceToken token([](){
|
||||
mINI::Instance()[kEnable] = false;
|
||||
mINI::Instance()[kTimeoutSec] = 10;
|
||||
mINI::Instance()[kOnPublish] = "http://127.0.0.1/index/hook/on_publish";
|
||||
mINI::Instance()[kOnPlay] = "http://127.0.0.1/index/hook/on_play";
|
||||
mINI::Instance()[kOnFlowReport] = "http://127.0.0.1/index/hook/on_flow_report";
|
||||
mINI::Instance()[kAdminParams] = "token=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
|
||||
},nullptr);
|
||||
}//namespace Hook
|
||||
|
||||
|
||||
static void parse_http_response(const SockException &ex,
|
||||
const string &status,
|
||||
const HttpClient::HttpHeader &header,
|
||||
const string &strRecvBody,
|
||||
const function<void(const Value &,const string &)> &fun){
|
||||
if(ex){
|
||||
auto errStr = StrPrinter << "[network err]:" << ex.what() << endl;
|
||||
fun(Json::nullValue,errStr);
|
||||
return;
|
||||
}
|
||||
if(status != "200"){
|
||||
auto errStr = StrPrinter << "[bad http status code]:" << status << endl;
|
||||
fun(Json::nullValue,errStr);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
stringstream ss(strRecvBody);
|
||||
Value result;
|
||||
ss >> result;
|
||||
if(result["code"].asInt() != 0) {
|
||||
auto errStr = StrPrinter << "[json code]:" << "code=" << result["code"] << ",msg=" << result["msg"] << endl;
|
||||
fun(Json::nullValue,errStr);
|
||||
return;
|
||||
}
|
||||
fun(result,"");
|
||||
}catch (std::exception &ex){
|
||||
auto errStr = StrPrinter << "[parse json failed]:" << ex.what() << endl;
|
||||
fun(Json::nullValue,errStr);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_http_hook(const string &url,const Value &body,const function<void(const Value &,const string &)> &fun){
|
||||
GET_CONFIG_AND_REGISTER(float,hook_timeoutSec,Hook::kTimeoutSec);
|
||||
HttpRequester::Ptr requester(new HttpRequester);
|
||||
requester->setMethod("POST");
|
||||
requester->setBody(body.toStyledString());
|
||||
requester->addHeader("Content-Type","application/json; charset=utf-8");
|
||||
std::shared_ptr<Ticker> pTicker(new Ticker);
|
||||
requester->startRequester(url,[url,fun,body,requester,pTicker](const SockException &ex,
|
||||
const string &status,
|
||||
const HttpClient::HttpHeader &header,
|
||||
const string &strRecvBody){
|
||||
onceToken token(nullptr,[&](){
|
||||
const_cast<HttpRequester::Ptr &>(requester).reset();
|
||||
});
|
||||
parse_http_response(ex,status,header,strRecvBody,[&](const Value &obj,const string &err){
|
||||
if(fun){
|
||||
fun(obj,err);
|
||||
}
|
||||
if(!err.empty()) {
|
||||
WarnL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,failed" << err << ":" << body;
|
||||
}else if(pTicker->elapsedTime() > 500){
|
||||
DebugL << "hook " << url << " " <<pTicker->elapsedTime() << "ms,success:" << body;
|
||||
}
|
||||
});
|
||||
},hook_timeoutSec);
|
||||
}
|
||||
|
||||
static Value make_json(const MediaInfo &args){
|
||||
Value body;
|
||||
body["schema"] = args._schema;
|
||||
body["vhost"] = args._vhost;
|
||||
body["app"] = args._app;
|
||||
body["stream"] = args._streamid;
|
||||
body["params"] = args._param_strs;
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
void installWebHook(){
|
||||
GET_CONFIG_AND_REGISTER(bool,hook_enable,Hook::kEnable);
|
||||
GET_CONFIG_AND_REGISTER(string,hook_publish,Hook::kOnPublish);
|
||||
GET_CONFIG_AND_REGISTER(string,hook_play,Hook::kOnPlay);
|
||||
GET_CONFIG_AND_REGISTER(string,hook_flowreport,Hook::kOnFlowReport);
|
||||
GET_CONFIG_AND_REGISTER(string,hook_adminparams,Hook::kAdminParams);
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastRtmpPublish,[](BroadcastRtmpPublishArgs){
|
||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
||||
invoker("");
|
||||
return;
|
||||
}
|
||||
//异步执行该hook api,防止阻塞NoticeCenter
|
||||
Value body = make_json(args);
|
||||
body["ip"] = sender.get_peer_ip();
|
||||
body["port"] = sender.get_peer_port();
|
||||
body["id"] = sender.getIdentifier();
|
||||
EventPollerPool::Instance().getExecutor()->async([body,invoker](){
|
||||
//执行hook
|
||||
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
|
||||
invoker(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPlayed,[](BroadcastMediaPlayedArgs){
|
||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
||||
invoker("");
|
||||
return;
|
||||
}
|
||||
Value body = make_json(args);
|
||||
body["ip"] = sender.get_peer_ip();
|
||||
body["port"] = sender.get_peer_port();
|
||||
body["id"] = sender.getIdentifier();
|
||||
//异步执行该hook api,防止阻塞NoticeCenter
|
||||
EventPollerPool::Instance().getExecutor()->async([body,invoker](){
|
||||
//执行hook
|
||||
do_http_hook(hook_play,body,[invoker](const Value &obj,const string &err){
|
||||
invoker(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastFlowReport,[](BroadcastFlowReportArgs){
|
||||
if(!hook_enable || args._param_strs == hook_adminparams){
|
||||
return;
|
||||
}
|
||||
Value body = make_json(args);
|
||||
body["ip"] = sender.get_peer_ip();
|
||||
body["port"] = sender.get_peer_port();
|
||||
body["id"] = sender.getIdentifier();
|
||||
body["totalBytes"] = (Json::UInt64)totalBytes;
|
||||
body["duration"] = (Json::UInt64)totalDuration;
|
||||
|
||||
//流量统计事件
|
||||
EventPollerPool::Instance().getExecutor()->async([body,totalBytes](){
|
||||
//执行hook
|
||||
do_http_hook(hook_flowreport,body, nullptr);
|
||||
});
|
||||
});
|
||||
}
|
||||
282
server/main.cpp
Normal file
282
server/main.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 <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 "RtmpMuxer/FlvMuxer.h"
|
||||
#include "Player/PlayerProxy.h"
|
||||
#include "Http/WebSocketSession.h"
|
||||
#include "System.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
namespace mediakit {
|
||||
////////////HTTP配置///////////
|
||||
namespace Http {
|
||||
#define HTTP_FIELD "http."
|
||||
#define HTTP_PORT 80
|
||||
const char kPort[] = HTTP_FIELD"port";
|
||||
#define HTTPS_PORT 443
|
||||
const char 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 char kPort[] = SHELL_FIELD"port";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = SHELL_PORT;
|
||||
},nullptr);
|
||||
} //namespace Shell
|
||||
|
||||
////////////RTSP服务器配置///////////
|
||||
namespace Rtsp {
|
||||
#define RTSP_FIELD "rtsp."
|
||||
#define RTSP_PORT 554
|
||||
#define RTSPS_PORT 322
|
||||
const char kPort[] = RTSP_FIELD"port";
|
||||
const char 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 1935
|
||||
const char kPort[] = RTMP_FIELD"port";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPort] = RTMP_PORT;
|
||||
},nullptr);
|
||||
} //namespace RTMP
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
class CMD_main : public CMD {
|
||||
public:
|
||||
CMD_main() {
|
||||
_parser.reset(new OptionParser(nullptr));
|
||||
|
||||
(*_parser) << Option('d',/*该选项简称,如果是\x00则说明无简称*/
|
||||
"daemon",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
|
||||
Option::ArgNone,/*该选项后面必须跟值*/
|
||||
nullptr,/*该选项默认值*/
|
||||
false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"是否以Daemon方式启动",/*该选项说明文字*/
|
||||
nullptr);
|
||||
|
||||
(*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/
|
||||
"level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
|
||||
Option::ArgRequired,/*该选项后面必须跟值*/
|
||||
to_string(LTrace).data(),/*该选项默认值*/
|
||||
false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"日志等级,LTrace~LError(0~4)",/*该选项说明文字*/
|
||||
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(thread::hardware_concurrency()).data(),/*该选项默认值*/
|
||||
false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
|
||||
"启动事件触发线程数",/*该选项说明文字*/
|
||||
nullptr);
|
||||
}
|
||||
|
||||
virtual ~CMD_main() {}
|
||||
virtual const char *description() const {
|
||||
return "主程序命令参数";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
extern void installWebApi();
|
||||
extern void installWebHook();
|
||||
|
||||
static void inline listen_shell_input(){
|
||||
cout << "> 欢迎进入命令模式,你可以输入\"help\"命令获取帮助" << endl;
|
||||
cout << "> " << std::flush;
|
||||
SockUtil::setNoBlocked(STDIN_FILENO);
|
||||
auto oninput = [](int event) {
|
||||
if (event & Event_Read) {
|
||||
char buf[1024];
|
||||
int n = read(STDIN_FILENO, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
buf[n] = '\0';
|
||||
try {
|
||||
CMDRegister::Instance()(buf);
|
||||
cout << "> " << std::flush;
|
||||
} catch (ExitException &ex) {
|
||||
InfoL << "ExitException";
|
||||
kill(getpid(), SIGINT);
|
||||
} catch (std::exception &ex) {
|
||||
cout << ex.what() << endl;
|
||||
}
|
||||
} else {
|
||||
DebugL << get_uv_errmsg();
|
||||
EventPollerPool::Instance().getFirstPoller()->delEvent(STDIN_FILENO);
|
||||
}
|
||||
}
|
||||
|
||||
if (event & Event_Error) {
|
||||
WarnL << "Event_Error";
|
||||
EventPollerPool::Instance().getFirstPoller()->delEvent(STDIN_FILENO);
|
||||
}
|
||||
};
|
||||
EventPollerPool::Instance().getFirstPoller()->addEvent(STDIN_FILENO, Event_Read | Event_Error | Event_LT,oninput);
|
||||
}
|
||||
int main(int argc,char *argv[]) {
|
||||
CMD_main cmd_main;
|
||||
try {
|
||||
cmd_main.operator()(argc,argv);
|
||||
} catch (std::exception &ex) {
|
||||
cout << ex.what() << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool bDaemon = cmd_main.hasKey("daemon");
|
||||
LogLevel logLevel = (LogLevel)cmd_main["level"].as<int>();
|
||||
logLevel = MIN(MAX(logLevel,LTrace),LError);
|
||||
string ini_file = cmd_main["config"];
|
||||
string ssl_file = cmd_main["ssl"];
|
||||
int threads = cmd_main["threads"];
|
||||
|
||||
//设置日志
|
||||
Logger::Instance().add(std::make_shared<ConsoleChannel>("ConsoleChannel",logLevel));
|
||||
#if defined(__linux__) || defined(__linux)
|
||||
Logger::Instance().add(std::make_shared<SysLogChannel>("SysLogChannel",logLevel));
|
||||
#else
|
||||
Logger::Instance().add(std::make_shared<FileChannel>("FileChannel",exePath() + ".log",logLevel));
|
||||
#endif
|
||||
|
||||
if(bDaemon){
|
||||
//启动守护进程
|
||||
System::startDaemon();
|
||||
}
|
||||
|
||||
//启动异步日志线程
|
||||
Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
|
||||
//加载配置文件,如果配置文件不存在就创建一个
|
||||
loadIniConfig(ini_file.data());
|
||||
|
||||
//加载证书,证书包含公钥和私钥
|
||||
SSL_Initor::Instance().loadCertificate(ssl_file.data());
|
||||
//信任某个自签名证书
|
||||
SSL_Initor::Instance().trustCertificate(ssl_file.data());
|
||||
//不忽略无效证书证书(例如自签名或过期证书)
|
||||
SSL_Initor::Instance().ignoreInvalidCertificate(false);
|
||||
|
||||
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);//默认554
|
||||
rtmpSrv->start<RtmpSession>(rtmpPort);//默认1935
|
||||
//http服务器,支持websocket
|
||||
httpSrv->start<EchoWebSocketSession>(httpPort);//默认80
|
||||
|
||||
//如果支持ssl,还可以开启https服务器
|
||||
TcpServer::Ptr httpsSrv(new TcpServer());
|
||||
//https服务器,支持websocket
|
||||
httpsSrv->start<SSLEchoWebSocketSession>(httpsPort);//默认443
|
||||
|
||||
//支持ssl加密的rtsp服务器,可用于诸如亚马逊echo show这样的设备访问
|
||||
TcpServer::Ptr rtspSSLSrv(new TcpServer());
|
||||
rtspSSLSrv->start<RtspSessionWithSSL>(rtspsPort);//默认322
|
||||
|
||||
installWebApi();
|
||||
InfoL << "已启动http api 接口";
|
||||
installWebHook();
|
||||
InfoL << "已启动http hook 接口";
|
||||
|
||||
if(!bDaemon) {
|
||||
//交互式shell输入
|
||||
listen_shell_input();
|
||||
}
|
||||
|
||||
//设置退出信号处理函数
|
||||
static semaphore sem;
|
||||
signal(SIGINT, [](int) { InfoL << "SIGINT:exit"; sem.post(); });// 设置退出信号
|
||||
signal(SIGHUP, [](int) { mediakit::loadIniConfig(); });
|
||||
sem.wait();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user