freeswitch lua/luarun的执行过程

本文介绍了freeswitch模块下的lua_function和luarun_api_function两个API,重点分析了luarun的执行过程,涉及lua_thread函数及lua_thread_run中的lua_State初始化和lua_parse_and_execute的脚本加载执行。同时,讲解了lua接口如何映射到C++代码,以CoreSession接口为例,说明其在mod_lua_wrap.cpp中的swig_type_initial和swig_CoreSession_methods结构体中的实现。

在mod_lua.cpp文件中定义了两个api
SWITCH_STANDARD_APP(lua_function)
SWITCH_STANDARD_API(luarun_api_function)
分别对应lua和luarun命令,所有以宏SWITCH_STANDARD_API定义的都是freeswitch暴露的api接口。
我们这里以luarun为例分析

SWITCH_STANDARD_API(luarun_api_function)
{

    if (zstr(cmd)) {
        stream->write_function(stream, "-ERR no args specified!\n");
    } else {
        lua_thread(cmd);
        stream->write_function(stream, "+OK\n");
    }

    return SWITCH_STATUS_SUCCESS;
}

所以当执行luarun的时候主要是调用lua_thread()函数处理。

int lua_thread(const char *text)
{
    switch_thread_t *thread;
    switch_threadattr_t *thd_attr = NULL;
    switch_memory_pool_t *pool;
    lua_thread_helper *lth;

    switch_core_new_memory_pool(&pool);
    lth = (lua_thread_helper *) switch_core_alloc(pool, sizeof(*lth));
    lth->pool = pool;
    lth->input_code = switch_core_strdup(lth->pool, text);

    switch_threadattr_create(&thd_attr, lth->pool);
    switch_threadattr_detach_set(thd_attr, 1);
    switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
    switch_thread_create(&thread, thd_attr, lua_thread_run, lth, lth->pool);

    return 0;
}

在lua_thread中主要是创建一个新线程,并在线程里面执行:lua_thread_run

static void *SWITCH_THREAD_FUNC lua_thread_run(switch_thread_t *thread, void *obj)
{
    struct lua_thread_helper *lth = (struct lua_thread_helper *) obj;
    switch_memory_pool_t *pool = lth->pool;
    lua_State *L = lua_init();  /* opens Lua */

    lua_parse_and_execute(L, lth->input_code, NULL);

    lth = NULL;

    switch_core_destroy_memory_pool(&pool);

    lua_uninit(L);

    return NULL;
}

在lua_thread_run中先初始化了一个lua_State,然后调用lua_parse_and_execute去加载lua脚本文件并执行

static int lua_parse_and_execute(lua_State * L, char *input_code, switch_core_session_t *session)
{
    int error = 0;

    if (zstr(input_code)) {
        switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "No code to execute!\n");
        return 1;
    }

    while(input_code && (*input_code == ' ' || *input_code == '\n' || *input_code == '\r')) input_code++;

    if (*input_code == '~') {
        char *buff = input_code + 1;
        error = luaL_loadbuffer(L, buff, strlen(buff), "line") || docall(L, 0, 0, 0, 1);    //lua_pcall(L, 0, 0, 0);
    } else if (!strncasecmp(input_code, "#!/lua", 6)) {
        char *buff = input_code + 6;
        error = luaL_loadbuffer(L, buff, strlen(buff), "line") || docall(L, 0, 0, 0, 1);    //lua_pcall(L, 0, 0, 0);
    } else {
        char *args = strchr(input_code, ' ');
        if (args) {
            char *code = NULL;
            int x, argc;
            char *argv[128] = { 0 };
            *args++ = '\0';

            if ((argc = switch_separate_string(args, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) {
                switch_stream_handle_t stream = { 0 };
                SWITCH_STANDARD_STREAM(stream);

                stream.write_function(&stream, " argv = {[0]='%y', ", input_code);
                for (x = 0; x < argc; x++) {
                    stream.write_function(&stream, "'%y'%s", argv[x], x == argc - 1 ? "" : ", ");
                }
                stream.write_function(&stream, " };");
                code = (char *) stream.data;
            } else {
                code = switch_mprintf("argv = {[0]='%s'};", input_code);
            }

            if (code) {
                error = luaL_loadbuffer(L, code, strlen(code), "line") || docall(L, 0, 0, 0, 1);
                switch_safe_free(code);
            }
        } else {
            // Force empty argv table
            char *code = NULL;
            code = switch_mprintf("argv = {[0]='%s'};", input_code);
            error = luaL_loadbuffer(L, code, strlen(code), "line") || docall(L, 0, 0, 0, 1);
            switch_safe_free(code);
        }

        if (!error) {
            char *file = input_code, *fdup = NULL;

            if (!switch_is_file_path(file)) {
                fdup = switch_mprintf("%s/%s", SWITCH_GLOBAL_dirs.script_dir, file);
                switch_assert(fdup);
                file = fdup;
            }
            error = luaL_loadfile(L, file) || docall(L, 0, 0, 0, 1);
            switch_safe_free(fdup);
        }
    }

    if (error) {
        const char *err = lua_tostring(L, -1);
        if (!zstr(err)) {
            switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "%s\n", err);
        }
        lua_pop(L, 1);          /* pop error message from the stack */
    }

    return error;
}

前面一段都是在处理参数最核心的是下面这一行

luaL_loadfile(L, file) || docall(L, 0, 0, 0, 1)

加载文件并执行

int docall(lua_State * L, int narg, int nresults, int perror, int fatal)
{
    int status;
    int base = lua_gettop(L) - narg;    /* function index */

    lua_pushcfunction(L, traceback);    /* push traceback function */
    lua_insert(L, base);        /* put it under chunk and args */

    status = lua_pcall(L, narg, nresults, base);

    lua_remove(L, base);        /* remove traceback function */
    /* force a complete garbage collection in case of errors */
    if (status != 0) {
        lua_gc(L, LUA_GCCOLLECT, 0);
    }

    if (status && perror) {
        const char *err = lua_tostring(L, -1);
        if (!zstr(err)) {
            switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s\n", err);
        }

        // pass error up to top
        if (fatal) {
            lua_error(L);
        } else {
            lua_pop(L, 1); /* pop error message from the stack */
        }
    }

    return status;
}

最后调用lua_pcall执行lua脚本。

下面再介绍一下一个lua的接口怎么找到他对应的c++l代码:
所有的暴露给lua的接口都放在mod_lua_wrap.cpp这文件的swig_type_initial里面,然后lua模块启动的时候会逐个加载。

static swig_type_info *swig_type_initial[] = {
  &_swigt__p_API,
  &_swigt__p_CoreSession,
  &_swigt__p_DTMF,
  &_swigt__p_Event,
  &_swigt__p_EventConsumer,
  &_swigt__p_IVRMenu,
  &_swigt__p_LUA__Dbh,
  &_swigt__p_LUA__Session,
  &_swigt__p_SWIGLUA_FN,
  &_swigt__p_Stream,
  &_swigt__p_input_callback_state,
  &_swigt__p_int,
  &_swigt__p_lua_State,
  &_swigt__p_p_switch_event_node_t,
  &_swigt__p_session_flag_t,
  &_swigt__p_switch_call_cause_t,
  &_swigt__p_switch_channel_state_t,
  &_swigt__p_switch_channel_t,
  &_swigt__p_switch_core_session_t,
  &_swigt__p_switch_event_t,
  &_swigt__p_switch_event_types_t,
  &_swigt__p_switch_input_args_t,
  &_swigt__p_switch_input_type_t,
  &_swigt__p_switch_priority_t,
  &_swigt__p_switch_queue_t,
  &_swigt__p_switch_state_handler_table_t,
  &_swigt__p_switch_status_t,
  &_swigt__p_switch_stream_handle_t,
  &_swigt__p_uint32_t,
  &_swigt__p_void,
};

我们以Session的接口为例介绍一下,他在_swigt__p_CoreSession这个结构体里面描述。

static swig_type_info _swigt__p_CoreSession = {"_p_CoreSession", "CoreSession *", 0, 0, (void*)&_wrap_class_CoreSession, 0};
static swig_lua_class _wrap_class_CoreSession = { "CoreSession", "CoreSession", &SWIGTYPE_p_CoreSession,0, swig_delete_CoreSession, swig_CoreSession_methods, swig_CoreSession_attributes, &swig_CoreSession_Sf_SwigStatic, swig_CoreSession_meta, swig_CoreSession_bases, swig_CoreSession_base_names };

被注册的方法放在swig_CoreSession_methods这个结构体里面:

static swig_lua_method swig_CoreSession_methods[]= {
    { "insertFile", _wrap_CoreSession_insertFile},
    { "answer", _wrap_CoreSession_answer},
    { "preAnswer", _wrap_CoreSession_preAnswer},
    { "hangup", _wrap_CoreSession_hangup},
    { "hangupState", _wrap_CoreSession_hangupState},
    { "setVariable", _wrap_CoreSession_setVariable},
    { "setPrivate", _wrap_CoreSession_setPrivate},
    { "getPrivate", _wrap_CoreSession_getPrivate},
    { "getVariable", _wrap_CoreSession_getVariable},
    { "process_callback_result", _wrap_CoreSession_process_callback_result},
    { "say", _wrap_CoreSession_say},
    { "sayPhrase", _wrap_CoreSession_sayPhrase},
    { "hangupCause", _wrap_CoreSession_hangupCause},
    { "getState", _wrap_CoreSession_getState},
    { "recordFile", _wrap_CoreSession_recordFile},
    { "originate", _wrap_CoreSession_originate},
    { "destroy", _wrap_CoreSession_destroy},
    { "setDTMFCallback", _wrap_CoreSession_setDTMFCallback},
    { "speak", _wrap_CoreSession_speak},
    { "set_tts_parms", _wrap_CoreSession_set_tts_parms},
    { "set_tts_params", _wrap_CoreSession_set_tts_params},
    { "collectDigits", _wrap_CoreSession_collectDigits},
    { "getDigits", _wrap_CoreSession_getDigits},
    { "transfer", _wrap_CoreSession_transfer},
    { "read", _wrap_CoreSession_read},
    { "playAndGetDigits", _wrap_CoreSession_playAndGetDigits},
    { "streamFile", _wrap_CoreSession_streamFile},
    { "sleep", _wrap_CoreSession_sleep},
    { "flushEvents", _wrap_CoreSession_flushEvents},
    { "flushDigits", _wrap_CoreSession_flushDigits},
    { "setAutoHangup", _wrap_CoreSession_setAutoHangup},
    { "setHangupHook", _wrap_CoreSession_setHangupHook},
    { "ready", _wrap_CoreSession_ready},
    { "bridged", _wrap_CoreSession_bridged},
    { "answered", _wrap_CoreSession_answered},
    { "mediaReady", _wrap_CoreSession_mediaReady},
    { "waitForAnswer", _wrap_CoreSession_waitForAnswer},
    { "execute", _wrap_CoreSession_execute},
    { "sendEvent", _wrap_CoreSession_sendEvent},
    { "setEventData", _wrap_CoreSession_setEventData},
    { "getXMLCDR", _wrap_CoreSession_getXMLCDR},
    { "begin_allow_threads", _wrap_CoreSession_begin_allow_threads},
    { "end_allow_threads", _wrap_CoreSession_end_allow_threads},
    { "get_uuid", _wrap_CoreSession_get_uuid},
    { "get_cb_args", _wrap_CoreSession_get_cb_args},
    { "check_hangup_hook", _wrap_CoreSession_check_hangup_hook},
    { "run_dtmf_callback", _wrap_CoreSession_run_dtmf_callback},
    { "consoleLog", _wrap_CoreSession_consoleLog},
    { "consoleLog2", _wrap_CoreSession_consoleLog2},
    {0,0}
};

在这里可以看到很熟悉的answer,bridge等方法了。

2025-10-10 11:14:24.537709 99.90% [ERR:7f4999ee0700] switch_cpp.cpp:1465 ❌ 脚本执行错误: /usr/local/freeswitch/scripts/lua/modules/query_concurrent.lua:58: attempt to call field 'addEventListener' (a nil value) 代码如下-- query_concurrent.lua -- 实际可行的并发统计方案 local M = {} -- 获取当前并发数 - 使用通道遍历的正确方法 function M.get_total_concurrency(prefixes) if type(prefixes) == "string" then prefixes = {prefixes} end -- 初始化并发计数,使用一个表来分别记录每个前缀的并发数 local concurrent_counts = {} for _, prefix in ipairs(prefixes) do concurrent_counts[prefix] = 0 end -- 用于存储每个通道的信息 local channel_info = {} -- 事件处理函数 function event_handler(session, event) local event_name = event:getHeader("Event-Name") if event_name == "CHANNEL_CREATE" then -- 判断是否进入拨号计划 local dialplan = event:getHeader("Dialplan") if dialplan then local uuid = event:getHeader("Unique-ID") local caller_id = event:getHeader("Caller-Caller-ID-Number") local callee_id = event:getHeader("Caller-Destination-Number") for _, prefix in ipairs(prefixes) do if string.sub(caller_id, 1, #prefix) == prefix then concurrent_counts[prefix] = concurrent_counts[prefix] + 1 channel_info[uuid] = { caller_id = caller_id, callee_id = callee_id, start_time = os.time(), prefix = prefix } freeswitch.consoleLog("info", string.format("New call entered dialplan with prefix %s. Concurrent count for %s: %d, Caller: %s, Callee: %s", prefix, prefix, concurrent_counts[prefix], caller_id, callee_id)) break end end end elseif event_name == "CHANNEL_DESTROY" then local uuid = event:getHeader("Unique-ID") if channel_info[uuid] then local prefix = channel_info[uuid].prefix concurrent_counts[prefix] = concurrent_counts[prefix] - 1 local info = channel_info[uuid] local duration = os.time() - info.start_time freeswitch.consoleLog("info", string.format("Call left dialplan with prefix %s. Concurrent count for %s: %d, Caller: %s, Callee: %s, Duration: %d seconds", prefix, prefix, concurrent_counts[prefix], info.caller_id, info.callee_id, duration)) channel_info[uuid] = nil end end end -- 注册事件监听器 freeswitch.addEventListener("CHANNEL_CREATE", event_handler) freeswitch.addEventListener("CHANNEL_DESTROY", event_handler) end return M
最新发布
10-11
FreeSWITCH 是一个开源的电话软交换平台,支持多种通信协议和模块化架构。`mod_pgsql` 是 FreeSWATCH 提供的一个模块,用于与 PostgreSQL 数据库进行交互。在使用 `mod_pgsql` 时,如果遇到连接数据库失败并提示 `could not generate nonce` 错误,通常涉及以下几个方面的问题: 1. **PostgreSQL 连接配置问题** 检查 `mod_pgsql` 的配置文件(通常是 `pgsql.conf.xml`),确保数据库连接参数(如主机名、端口、用户名、密码、数据库名称)正确无误。特别是密码字段,若密码中包含特殊字符,需要进行适当的转义处理。 2. **nonce 生成失败的原因** `nonce` 是一种用于身份验证的一次性随机值,在 `mod_pgsql` 中可能用于某些安全机制或会话管理。若系统无法生成 `nonce`,可能是由于以下原因: - 系统熵不足:FreeSWITCH 依赖系统的随机数生成器来创建 `nonce`,如果系统熵池耗尽,可能导致生成失败。可以通过检查 `/proc/sys/kernel/random/entropy_avail` 来确认当前可用熵值。 - 随机数生成函数调用失败:某些情况下,FreeSWITCH 可能无法正确调用底层的随机数生成函数,导致 `nonce` 生成失败。可以尝试更新 FreeSWITCH 到最新版本以修复潜在的 bug。 3. **权限问题** 确保运行 FreeSWITCH 的用户对相关目录和文件具有足够的读写权限,尤其是临时文件目录(如 `/tmp` 或自定义的临时路径)。`mod_pgsql` 在生成 `nonce` 时可能需要写入临时文件。 4. **日志信息分析** 查看 FreeSWITCH 的日志文件(通常位于 `logs/freeswitch.log`),获取更详细的错误信息。日志中可能会记录具体的失败原因,例如数据库连接超时、认证失败等。 5. **PostgreSQL 服务状态** 确认 PostgreSQL 服务正在运行,并且可以从运行 FreeSWITCH 的机器上访问。可以通过简单的 `psql` 命令测试连接,例如: ```bash psql -h <host> -U <user> -d <database> ``` 如果无法通过 `psql` 连接,则说明网络或数据库服务存在问题。 6. **加密设置问题** 若 PostgreSQL 启用了 SSL 加密连接,而 `mod_pgsql` 未正确配置 SSL 参数,也可能导致连接失败。可以在 `pgsql.conf.xml` 中添加 `sslmode=require` 或其他合适的 SSL 模式以匹配 PostgreSQL 的配置。 7. **依赖库缺失或版本不兼容** 确保 FreeSWITCH 编译时包含了 `mod_pgsql` 所需的所有依赖库,尤其是 PostgreSQL 客户端库。可以通过以下命令检查是否安装了必要的库: ```bash ldd /usr/lib/freeswitch/mod/mod_pgsql.so | grep libpq ``` ### 示例配置片段 以下是一个典型的 `pgsql.conf.xml` 配置示例,供参考: ```xml <configuration name="pgsql.conf" description="PostgreSQL Connection"> <settings> <param name="dsn" value="host=localhost port=5432 dbname=freeswitch user=freeswitch password=yourpassword"/> <param name="connect-timeout" value="10"/> <param name="query-timeout" value="30"/> <param name="sslmode" value="disable"/> </settings> </configuration> ``` ### 排查建议 - 确保 PostgreSQL 服务正常运行,并且允许远程连接(如果是跨主机部署)。 - 使用 `freeswitch -nc` 启动 FreeSWITCH 并启用控制台模式,观察启动过程中的详细输出。 - 尝试简化 `mod_pgsql` 的配置,逐步排除复杂配置带来的干扰。 - 更新 FreeSWITCH 和 PostgreSQL 到最新稳定版本,确保没有已知的安全漏洞或兼容性问题。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值