kcp学习-通用的kcp lua绑定

kcp学习-通用的kcp lua绑定

  • 源码
  • [架构对比:lkcp.c vs lkcpsn.c](#架构对比:lkcp.c vs lkcpsn.c)
  • 核心组件详解
    • [1. Callback 结构体](#1. Callback 结构体)
    • [2. 线程安全机制](#2. 线程安全机制)
    • [3. 核心回调函数](#3. 核心回调函数)
    • [4. Lua C 函数详解](#4. Lua C 函数详解)
    • [5. 模块初始化](#5. 模块初始化)
  • 完整使用示例
  • 总结

一个通用版本的 KCP Lua 绑定,不依赖于特定的框架(如 Skynet),可以独立使用

源码

lkcp.c

c 复制代码
/**
 *
 * Copyright (C) 2015 by David Lin
 * 
 * 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 DEALING IN
 * THE SOFTWARE.
 *
 */

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include "ikcp.h"

#define RECV_BUFFER_LEN 4*1024*1024

#define check_kcp(L, idx)\
    *(ikcpcb**)luaL_checkudata(L, idx, "kcp_meta")

#define check_buf(L, idx)\
    (char*)luaL_checkudata(L, idx, "recv_buffer")

struct Callback {
    uint64_t handle;
    lua_State* L;
    lua_State* eL;
};

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

// #define logs_on

#define lock_on \
    // if(pthread_mutex_lock(&lock) != 0) {\
    //     luaL_error(L, "pthread_mutex_lock error");\
    // } else {\
    //     printf("pthread_mutex_lock lock\n");\
    // }\

#define lock_off \
    // pthread_mutex_unlock(&lock);\
    // printf("pthread_mutex_lock unlock\n");\

static int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg) {
    struct Callback* c = (struct Callback*)arg;
    lua_State* L = c->eL;
    uint64_t handle = c->handle;

    if (handle > 0 && L) {
        #ifdef logs_on
            printf("debug kcp kcp_output_callback do kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
        #endif
        lua_rawgeti(L, LUA_REGISTRYINDEX, handle);
        if(lua_isfunction(L, -1)) {
            lua_pushlstring(L, buf, len);
            lua_call(L, 1, 0);
        } else {
            printf("debug kcp kcp_output_callback error1 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
        }
    } else {
        printf("debug kcp kcp_output_callback error2 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
    }

    return 0;
}

static int kcp_gc(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL) {
        printf("debug kcp kcp_gc error L=%p\n", L);
        lock_off
        return 0;
    }
    if (kcp->user != NULL) {
        struct Callback* c = (struct Callback*)kcp->user;
        #ifdef logs_on
            printf("debug kcp kcp_gc kcp=%p L=%p eL=%p thisL=%p handle=%ld\n", kcp, c->L, c->eL, L, c->handle);
        #endif
        if(c->handle > 0) {
            luaL_unref(c->L, LUA_REGISTRYINDEX, c->handle);
            c->handle = 0;
        }
        c->L = NULL;
        c->eL = NULL;
        free(c);
        kcp->user = NULL;
    }
    ikcp_release(kcp);
    kcp = NULL;
    
    lock_off
    return 0;
}

static int lkcp_create(lua_State* L) {
    lock_on
    uint64_t handle = luaL_ref(L, LUA_REGISTRYINDEX);
    int32_t conv = luaL_checkinteger(L, 1);

    struct Callback* c = malloc(sizeof(struct Callback));
    memset(c, 0, sizeof(struct Callback));
    c->handle = handle;
    c->L = L;
    c->eL = L;

    ikcpcb* kcp = ikcp_create(conv, (void*)c);
    if (kcp == NULL) {
        free(c);
        lua_pushnil(L);
        lua_pushstring(L, "error: fail to create kcp");
        lock_off
        return 2;
    }

    kcp->output = kcp_output_callback;

    *(ikcpcb**)lua_newuserdata(L, sizeof(void*)) = kcp;
    luaL_getmetatable(L, "kcp_meta");
    lua_setmetatable(L, -2);

    #ifdef logs_on
        printf("debug kcp lkcp_create kcp=%p L=%p handle=%ld conv=%d\n", kcp, L, c->handle, conv);
    #endif
    lock_off
    return 1;
}

static int lkcp_recv(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    lua_getfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer");
    char* buf = check_buf(L, -1);
    lua_pop(L, 1);

    int32_t hr = ikcp_recv(kcp, buf, RECV_BUFFER_LEN);
    if (hr <= 0) {
        lua_pushinteger(L, hr);
        lock_off
        return 1;
    }
    lua_pushinteger(L, hr);
    lua_pushlstring(L, (const char *)buf, hr);

    #ifdef logs_on
        printf("debug kcp lkcp_recv kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 2;
}

static int lkcp_send(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    size_t size;
    const char *data = luaL_checklstring(L, 2, &size);
    int32_t hr = ikcp_send(kcp, data, size);
    lua_pushinteger(L, hr);

    #ifdef logs_on
        printf("debug kcp lkcp_send kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 1;
}

static int lkcp_update(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    int32_t current = luaL_checkinteger(L, 2);
    struct Callback* c = (struct Callback*)kcp->user;
    c->eL = L;
    ikcp_update(kcp, current);

    #ifdef logs_on
        printf("debug kcp lkcp_update kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 0;
}

static int lkcp_check(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    int32_t current = luaL_checkinteger(L, 2);
    int32_t hr = ikcp_check(kcp, current);
    lua_pushinteger(L, hr);

    #ifdef logs_on
        printf("debug kcp lkcp_check kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 1;
}

static int lkcp_input(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    size_t size;
    const char *data = luaL_checklstring(L, 2, &size);
    int32_t hr = ikcp_input(kcp, data, size);
    lua_pushinteger(L, hr);
    
    #ifdef logs_on
        printf("debug kcp lkcp_input kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 1;
}

static int lkcp_flush(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    struct Callback* c = (struct Callback*)kcp->user;
    c->eL = L;
    ikcp_flush(kcp);

    #ifdef logs_on
        printf("debug kcp lkcp_flush kcp=%p L=%p handle=%ld\n", kcp, L, ((struct Callback*)kcp->user)->handle);
    #endif
    lock_off
    return 0;
}

static int lkcp_wndsize(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    int32_t sndwnd = luaL_checkinteger(L, 2);
    int32_t rcvwnd = luaL_checkinteger(L, 3);
    ikcp_wndsize(kcp, sndwnd, rcvwnd);

    lock_off
    return 0;
}

static int lkcp_nodelay(lua_State* L) {
    lock_on
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    int32_t nodelay = luaL_checkinteger(L, 2);
    int32_t interval = luaL_checkinteger(L, 3);
    int32_t resend = luaL_checkinteger(L, 4);
    int32_t nc = luaL_checkinteger(L, 5);
    int32_t hr = ikcp_nodelay(kcp, nodelay, interval, resend, nc);
    lua_pushinteger(L, hr);

    lock_off
    return 1;
}

static const struct luaL_Reg lkcp_methods [] = {
    { "lkcp_recv" , lkcp_recv },
    { "lkcp_send" , lkcp_send },
    { "lkcp_update" , lkcp_update },
    { "lkcp_check" , lkcp_check },
    { "lkcp_input" , lkcp_input },
    { "lkcp_flush" , lkcp_flush },
    { "lkcp_wndsize" , lkcp_wndsize },
    { "lkcp_nodelay" , lkcp_nodelay },
    {NULL, NULL},
};

static const struct luaL_Reg l_methods[] = {
    { "lkcp_create" , lkcp_create },
    {NULL, NULL},
};

int luaopen_lkcp(lua_State* L) {
    luaL_checkversion(L);

    luaL_newmetatable(L, "kcp_meta");

    lua_newtable(L);
    luaL_setfuncs(L, lkcp_methods, 0);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, kcp_gc);
    lua_setfield(L, -2, "__gc");

    luaL_newmetatable(L, "recv_buffer");

    char* global_recv_buffer = lua_newuserdata(L, sizeof(char)*RECV_BUFFER_LEN);
    memset(global_recv_buffer, 0, sizeof(char)*RECV_BUFFER_LEN);
    luaL_getmetatable(L, "recv_buffer");
    lua_setmetatable(L, -2);
    lua_setfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer");

    luaL_newlib(L, l_methods);

    return 1;
}

架构对比:lkcp.c vs lkcpsn.c

特性 lkcp.c(通用版) lkcpsn.c(Skynet版)
依赖 无框架依赖 依赖 Skynet 框架
输出方式 Lua 回调函数 直接通过 Skynet UDP 发送
地址管理 无地址管理 内置地址管理
线程模型 可选线程安全 单线程安全
使用场景 通用 Lua 项目 Skynet 专用

核心组件详解

1. Callback 结构体

c 复制代码
struct Callback {
    uint64_t handle;      // Lua 回调函数的引用(Registry 索引)
    lua_State* L;        // 创建 KCP 时的 Lua 状态
    lua_State* eL;       // 当前执行时的 Lua 状态
};
  • handle: Lua 函数的引用 ID,通过 luaL_ref 获得
  • L: 保存创建时的 Lua 状态,用于垃圾回收时解除引用
  • eL: 实时更新,确保在正确的 Lua 上下文中执行回调

2. 线程安全机制

c 复制代码
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

#define lock_on \
    // if(pthread_mutex_lock(&lock) != 0) {\
    //     luaL_error(L, "pthread_mutex_lock error");\
    // } else {\
    //     printf("pthread_mutex_lock lock\n");\
    // }\

#define lock_off \
    // pthread_mutex_unlock(&lock);\
    // printf("pthread_mutex_lock unlock\n");\
  • 默认禁用:锁相关代码被注释掉,单线程模式下性能更好
  • 可启用:需要多线程时取消注释即可
  • 设计考虑:大多数 Lua 应用是单线程的,避免不必要的锁开销

3. 核心回调函数

kcp_output_callback - 输出回调

c 复制代码
static int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg) {
    struct Callback* c = (struct Callback*)arg;
    lua_State* L = c->eL;
    uint64_t handle = c->handle;

    if (handle > 0 && L) {
        #ifdef logs_on
            printf("debug kcp kcp_output_callback do kcp=%p L=%p eL=%p handle=%ld\n", 
                   kcp, c->L, c->eL, c->handle);
        #endif
        
        // 1. 从 Registry 中获取 Lua 函数
        lua_rawgeti(L, LUA_REGISTRYINDEX, handle);
        
        // 2. 检查是否为函数
        if(lua_isfunction(L, -1)) {
            // 3. 将数据推入栈
            lua_pushlstring(L, buf, len);
            // 4. 调用 Lua 函数 (1个参数,0个返回值)
            lua_call(L, 1, 0);
        } else {
            printf("debug kcp kcp_output_callback error1\n");
        }
    } else {
        printf("debug kcp kcp_output_callback error2\n");
    }

    return 0;
}

工作流程:

  1. 从 handle 获取 Lua 回调函数
  2. 检查函数有效性
  3. 将 KCP 数据作为参数调用 Lua 函数
  4. Lua 函数负责实际的数据发送(如 UDP 发送)

4. Lua C 函数详解

lkcp_create - 创建 KCP 对象

c 复制代码
static int lkcp_create(lua_State* L) {
    lock_on
    
    // 获取参数:第一个参数是会话ID,栈顶是回调函数
    uint64_t handle = luaL_ref(L, LUA_REGISTRYINDEX);  // 将栈顶函数保存到 Registry
    int32_t conv = luaL_checkinteger(L, 1);           // 会话ID
    
    // 创建回调结构
    struct Callback* c = malloc(sizeof(struct Callback));
    memset(c, 0, sizeof(struct Callback));
    c->handle = handle;  // Lua 函数引用
    c->L = L;            // 当前 Lua 状态
    c->eL = L;           // 执行 Lua 状态(初始相同)
    
    // 创建 KCP 实例
    ikcpcb* kcp = ikcp_create(conv, (void*)c);
    if (kcp == NULL) {
        free(c);
        lua_pushnil(L);
        lua_pushstring(L, "error: fail to create kcp");
        lock_off
        return 2;
    }
    
    // 设置输出回调
    kcp->output = kcp_output_callback;
    
    // 创建 Lua userdata
    *(ikcpcb**)lua_newuserdata(L, sizeof(void*)) = kcp;
    luaL_getmetatable(L, "kcp_meta");
    lua_setmetatable(L, -2);
    
    #ifdef logs_on
        printf("debug kcp lkcp_create kcp=%p L=%p handle=%ld conv=%d\n", 
               kcp, L, c->handle, conv);
    #endif
    
    lock_off
    return 1;
}

Lua 调用方式:

lua 复制代码
local kcp = lkcp.lkcp_create(conv, function(data)
    -- 这里处理数据发送
    udp_socket:send(data)
end)

lkcp_recv - 接收数据

c 复制代码
static int lkcp_recv(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    // 从 Registry 获取全局接收缓冲区
    lua_getfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer");
    char* buf = check_buf(L, -1);  // 4MB 的缓冲区
    lua_pop(L, 1);
    
    // 调用 KCP 接收
    int32_t hr = ikcp_recv(kcp, buf, RECV_BUFFER_LEN);
    
    if (hr <= 0) {
        // 错误码返回
        lua_pushinteger(L, hr);
        lock_off
        return 1;
    }
    
    // 成功:返回长度和数据
    lua_pushinteger(L, hr);
    lua_pushlstring(L, (const char *)buf, hr);
    
    lock_off
    return 2;
}

缓冲区管理:

  • 全局缓冲区:所有 KCP 实例共享一个 4MB 的缓冲区
  • 避免频繁分配:减少内存分配开销
  • 线程安全:由于是只读操作,多线程安全

lkcp_send - 发送数据

c 复制代码
static int lkcp_send(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    size_t size;
    const char *data = luaL_checklstring(L, 2, &size);
    
    // 调用 KCP 发送
    int32_t hr = ikcp_send(kcp, data, size);
    lua_pushinteger(L, hr);  // 返回结果
    
    lock_off
    return 1;
}

注意:这里只是将数据放入 KCP 发送队列,实际发送由 kcp_output_callback 触发

lkcp_update - 更新 KCP 状态

c 复制代码
static int lkcp_update(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    int32_t current = luaL_checkinteger(L, 2);
    
    // 更新当前 Lua 状态
    struct Callback* c = (struct Callback*)kcp->user;
    c->eL = L;  // 确保回调在正确的上下文中执行
    
    // 更新 KCP
    ikcp_update(kcp, current);
    
    lock_off
    return 0;
}

关键点:

  • 更新 c->eL = L:确保输出回调在正确的 Lua 上下文中
  • 定期调用:每 10-100ms 调用一次

lkcp_check - 检查下次更新时间

c 复制代码
static int lkcp_check(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    int32_t current = luaL_checkinteger(L, 2);
    int32_t hr = ikcp_check(kcp, current);
    
    lua_pushinteger(L, hr);  // 返回下次更新时间
    
    lock_off
    return 1;
}

优化用途:

c 复制代码
local next_time = kcp:lkcp_check(current_time)
if next_time > current_time then
    -- 可以休眠到 next_time
    sleep(next_time - current_time)
end

lkcp_input - 输入网络数据

c 复制代码
static int lkcp_input(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    size_t size;
    const char *data = luaL_checklstring(L, 2, &size);
    
    // 将网络数据输入到 KCP
    int32_t hr = ikcp_input(kcp, data, size);
    lua_pushinteger(L, hr);  // 返回结果
    
    lock_off
    return 1;
}

使用场景:

c 复制代码
-- 当收到 UDP 数据时
udp_socket:receive(function(data)
    local result = kcp:lkcp_input(data)
    if result < 0 then
        print("KCP input error:", result)
    end
end)

lkcp_flush - 强制刷新

c 复制代码
static int lkcp_flush(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    // 更新执行上下文
    struct Callback* c = (struct Callback*)kcp->user;
    c->eL = L;
    
    // 强制刷新发送缓冲区
    ikcp_flush(kcp);
    
    lock_off
    return 0;
}

注意:通常不需要手动调用,ikcp_update 会自动处理

lkcp_wndsize - 设置窗口大小

c 复制代码
static int lkcp_wndsize(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    int32_t sndwnd = luaL_checkinteger(L, 2);  // 发送窗口
    int32_t rcvwnd = luaL_checkinteger(L, 3);  // 接收窗口
    
    ikcp_wndsize(kcp, sndwnd, rcvwnd);
    
    lock_off
    return 0;
}

lkcp_nodelay - 设置快速模式

c 复制代码
static int lkcp_nodelay(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL || kcp->user == NULL) {
        lua_pushnil(L);
        lua_pushstring(L, "error: kcp not args");
        lock_off
        return 2;
    }
    
    int32_t nodelay = luaL_checkinteger(L, 2);   // 是否启用无延迟
    int32_t interval = luaL_checkinteger(L, 3);  // 更新间隔
    int32_t resend = luaL_checkinteger(L, 4);    // 快速重传次数
    int32_t nc = luaL_checkinteger(L, 5);        // 是否禁用拥塞控制
    
    int32_t hr = ikcp_nodelay(kcp, nodelay, interval, resend, nc);
    lua_pushinteger(L, hr);  // 返回结果
    
    lock_off
    return 1;
}

kcp_gc - 垃圾回收

c 复制代码
static int kcp_gc(lua_State* L) {
    lock_on
    
    ikcpcb* kcp = check_kcp(L, 1);
    if (kcp == NULL) {
        printf("debug kcp kcp_gc error L=%p\n", L);
        lock_off
        return 0;
    }
    
    if (kcp->user != NULL) {
        struct Callback* c = (struct Callback*)kcp->user;
        
        #ifdef logs_on
            printf("debug kcp kcp_gc kcp=%p L=%p eL=%p thisL=%p handle=%ld\n", 
                   kcp, c->L, c->eL, L, c->handle);
        #endif
        
        // 1. 释放 Lua 函数引用
        if(c->handle > 0) {
            luaL_unref(c->L, LUA_REGISTRYINDEX, c->handle);
            c->handle = 0;
        }
        
        // 2. 清理指针
        c->L = NULL;
        c->eL = NULL;
        
        // 3. 释放内存
        free(c);
        kcp->user = NULL;
    }
    
    // 4. 释放 KCP 实例
    ikcp_release(kcp);
    kcp = NULL;
    
    lock_off
    return 0;
}

关键步骤:

  1. 从 Registry 中移除 Lua 函数引用
  2. 防止野指针
  3. 释放回调结构内存
  4. 释放 KCP 实例

5. 模块初始化

luaopen_lkcp - 模块入口

c 复制代码
int luaopen_lkcp(lua_State* L) {
    luaL_checkversion(L);  // 检查 Lua 版本兼容性
    
    // 1. 创建 KCP 元表
    luaL_newmetatable(L, "kcp_meta");
    
    // 2. 设置元方法
    lua_newtable(L);
    luaL_setfuncs(L, lkcp_methods, 0);  // 注册方法表
    lua_setfield(L, -2, "__index");     // 设置索引元方法
    
    // 3. 设置垃圾回收元方法
    lua_pushcfunction(L, kcp_gc);
    lua_setfield(L, -2, "__gc");
    
    // 4. 创建接收缓冲区元表
    luaL_newmetatable(L, "recv_buffer");
    
    // 5. 创建全局接收缓冲区(4MB)
    char* global_recv_buffer = lua_newuserdata(L, sizeof(char)*RECV_BUFFER_LEN);
    memset(global_recv_buffer, 0, sizeof(char)*RECV_BUFFER_LEN);
    
    // 6. 设置元表
    luaL_getmetatable(L, "recv_buffer");
    lua_setmetatable(L, -2);
    
    // 7. 保存到 Registry
    lua_setfield(L, LUA_REGISTRYINDEX, "kcp_lua_recv_buffer");
    
    // 8. 创建模块函数表
    luaL_newlib(L, l_methods);
    
    return 1;  // 返回模块表
}

完整使用示例

lua 复制代码
-- 1. 加载模块
local lkcp = require "lkcp"

-- 2. 创建 UDP socket(假设有 LuaSocket)
local socket = require "socket"
local udp = socket.udp()
udp:settimeout(0)
udp:setsockname("127.0.0.1", 8888)

-- 3. 创建 KCP 实例(会话ID为 12345)
local kcp = lkcp.lkcp_create(12345, function(data)
    -- 输出回调:发送数据到网络
    udp:sendto(data, "127.0.0.1", 9999)
end)

-- 4. 配置 KCP
kcp:lkcp_nodelay(1, 10, 2, 1)  -- 快速模式
kcp:lkcp_wndsize(128, 128)     -- 窗口大小

-- 5. 发送数据
function send_data(data)
    local result = kcp:lkcp_send(data)
    if result < 0 then
        print("Send error:", result)
    end
end

-- 6. 接收数据循环
function receive_loop()
    while true do
        -- 检查网络数据
        local data, ip, port = udp:receivefrom()
        if data then
            -- 输入到 KCP
            local result = kcp:lkcp_input(data)
            if result < 0 then
                print("Input error:", result)
            end
        end
        
        -- 更新 KCP
        local current_time = socket.gettime() * 1000  -- 毫秒
        kcp:lkcp_update(math.floor(current_time))
        
        -- 接收 KCP 数据
        local len, kcp_data = kcp:lkcp_recv()
        if len > 0 then
            -- 处理应用层数据
            process_application_data(kcp_data)
        end
        
        -- 使用 ikcp_check 优化
        local next_time = kcp:lkcp_check(math.floor(current_time))
        if next_time > current_time then
            -- 休眠到下次更新时间
            socket.sleep((next_time - current_time) / 1000)
        else
            socket.sleep(0.001)  -- 1ms
        end
    end
end

总结

这个 lkcp.c 文件实现了一个干净、高效、可移植的 KCP Lua 绑定:

优点:

  1. 无外部依赖:纯 C + Lua,易于集成到任何项目
  2. 设计简洁:回调机制灵活,适应各种网络后端
  3. 内存高效:共享接收缓冲区,减少内存分配
  4. 线程安全可选:默认单线程模式,性能更好
  5. 完整的垃圾回收:避免内存泄漏

缺点:

  1. 需要手动处理网络 I/O:回调函数中需要自行实现发送逻辑
  2. 地址管理需要外部处理:不像 lkcpsn.c 那样内置地址管理
相关推荐
hero.fei1 小时前
kaptcha 验证码生成工具在springboot中集成
java·spring boot·后端
Ydwlcloud2 小时前
AWS 2026折扣活动深度解析:寻找最大优惠的智慧路径
大数据·服务器·人工智能·云计算·aws
曾浩轩2 小时前
图灵完备Turing Complete 2
学习·图灵完备
csbysj20202 小时前
并查集路径压缩
开发语言
JavaEdge.2 小时前
java.io.IOException: Previous writer likely failed to write hdfs报错解决方案
java·开发语言·hdfs
松涛和鸣2 小时前
DAY55 Getting Started with ARM and IMX6ULL
linux·服务器·网络·arm开发·数据库·html
w***76552 小时前
存储技术全景:从基础原理到未来趋势
spring boot·后端·mybatis
J_liaty2 小时前
基于ip2region.xdb数据库从IP获取到属地解析全攻略
java·网络·后端
C+++Python2 小时前
C++类型判断
开发语言·c++