kcp学习-skynet中的kcp绑定
- 源码
- 整体架构
- 文件结构详解
-
- [1. 头文件包含](#1. 头文件包含)
- [2. 宏定义与常量](#2. 宏定义与常量)
- [3. 全局变量与锁机制](#3. 全局变量与锁机制)
- [4. Callback 结构体](#4. Callback 结构体)
- [5. 辅助函数](#5. 辅助函数)
- [6. 核心回调函数](#6. 核心回调函数)
- [7. Lua C 函数实现](#7. Lua C 函数实现)
- 接口映射表
- 数据流详解
- 配置与调优
- 实现了什么?
可以在 Skynet 中轻松使用 KCP 协议,获得比 TCP 更低的延迟,同时保持可靠性
源码
lkcpsn.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"
#include "../../../skynet/skynet-src/skynet_server.h"
#include "../../../skynet/skynet-src/socket_buffer.h"
#include "../../../skynet/skynet-src/skynet_socket.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;
char *address;
size_t addresssz;
};
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static lua_State *LL = NULL;
static uint64_t svrsock = 0;
// #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 void print_address(const uint8_t *addr, size_t sz) {
uint16_t port = 0;
memcpy(&port, addr+1, sizeof(uint16_t));
port = ntohs(port);
const void * src = addr+3;
char tmp[256];
int family;
if (sz == 1+2+4) {
family = AF_INET;
} else {
if (sz != 1+2+16) {
printf("debug kcp print_address error1\n");
return;
}
family = AF_INET6;
}
if (inet_ntop(family, src, tmp, sizeof(tmp)) == NULL) {
printf("debug kcp print_address error2\n");
return;
}
printf("debug kcp print_address ip=%s port=%d sz=%d\n", tmp, port, sz);
}
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);
// }
if(!c->address) {
printf("debug kcp kcp_output_callback error1 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
luaL_error(L, "kcp_output_callback error1: address invalid");
return 1;
}
// struct skynet_context *ctx = lua_touserdata(L, lua_upvalueindex(1));
lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");
struct skynet_context *ctx = lua_touserdata(L, -1);
if(!ctx) {
printf("debug kcp kcp_output_callback error2 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
luaL_error(L, "kcp_output_callback error2: ctx invalid");
return 1;
}
lua_pop(L, 1);
struct socket_sendbuffer buf2;
buf2.id = svrsock;
buf2.type = SOCKET_BUFFER_RAWPOINTER;
lua_getglobal(LL, "pack_output");
if(!lua_isfunction(LL, -1)) {
printf("debug kcp kcp_output_callback error3 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
luaL_error(L, "kcp_output_callback error3: pack_output invalid");
return 1;
}
lua_pushinteger(LL, kcp->conv);
lua_pushlstring(LL, buf, len);
lua_pcall(LL, 2, 1, 0);
buf2.buffer = luaL_checklstring(LL, 1, &buf2.sz);
lua_pop(LL, 1);
if(!buf2.buffer) {
printf("debug kcp kcp_output_callback error4 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
luaL_error(L, "kcp_output_callback error4: buf2.buffer invalid");
return 1;
}
int err = skynet_socket_udp_sendbuffer(ctx, c->address, &buf2);
if(err) {
printf("debug kcp kcp_output_callback error5 kcp=%p L=%p eL=%p handle=%ld\n", kcp, c->L, c->eL, c->handle);
luaL_error(L, "kcp_output_callback error5: skynet_socket_udp_sendbuffer fail");
return 1;
}
#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
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;
if(c->address) {
free(c->address);
c->address = 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);
size_t sz = 0;
uint64_t sock = luaL_checkinteger(L, 1);
const char *address = luaL_checklstring(L, 2, &sz);
int32_t conv = luaL_checkinteger(L, 3);
if(sock && !svrsock) {
svrsock = sock;
}
struct Callback* c = malloc(sizeof(struct Callback));
memset(c, 0, sizeof(struct Callback));
c->handle = 0;
c->L = L;
c->eL = L;
c->address = (char*)malloc(sz+1);
memset(c->address, 0, sz+1);
memcpy(c->address, address, sz);
c->addresssz = sz;
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
print_address(c->address, c->addresssz);
printf("debug kcp lkcp_create kcp=%p L=%p handle=%ld conv=%d svrsock=%ld address=%s addresssz=%d\n", kcp, L, c->handle, conv, svrsock, c->address, c->addresssz);
// #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);
size_t sz;
const uint8_t *address = (const uint8_t *)luaL_checklstring(L, 3, &sz);
if(address && sz) {
struct Callback* c = (struct Callback*)kcp->user;
if(c->addresssz != sz) {
free(c->address);
c->address = (char*)malloc(sz+1);
c->addresssz = sz;
}
memset(c->address, 0, sz+1);
memcpy(c->address, address, sz);
}
#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);
size_t sz;
const uint8_t *address = (const uint8_t *)luaL_checklstring(L, 3, &sz);
if(address && sz) {
struct Callback* c = (struct Callback*)kcp->user;
if(c->addresssz != sz) {
free(c->address);
c->address = (char*)malloc(sz+1);
c->addresssz = sz;
}
memset(c->address, 0, sz+1);
memcpy(c->address, address, sz);
}
#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_lkcpsn(lua_State* L) {
luaL_checkversion(L);
if(LL == NULL) {
LL = luaL_newstate();
luaL_openlibs(LL);
char code[512] = "\
function pack_output(subid, buf)\
return string.pack(\">I4s2\", subid, buf)\
end\
";
luaL_dostring(LL, code);
// test
// printf("luaopen_lkcp L=%p size2=%d\n", L, lua_gettop(LL));
// int subid =100;
// char str[128] = "dffccc";
// lua_getglobal(LL, "pack_output");
// lua_pushinteger(LL, subid);
// lua_pushstring(LL, &str);
// lua_call(LL, 2, 1);
// size_t sz;
// const char *r = luaL_checklstring(LL, 1, &sz);
// printf("==xxxxxxrr r==%s sz==%d\n", r, sz);
// lua_pop(LL, 1);
}
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;
}
整体架构
bash
+----------------+ +---------------+ +----------------+
| Lua Layer | ---> | lkcpsn.c | ---> | KCP Protocol |
+----------------+ +---------------+ +----------------+
↓ ↓ ↓
+----------------+ +---------------+ +----------------+
| Skynet Service | <--> | UDP Network | <--> | Remote Peer |
+----------------+ +---------------+ +----------------+
文件结构详解
1. 头文件包含
c
// 系统头文件
#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>
// Lua 头文件
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// KCP 头文件
#include "ikcp.h"
// Skynet 头文件
#include "../../../skynet/skynet-src/skynet_server.h"
#include "../../../skynet/skynet-src/socket_buffer.h"
#include "../../../skynet/skynet-src/skynet_socket.h"
2. 宏定义与常量
c
#define RECV_BUFFER_LEN 4*1024*1024 // 4MB 接收缓冲区
// Lua 类型检查宏
#define check_kcp(L, idx) \
*(ikcpcb**)luaL_checkudata(L, idx, "kcp_meta")
#define check_buf(L, idx) \
(char*)luaL_checkudata(L, idx, "recv_buffer")
// 调试日志控制
// #define logs_on // 注释掉表示关闭调试日志
3. 全局变量与锁机制
c
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static lua_State *LL = NULL; // 全局 Lua 状态(用于 pack_output)
static uint64_t svrsock = 0; // Skynet UDP socket ID
// 线程锁宏(当前被注释掉,表示单线程模式)
#define lock_on // 空
#define lock_off // 空
4. Callback 结构体
c
struct Callback {
uint64_t handle; // Lua 回调函数引用(当前未使用)
lua_State* L; // 创建时的 Lua 状态
lua_State* eL; // 当前执行时的 Lua 状态
char *address; // 目标地址(二进制格式)
size_t addresssz; // 地址长度
};
5. 辅助函数
print_address - 地址打印
c
static void print_address(const uint8_t *addr, size_t sz) {
// 解析并打印二进制地址
// 格式:[type(1字节) + port(2字节) + ip(4/16字节)]
// type: 1=IPv4, 2=IPv6
}
- 功能:调试用,打印二进制格式的地址
- 地址格式:
bash
IPv4: [type(1) + port(2) + ip(4)] = 7字节
IPv6: [type(1) + port(2) + ip(16)] = 19字节
6. 核心回调函数
kcp_output_callback - KCP 输出回调
c
static int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg) {
struct Callback* c = (struct Callback*)arg;
// 1. 获取 Skynet 上下文
lua_getfield(c->eL, LUA_REGISTRYINDEX, "skynet_context");
struct skynet_context *ctx = lua_touserdata(c->eL, -1);
lua_pop(c->eL, 1);
// 2. 调用 Lua 函数 pack_output 打包数据
lua_getglobal(LL, "pack_output");
lua_pushinteger(LL, kcp->conv); // 会话ID
lua_pushlstring(LL, buf, len); // KCP数据
lua_pcall(LL, 2, 1, 0);
// 3. 获取打包后的数据
const char* packed = luaL_checklstring(LL, 1, &size);
// 4. 通过 Skynet UDP 发送
struct socket_sendbuffer buf2;
buf2.id = svrsock;
buf2.type = SOCKET_BUFFER_RAWPOINTER;
buf2.buffer = packed;
buf2.sz = size;
skynet_socket_udp_sendbuffer(ctx, c->address, &buf2);
}
打包格式说明:
bash
-- Lua 中的 pack_output 函数
function pack_output(subid, buf)
-- 使用 string.pack 打包数据
-- ">I4s2" 表示:大端序的 4 字节整数 + 字符串(长度在前)
return string.pack(">I4s2", subid, buf)
end
-- 实际打包结果:
-- +------------+------------+-------------+
-- | 会话ID(4B) | 数据长度(2B)| 数据(N字节) |
-- +------------+------------+-------------+
7. Lua C 函数实现
luaopen_lkcpsn - 模块初始化
c
int luaopen_lkcpsn(lua_State* L) {
// 1. 创建全局 Lua 状态 LL(用于 pack_output)
if(LL == NULL) {
LL = luaL_newstate();
luaL_openlibs(LL);
// 定义 pack_output 函数
char code[512] = "function pack_output(subid, buf) return string.pack(\">I4s2\", subid, buf) end";
luaL_dostring(LL, code);
}
// 2. 创建 KCP 元表
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");
// 3. 创建接收缓冲区元表和全局缓冲区
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");
// 4. 注册模块函数
luaL_newlib(L, l_methods);
return 1;
}
lkcp_create - 创建 KCP 实例
c
static int lkcp_create(lua_State* L) {
// 参数:sock, address, conv
uint64_t sock = luaL_checkinteger(L, 1);
const char *address = luaL_checklstring(L, 2, &sz);
int32_t conv = luaL_checkinteger(L, 3);
// 保存 socket ID
if(sock && !svrsock) {
svrsock = sock;
}
// 创建 Callback 结构
struct Callback* c = malloc(sizeof(struct Callback));
c->handle = 0;
c->L = L;
c->eL = L;
c->address = (char*)malloc(sz+1);
memcpy(c->address, address, sz);
c->addresssz = sz;
// 创建 KCP 实例
ikcpcb* kcp = ikcp_create(conv, (void*)c);
kcp->output = kcp_output_callback;
// 创建 Lua userdata
*(ikcpcb**)lua_newuserdata(L, sizeof(void*)) = kcp;
luaL_getmetatable(L, "kcp_meta");
lua_setmetatable(L, -2);
return 1;
}
lkcp_recv - 接收数据
c
static int lkcp_recv(lua_State* L) {
ikcpcb* kcp = check_kcp(L, 1);
// 获取全局接收缓冲区
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); // 返回错误码
return 1;
}
// 返回数据和长度
lua_pushinteger(L, hr);
lua_pushlstring(L, (const char *)buf, hr);
return 2;
}
lkcp_send - 发送数据
c
static int lkcp_send(lua_State* L) {
ikcpcb* kcp = check_kcp(L, 1);
const char *data = luaL_checklstring(L, 2, &size);
// 发送数据
int32_t hr = ikcp_send(kcp, data, size);
lua_pushinteger(L, hr);
// 可选:更新目标地址
const uint8_t *address = (const uint8_t *)luaL_checklstring(L, 3, &sz);
if(address && sz) {
struct Callback* c = (struct Callback*)kcp->user;
// 重新分配地址内存
}
return 1;
}
lkcp_input - 输入网络数据
c
static int lkcp_input(lua_State* L) {
ikcpcb* kcp = check_kcp(L, 1);
const char *data = luaL_checklstring(L, 2, &size);
// 处理输入数据
int32_t hr = ikcp_input(kcp, data, size);
lua_pushinteger(L, hr);
// 可选:更新源地址
const uint8_t *address = (const uint8_t *)luaL_checklstring(L, 3, &sz);
if(address && sz) {
struct Callback* c = (struct Callback*)kcp->user;
// 更新地址
}
return 1;
}
kcp_gc - 垃圾回收
c
static int kcp_gc(lua_State* L) {
ikcpcb* kcp = check_kcp(L, 1);
if (kcp->user != NULL) {
struct Callback* c = (struct Callback*)kcp->user;
// 清理资源
if(c->address) {
free(c->address);
c->address = NULL;
}
free(c);
kcp->user = NULL;
}
ikcp_release(kcp);
return 0;
}
接口映射表
| Lua 函数名 | C 函数名 | KCP 函数 | 功能描述 |
|---|---|---|---|
| lkcp_create | lkcp_create | ikcp_create | 创建 KCP 实例 |
| lkcp_recv | lkcp_recv | ikcp_recv | 接收数据 |
| lkcp_send | lkcp_send | ikcp_send | 发送数据 |
| lkcp_update | lkcp_update | ikcp_update | 更新 KCP 状态 |
| lkcp_check | lkcp_check | ikcp_check | 检查下次更新时间 |
| lkcp_input | lkcp_input | ikcp_input | 输入网络数据 |
| lkcp_flush | lkcp_flush | ikcp_flush | 强制刷新发送缓冲区 |
| lkcp_wndsize | lkcp_wndsize | ikcp_wndsize | 设置窗口大小 |
| lkcp_nodelay | lkcp_nodelay | ikcp_nodelay | 设置快速模式参数 |
数据流详解
发送流程
bash
Lua发送数据 → lkcp_send() → ikcp_send() → 放入发送队列
↓
定时器触发 → lkcp_update() → ikcp_update() → ikcp_flush()
↓
需要发送 → kcp_output_callback() → pack_output() → 打包
↓
skynet_socket_udp_sendbuffer() → 网络发送
接收流程
bash
网络接收 → Skynet UDP回调 → on_udp_message()
↓
解包 → string.unpack(">I4s2") → 分离会话ID和数据
↓
lkcp_input() → ikcp_input() → 解析处理
↓
定时器 → lkcp_update() → 触发接收处理
↓
应用层 → lkcp_recv() → ikcp_recv() → 获取数据
配置与调优
1. 缓冲区配置
c
#define RECV_BUFFER_LEN 4*1024*1024 // 4MB
- 调优建议:
- 高并发:减小缓冲区,如 1MB
- 大数据传输:增大缓冲区,如 8MB
- 根据实际内存调整
2. 线程安全
bash
// 当前实现是单线程安全的,因为:
// 1. lock_on/off 宏被注释掉(无锁)
// 2. Skynet 服务是单线程模型
// 3. 全局状态 LL 在初始化时创建
3. 地址管理
- 地址格式:Skynet 二进制地址格式
- 地址更新:lkcp_send 和 lkcp_input 可更新地址
- 内存管理:需要手动管理地址内存
实现了什么?
1. KCP 到 Lua 的绑定 :为 Skynet 提供了 KCP 协议的 Lua 接口
2. Skynet 集成 :与 Skynet 的 UDP socket 系统无缝集成
3. 二进制协议 :使用自定义的打包格式传输数据
4. 地址管理 :支持动态更新目标地址
5. 资源管理:完善的垃圾回收机制