操作系统 :CentOS 7.6_x64     
FreeSWITCH版本 :1.10.9
 
日常开发过程中会遇到需要扩展FreeSWITCH对接其它系统的情况,这里记录下编写FreeSWITCH自定义endpoint的过程。

一、模块定义函数

使用FreeSWITCH自带的框架来定义模块函数,函数指针及参数列表定义如下(src/include/switch_types.h)
#define SWITCH_MODULE_LOAD_ARGS (switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool)
#define SWITCH_MODULE_RUNTIME_ARGS (void)
#define SWITCH_MODULE_SHUTDOWN_ARGS (void)
typedef switch_status_t (*switch_module_load_t) SWITCH_MODULE_LOAD_ARGS;
typedef switch_status_t (*switch_module_runtime_t) SWITCH_MODULE_RUNTIME_ARGS;
typedef switch_status_t (*switch_module_shutdown_t) SWITCH_MODULE_SHUTDOWN_ARGS;
#define SWITCH_MODULE_LOAD_FUNCTION(name) switch_status_t name SWITCH_MODULE_LOAD_ARGS
#define SWITCH_MODULE_RUNTIME_FUNCTION(name) switch_status_t name SWITCH_MODULE_RUNTIME_ARGS
#define SWITCH_MODULE_SHUTDOWN_FUNCTION(name) switch_status_t name SWITCH_MODULE_SHUTDOWN_ARGS
 

1、模块加载

SWITCH_MODULE_LOAD_FUNCTION
模块加载函数,负责系统启动时或运行时加载模块,可以进行配置读取及资源初始化。

2、模块卸载

SWITCH_MODULE_SHUTDOWN_FUNCTION
模块卸载函数,负载模块卸载及相关资源回收。

3、模块运行时

SWITCH_MODULE_RUNTIME_FUNCTION
模块运行时函数,可以启动线程处理请求,监听socket等。

4、模块定义

SWITCH_MODULE_DEFINITION
相关代码:
typedef struct switch_loadable_module_function_table {
int switch_api_version;
switch_module_load_t load;
switch_module_shutdown_t shutdown;
switch_module_runtime_t runtime;
switch_module_flag_t flags;
} switch_loadable_module_function_table_t; #define SWITCH_MODULE_DEFINITION_EX(name, load, shutdown, runtime, flags) \
static const char modname[] = #name ; \
SWITCH_MOD_DECLARE_DATA switch_loadable_module_function_table_t name##_module_interface = { \
SWITCH_API_VERSION, \
load, \
shutdown, \
runtime, \
flags \
} #define SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) \
SWITCH_MODULE_DEFINITION_EX(name, load, shutdown, runtime, SMODF_NONE) 

二、模块加载流程

FreeSWITCH使用 switch_loadable_module_load_module 或 switch_loadable_module_load_module_ex 进行模块加载,具体实现逻辑可以在 switch_loadable_module.c 中查看,这里做下简单介绍。 

1、模块加载函数

通过 switch_loadable_module_load_module 函数加载模块,函数调用链如下:
switch_loadable_module_load_module
=> switch_loadable_module_load_module_ex
=> switch_loadable_module_load_file
=> switch_loadable_module_process
=> switch_core_launch_thread => switch_loadable_module_exec
通过 switch_dso_data_sym 根据定义的 XXX_module_interface 从动态库里面获取回调函数指针,使用 switch_loadable_module_function_table_t 数据结构进行回调函数绑定。
switch_dso_data_sym 函数实现如下(src/switch_dso.c):
void *switch_dso_data_sym(switch_dso_lib_t lib, const char *sym, char **err)
{
void *addr = dlsym(lib, sym);
if (!addr) {
char *err_str = NULL;
dlerror(); if (!(addr = dlsym(lib, sym))) {
err_str = (char *)dlerror();
} if (err_str) {
*err = strdup(err_str);
}
}
return addr;
}
switch_loadable_module_exec函数:
static void *SWITCH_THREAD_FUNC switch_loadable_module_exec(switch_thread_t *thread, void *obj)
{ switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_core_thread_session_t *ts = obj;
switch_loadable_module_t *module = ts->objs[0];
int restarts; switch_assert(thread != NULL);
switch_assert(module != NULL); for (restarts = 0; status != SWITCH_STATUS_TERM && !module->shutting_down; restarts++) {
status = module->switch_module_runtime();
}
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Thread ended for %s\n", module->module_interface->module_name); if (ts->pool) {
switch_memory_pool_t *pool = ts->pool;
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Destroying Pool for %s\n", module->module_interface->module_name);
switch_core_destroy_memory_pool(&pool);
}
switch_thread_exit(thread, 0);
return NULL;
}
switch_loadable_module_exec 函数为独立线程中运行,模块运行时通过 module->switch_module_runtime() 触发。

2、FreeSWITCH启动时加载模块

1)整体结构

函数调用链如下:

main
=> switch_core_init_and_modload
=> switch_core_init
=> switch_loadable_module_init => switch_loadable_module_load_module
main函数在switch.c中实现。 
 
2)加载顺序
先加载系统核心模块:
switch_loadable_module_load_module_ex("", "CORE_SOFTTIMER_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash);
switch_loadable_module_load_module_ex("", "CORE_PCM_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash);
switch_loadable_module_load_module_ex("", "CORE_SPEEX_MODULE", SWITCH_FALSE, SWITCH_FALSE, &err, SWITCH_LOADABLE_MODULE_TYPE_COMMON, event_hash);
使用 switch_xml_open_cfg 函数(src/switch_xml.c中定义)先后加载以下文件中定义的模块:
pre_load_modules.conf
modules.conf
post_load_modules.conf
具体格式参考 conf/autoload_configs/modules.conf.xml
 
3)xml加载过程
 函数调用链如下:
main => switch_core_init_and_modload
=> switch_core_init
=> switch_xml_init
=> switch_xml_open_root => XML_OPEN_ROOT_FUNCTION
其中 SWITCH_GLOBAL_filenames 变量定义如下(main => switch_core_set_globals):
if (!SWITCH_GLOBAL_filenames.conf_name && (SWITCH_GLOBAL_filenames.conf_name = (char *) malloc(BUFSIZE))) {
switch_snprintf(SWITCH_GLOBAL_filenames.conf_name, BUFSIZE, "%s", "freeswitch.xml");
}
XML_OPEN_ROOT_FUNCTION实现如下(src/switch_xml.c):
static switch_xml_open_root_function_t XML_OPEN_ROOT_FUNCTION = (switch_xml_open_root_function_t)__switch_xml_open_root;

SWITCH_DECLARE_NONSTD(switch_xml_t) __switch_xml_open_root(uint8_t reload, const char **err, void *user_data)
{
char path_buf[1024];
uint8_t errcnt = 0;
switch_xml_t new_main, r = NULL; if (MAIN_XML_ROOT) {
if (!reload) {
r = switch_xml_root();
goto done;
}
} switch_snprintf(path_buf, sizeof(path_buf), "%s%s%s", SWITCH_GLOBAL_dirs.conf_dir, SWITCH_PATH_SEPARATOR, SWITCH_GLOBAL_filenames.conf_name);
if ((new_main = switch_xml_parse_file(path_buf))) {
*err = switch_xml_error(new_main);
switch_copy_string(not_so_threadsafe_error_buffer, *err, sizeof(not_so_threadsafe_error_buffer));
*err = not_so_threadsafe_error_buffer;
if (!zstr(*err)) {
switch_xml_free(new_main);
new_main = NULL;
errcnt++;
} else {
*err = "Success";
switch_xml_set_root(new_main); }
} else {
*err = "Cannot Open log directory or XML Root!";
errcnt++;
} if (errcnt == 0) {
r = switch_xml_root();
} done: return r;
}
freeswitch.xml 为xml文件的总入口,配置的有加载各个模块的数据:
<section name="configuration" description="Various Configuration">
<X-PRE-PROCESS cmd="include" data="autoload_configs/*.xml"/>
</section>

3、控制台动态加载

在fs_cli中可以使用load及reload加载模块,具体流程如下:
fs_cli => load ... => SWITCH_STANDARD_API(load_function) => switch_loadable_module_load_module 

fs_cli => reload ... => SWITCH_STANDARD_API(reload_function) => switch_loadable_module_unload_module
=> switch_loadable_module_load_module

三、关键数据结构

1、switch_loadable_module_t

作用:用于定义模块信息。
结构体定义:
struct switch_loadable_module {
char *key;
char *filename;
int perm;
switch_loadable_module_interface_t *module_interface;
switch_dso_lib_t lib;
switch_module_load_t switch_module_load;
switch_module_runtime_t switch_module_runtime;
switch_module_shutdown_t switch_module_shutdown;
switch_memory_pool_t *pool;
switch_status_t status;
switch_thread_t *thread;
switch_bool_t shutting_down;
switch_loadable_module_type_t type;
}; typedef struct switch_loadable_module switch_loadable_module_t;
字段解释:
key =》 模块文件名称
filename => 模块文件路径(动态库路径)
perm =》 定义模块是否允许被卸载
module_interface =》 模块接口(由switch_module_load函数赋值)
lib =》 动态库句柄(dlopen函数返回)
switch_module_load =》 模块加载函数
switch_module_runtime =》 模块运行时函数
switch_module_shutdown =》 模块关闭(卸载)函数
pool =》 模块内存池
status =》 switch_module_shutdown 函数的返回值
shutting_down => 模块是否关闭

2、switch_loadable_module_interface

作用: 模块接口(入口)
结构体定义:
struct switch_loadable_module_interface {
/*! the name of the module */
const char *module_name;
/*! the table of endpoints the module has implemented */
switch_endpoint_interface_t *endpoint_interface;
/*! the table of timers the module has implemented */
switch_timer_interface_t *timer_interface;
/*! the table of dialplans the module has implemented */
switch_dialplan_interface_t *dialplan_interface;
/*! the table of codecs the module has implemented */
switch_codec_interface_t *codec_interface;
/*! the table of applications the module has implemented */
switch_application_interface_t *application_interface;
/*! the table of chat applications the module has implemented */
switch_chat_application_interface_t *chat_application_interface;
/*! the table of api functions the module has implemented */
switch_api_interface_t *api_interface;
/*! the table of json api functions the module has implemented */
switch_json_api_interface_t *json_api_interface;
/*! the table of file formats the module has implemented */
switch_file_interface_t *file_interface;
/*! the table of speech interfaces the module has implemented */
switch_speech_interface_t *speech_interface;
/*! the table of directory interfaces the module has implemented */
switch_directory_interface_t *directory_interface;
/*! the table of chat interfaces the module has implemented */
switch_chat_interface_t *chat_interface;
/*! the table of say interfaces the module has implemented */
switch_say_interface_t *say_interface;
/*! the table of asr interfaces the module has implemented */
switch_asr_interface_t *asr_interface;
/*! the table of management interfaces the module has implemented */
switch_management_interface_t *management_interface;
/*! the table of limit interfaces the module has implemented */
switch_limit_interface_t *limit_interface;
/*! the table of database interfaces the module has implemented */
switch_database_interface_t *database_interface;
switch_thread_rwlock_t *rwlock;
int refs;
switch_memory_pool_t *pool;
}; typedef struct switch_loadable_module_interface switch_loadable_module_interface_t;
字段解释:
module_name => 模块的名称
endpoint_interface => 模块endpoint的具体实现
timer_interface => 模块timer的具体实现
dialplan_interface => 模块dialplan的具体实现
codec_interface => 模块编解码的具体实现
application_interface => 模块提供的app工具的具体实现
chat_application_interface => 模块提供的文本聊天app工具的具体实现
api_interface => 模块提供的api具体实现
json_api_interface => 模块提供的json格式api的具体实现
file_interface => 模块支持的文件格式的具体实现(比如mp4、mkv等文件格式)
speech_interface => 模块使用的speech接口实现
directory_interface => 模块使用的directory接口实现
chat_interface => 模块使用的chat接口实现
say_interface => 模块使用的say接口实现
asr_interface => 模块使用的asr接口实现
management_interface => 模块使用的管理接口实现
limit_interface => 模块使用的limit接口实现
database_interface => 模块使用的limit接口实现
rwlock => 模块使用的锁
refs => 模块锁的计数器
pool =》 模块内存池
使用 switch_loadable_module_create_module_interface 来创建 switch_loadable_module_interface_t 实例。
SWITCH_DECLARE(switch_loadable_module_interface_t *) switch_loadable_module_create_module_interface(switch_memory_pool_t *pool, const char *name)
{
switch_loadable_module_interface_t *mod; mod = switch_core_alloc(pool, sizeof(switch_loadable_module_interface_t));
switch_assert(mod != NULL); mod->pool = pool; mod->module_name = switch_core_strdup(mod->pool, name);
switch_thread_rwlock_create(&mod->rwlock, mod->pool);
return mod;
}
使用 switch_loadable_module_create_interface 来创建模块里面的子接口,示例如下:
*module_interface = switch_loadable_module_create_module_interface(pool, modname);

rtc_endpoint_interface = switch_loadable_module_create_interface(*module_interface, SWITCH_ENDPOINT_INTERFACE);
rtc_endpoint_interface->interface_name = "rtc";
rtc_endpoint_interface->io_routines = &rtc_io_routines;
rtc_endpoint_interface->state_handler = &rtc_event_handlers;
rtc_endpoint_interface->recover_callback = rtc_recover_callback;
具体实现如下:
SWITCH_DECLARE(void *) switch_loadable_module_create_interface(switch_loadable_module_interface_t *mod, switch_module_interface_name_t iname)
{ switch (iname) {
case SWITCH_ENDPOINT_INTERFACE:
ALLOC_INTERFACE(endpoint) case SWITCH_TIMER_INTERFACE:
ALLOC_INTERFACE(timer) case SWITCH_DIALPLAN_INTERFACE:
ALLOC_INTERFACE(dialplan) case SWITCH_CODEC_INTERFACE:
ALLOC_INTERFACE(codec) case SWITCH_APPLICATION_INTERFACE:
ALLOC_INTERFACE(application) case SWITCH_CHAT_APPLICATION_INTERFACE:
ALLOC_INTERFACE(chat_application) case SWITCH_API_INTERFACE:
ALLOC_INTERFACE(api) case SWITCH_JSON_API_INTERFACE:
ALLOC_INTERFACE(json_api) case SWITCH_FILE_INTERFACE:
ALLOC_INTERFACE(file) case SWITCH_SPEECH_INTERFACE:
ALLOC_INTERFACE(speech) case SWITCH_DIRECTORY_INTERFACE:
ALLOC_INTERFACE(directory) case SWITCH_CHAT_INTERFACE:
ALLOC_INTERFACE(chat) case SWITCH_SAY_INTERFACE:
ALLOC_INTERFACE(say) case SWITCH_ASR_INTERFACE:
ALLOC_INTERFACE(asr) case SWITCH_MANAGEMENT_INTERFACE:
ALLOC_INTERFACE(management) case SWITCH_LIMIT_INTERFACE:
ALLOC_INTERFACE(limit) case SWITCH_DATABASE_INTERFACE:
ALLOC_INTERFACE(database) default:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid Module Type!\n");
return NULL;
}
}

3、switch_endpoint_interface_t

作用:endpoint的入口
结构体定义:
struct switch_endpoint_interface {
/*! the interface's name */
const char *interface_name; /*! channel abstraction methods */
switch_io_routines_t *io_routines; /*! state machine methods */
switch_state_handler_table_t *state_handler; /*! private information */
void *private_info; switch_thread_rwlock_t *rwlock;
int refs;
switch_mutex_t *reflock; /* parent */
switch_loadable_module_interface_t *parent; /* to facilitate linking */
struct switch_endpoint_interface *next; switch_core_recover_callback_t recover_callback; }; typedef struct switch_endpoint_interface switch_endpoint_interface_t;
字段解释:
interface_name => endpoint名称,比如:"rtc"
io_routines => endpoint对应的io操作回调函数
state_handler => endpoint对应的事件处理回调函数
private_info => endpoint私有参数配置(比如编码格式、采样率等)
rwlock => endpoint锁
refs => endpoint锁的引用次数
reflock => endpoint引用锁
parent => endpoint所属模块
next => next指针
recover_callback => endpoint对应的recover回调函数

4、switch_io_routines

作用:存储io操作的回调函数
结构体定义:
struct switch_io_routines {
/*! creates an outgoing session from given session, caller profile */
switch_io_outgoing_channel_t outgoing_channel;
/*! read a frame from a session */
switch_io_read_frame_t read_frame;
/*! write a frame to a session */
switch_io_write_frame_t write_frame;
/*! send a kill signal to the session's channel */
switch_io_kill_channel_t kill_channel;
/*! send a string of DTMF digits to a session's channel */
switch_io_send_dtmf_t send_dtmf;
/*! receive a message from another session */
switch_io_receive_message_t receive_message;
/*! queue a message for another session */
switch_io_receive_event_t receive_event;
/*! change a sessions channel state */
switch_io_state_change_t state_change;
/*! read a video frame from a session */
switch_io_read_video_frame_t read_video_frame;
/*! write a video frame to a session */
switch_io_write_video_frame_t write_video_frame;
/*! read a video frame from a session */
switch_io_read_text_frame_t read_text_frame;
/*! write a video frame to a session */
switch_io_write_text_frame_t write_text_frame;
/*! change a sessions channel run state */
switch_io_state_run_t state_run;
/*! get sessions jitterbuffer */
switch_io_get_jb_t get_jb;
void *padding[10];
}; typedef struct switch_io_routines switch_io_routines_t;
字段解释:
outgoing_channel => 创建外呼channel的回调函数
read_frame => 读session音频数据的回调函数
write_frame => 写session音频数据的回调函数
kill_channel => kill信号处理函数,用于处理channel接收的kill信号
send_dtmf => send dtmf操作的回调函数,用于处理channel接收的DTMF字符串
receive_message => 处理channel消息的回调函数,用于处理其它channel发来的消息
receive_event => 发送channel消息的回调函数,用于向目标session发送自定义事件(比如rtc session、rtmp session等)
state_change => channel状态修改的回调函数
read_video_frame => 读session视频数据的回调函数
write_video_frame => 写session视频数据的回调函数
read_text_frame => 读session文本数据的回调函数
write_text_frame => 写session文本数据的回调函数
state_run => 改变session的运行状态,目前没见到有endpoint使用过
get_jb => 获取session的jitter_buffer

5、switch_state_handler_table_t

作用:用于存储状态机的回调函数。
定义如下:
struct switch_state_handler_table {
/*! executed when the state changes to init */
switch_state_handler_t on_init;
/*! executed when the state changes to routing */
switch_state_handler_t on_routing;
/*! executed when the state changes to execute */
switch_state_handler_t on_execute;
/*! executed when the state changes to hangup */
switch_state_handler_t on_hangup;
/*! executed when the state changes to exchange_media */
switch_state_handler_t on_exchange_media;
/*! executed when the state changes to soft_execute */
switch_state_handler_t on_soft_execute;
/*! executed when the state changes to consume_media */
switch_state_handler_t on_consume_media;
/*! executed when the state changes to hibernate */
switch_state_handler_t on_hibernate;
/*! executed when the state changes to reset */
switch_state_handler_t on_reset;
/*! executed when the state changes to park */
switch_state_handler_t on_park;
/*! executed when the state changes to reporting */
switch_state_handler_t on_reporting;
/*! executed when the state changes to destroy */
switch_state_handler_t on_destroy;
int flags;
void *padding[10];
}; typedef struct switch_state_handler_table switch_state_handler_table_t;
参数解释:
on_init => channel进入 CS_INIT 状态的回调函数
on_routing => channel进入 CS_ROUTING 状态的回调函数
on_execute => channel进入 CS_EXECUTE 状态的回调函数,用于执行操作
on_hangup => channel进入 CS_HANGUP 状态的回调函数
on_exchange_media => channel进入 CS_EXCHANGE_MEDIA 状态的回调函数
on_soft_execute => channel进入 CS_SOFT_EXECUTE 状态的回调函数,用于从其它channel接收或发送数据
on_consume_media => channel进入 CS_CONSUME_MEDIA 状态的回调函数,
on_hibernate => channel进入 CS_HIBERNATE 状态的回调函数,sleep操作
on_reset => channel进入 CS_RESET 状态的回调函数
on_park => channel进入 CS_PARK 状态的回调函数
on_reporting => channel进入 CS_REPORTING 状态的回调函数
on_destroy => channel进入 CS_DESTROY 状态的回调函数
switch_core_state_machine.c中使用 STATE_MACRO 触发,部分触发代码如下:
case CS_ROUTING:    /* Look for a dialplan and find something to do */
STATE_MACRO(routing, "ROUTING");
break;
case CS_RESET: /* Reset */
STATE_MACRO(reset, "RESET");
break;
/* These other states are intended for prolonged durations so we do not signal lock for them */
case CS_EXECUTE: /* Execute an Operation */
STATE_MACRO(execute, "EXECUTE");
break;
case CS_EXCHANGE_MEDIA: /* loop all data back to source */
STATE_MACRO(exchange_media, "EXCHANGE_MEDIA");
break;
case CS_SOFT_EXECUTE: /* send/recieve data to/from another channel */
STATE_MACRO(soft_execute, "SOFT_EXECUTE");
break;
case CS_PARK: /* wait in limbo */
STATE_MACRO(park, "PARK");
break;
case CS_CONSUME_MEDIA: /* wait in limbo */
STATE_MACRO(consume_media, "CONSUME_MEDIA");
break;
case CS_HIBERNATE: /* sleep */
STATE_MACRO(hibernate, "HIBERNATE");
break;

四、模块编写示例

1、编写c风格的endpoint模块

仿照mod_rtc模块编写,核心文件只有两个:
mod_rtc.c
Makefile.am
 
1)复制mod_artc目录
cp mod_rtc mod_ctest -r

2)修改文件名

mv mod_rtc.c mod_ctest.c
 
3)修改文件内容,将rtc关键字替换成ctest

4)修改编译选项

文件: freeswitch-1.10.9.-release/configure.ac
仿照rtc模块,添加ctest模块内容:
src/mod/endpoints/mod_ctest/Makefile

5)开启模块编译
文件:freeswitch-1.10.9.-release/modules.conf
仿照rtc模块,添加ctest模块编译:
endpoints/mod_ctest

6)生成Makefile
./rebootstrap.sh && ./configure

7)安装模块

在 freeswitch-1.10.9.-release 根目录(或mod_ctest目录)执行如下指令:
make && make install

8)加载模块

文件:conf/autoload_configs/modules.conf.xml
添加如下内容:
 
9)模块测试
控制台加载测试:
reload mod_ctest

c风格endpoint模块编译及运行效果视频:

关注微信公众号(聊聊博文,文末可扫码)后回复 2023052801 获取。

2、编写c++风格的endpoint模块

仿照mod_h323模块编写,目录结构、编译等参考c风格endpoint模块编写部分。
加载效果如下:

c++风格endpoint模块编译及运行效果视频:

关注微信公众号(聊聊博文,文末可扫码)后回复 2023052802 获取。

五、资源下载

本文涉及源码和文件,可以从如下途径获取:

关注微信公众号(聊聊博文,文末可扫码)后回复 20230528 获取。

FreeSWITCH添加自定义endpoint的更多相关文章

  1. 《FreeSWITCH: VoIP实战》:SIP 模块 - mod_sofia

    SIP 模块是 FreeSWITCH 的主要模块,所以,值得拿出专门一章来讲解. 在前几章时里,你肯定见过几次 sofia 这个词,只是或许还不知道是什么意思.是这样的,Sofia-SIP 是由诺基亚 ...

  2. Freeswitch配置之sofia

    SIP模块 - mod_sofia SIP 模块是 FreeSWITCH的主要模块. 在 FreeSWITCH中,实现一些互联协议接口的模块称为 Endpoint.FreeSWITH支持很多的 End ...

  3. SpringCloud学习2-Springboot监控模块(actuator)

    前言 学习一项新技术最大的困难是什么? 是资料.让人高兴的是找到了一本系统学习Spring Cloud的教程,<Spring Cloud微服务实战>, 接下来的学习目标将以此书顺序演进. ...

  4. springcloud添加自定义的endpoint来实现平滑发布

    在我之前的文章  springcloud如何实现服务的平滑发布 里介绍了基于pause的发布方案. 平滑发布的核心思想就是:所有服务的调用者不再调用该服务了就表示安全的将服务kill掉. 另外actu ...

  5. freeswitch 拨号时添加自定义变量

    Using Channel Variables in Dialplan Condition Statements Channel variables can be used in conditions ...

  6. FreeSWITCH的originate命令解析及示例

    FreeSWITCH版本:1.10.9 操作系统:CentOS 7.6.1810 originate经常用于发起呼叫,在实际工作过程中用到的也比较多,今天总结下基本用法,也方便我以后查阅. 一.wik ...

  7. 生成freeswitch事件的几种方式

    本文描述了生成freeswitch事件的几种方式,这里记录下,也方便我以后查阅. 操作系统:debian8.5_x64 freeswitch 版本 : 1.6.8 在freeswitch代码中加入事件 ...

  8. WCF之添加自定义用户名密码认证

    1.创建WCF服务应用以及调用客户端(请自行google).  2.创建X509证书       cmd 进入  C:\Program Files\Microsoft SDKs\Windows\v6. ...

  9. FreeSwitch 终端命令详细介绍

    FreeSwitch版本:1.6.9 以下为部分终端命令 alias 语法: alias [add|stickyadd] <alias> <command> | del [&l ...

  10. freeswitch编译安装,初探, 以及联合sipgateway, webrtc server的使用场景。

    本文主要记录freeswitch学习过程. 一 安装freeswitch NOTE 以下两种安装方式,再安装的过程中遇到了不少问题,印象比较深刻的就是lua库找到不到这个问题.这个问题发生在make ...

随机推荐

  1. 【单元测试】Junit 4(六)--junit4测试优先级顺序

    ​ @FixMethodOrder的顺序也并不一定是方法在代码中定义的顺序,这与JVM的实现有关. ​ 我们在写JUnit测试用例时,有时候需要按照定义顺序执行我们的单元测试方法,比如如在测试数据库相 ...

  2. Kubernetes 的亲和性污点与容忍

    写在前面 我们在使用k8s过程中经常有这样的需求:我的k8s集群有多台服务器,配置不尽相同.我想把数据库部署到CPU.内存比较好的这几台机:我想把静态承载服务部署到有固态硬盘的机器等:而这些需求,就是 ...

  3. 解决margin合并问题

    一.什么是外边距合并 外边距合并(叠加)是一个相当简单的概念.但是,在实践中对网页进行布局时,它会造成许多混淆. 所谓的外边距合并就是,当两个垂直外边距相遇时,它们将形成一个外边距.合并的外边距的高度 ...

  4. Salesforce CPQ之后续慢慢看系列

    salesforce核心两朵云,sales & service. 针对sales的quote / quote line item的报价功能,还是相对薄弱.针对sales,报价的准确性影响着成单 ...

  5. vue 前端项目创建

    一.创建项目 将vue-admin-template-master 模板放入创建的 VS code 的工作空间.重命名为自己的项目. 模块获取方法:关注"Java程序员进阶",回复 ...

  6. VUE2.x原理之Object.defineProperty()

    Object.defineProperty()语法说明 Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性 Object.defineP ...

  7. STM32 HAL库学习 (3) 中断!

        中断在单片机开发中有着重中之重的地位.    中断即打断,实至CPU再执行当前程序时,由于系统出现了某种需要处理的紧急情况,CPU暂停正在执行的程序,转而去执行另一段特殊程序来处理的出现的紧急 ...

  8. 重磅!Apache Hudi联合传智教育推出免费中文视频教程

    基础介绍 Apache Hudi(简称:Hudi)使得您能在hadoop兼容的存储之上存储大量数据,同时它还提供两种原语,使得除了经典的批处理之外,还可以在数据湖上进行流处理.这两种原语分别是: Up ...

  9. What's the best way to read and understand someone else's code?

    Find one thing you know the code does, and trace those actions backward, starting at the end Say, fo ...

  10. ggplot2图形可视化应用集锦

    数据可视化就是将我们从数据中探索的信息与图形要素对应起来的过程.数据可视化,先要理解数据,再去掌握可视化的方法,这样才能实现高效的数据可视化.数据可视化技术的基本思想,是将数据库中每一个数据项作为单个 ...