├── .gitignore ├── LICENSE ├── README.md ├── c_src ├── Makefile ├── ekcp.c ├── ikcp.c └── ikcp.h ├── include └── ekcp.hrl ├── rebar.config ├── rebar3 ├── src ├── ekcp.app.src ├── ekcp.erl ├── ekcp_app.erl ├── ekcp_conn_srv.erl ├── ekcp_conn_sup.erl ├── ekcp_handle.erl ├── ekcp_lib.erl ├── ekcp_listener_srv.erl ├── ekcp_sup.erl └── kcp_handle.erl ├── test └── ekcp_test_server.erl └── windows下编译方法.md /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | erl_crash.dump 7 | ebin/*.beam 8 | rel/example_project 9 | .concrete/DEV_MODE 10 | .rebar 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | kcp的erlang实现版本,使用的erlang的nif完成,windows下查看windows编译方法.md。 3 | kcp的源地址 https://github.com/skywind3000/kcp 4 | 关于kcp的基础部分不再做介绍,查看原地址的文档即可。 5 | 6 | # 上层包装介绍 7 | 这里基于udp简单实现了一份链接维护逻辑,并额外加入了一些应用层的打包,如心跳等。 8 | 但是总体都是由ekcp_handle模块来统一实现。 9 | ekcp_handle是一个kcp_handle的模板实现,你可以仿照ekcp_handle自定义自己的处理模块。 10 | 11 | # 基础使用 12 | 1. 服务器启动udp端口监听 13 | ```erlang 14 | ekcp:start_listener( 15 | [#{port=>9000,handle_module=>ekcp_handle, ref=>9000, sndwnd=>128, rcvwnd=>128, 16 | nodelay => 0, interval=>10, resend =>0, nc =>0}]). 17 | ``` 18 | - port udp服务要监听的端口 19 | - handle_module 一个基于kcp_handle模板实现的处理模块 20 | - ref 用来区分不同的监听端口,类型没有要求,不一定非得是数字 21 | 其他参数对应kcp的 ikcp_nodelay 和 ikcp_wndsize 22 | 23 | 2. 客户端发起连接请求 24 | ```erlang 25 | {ok, Socket} = gen_udp:open(Port, [binary, {active, true}]), 26 | {ok, Kcp} = ekcp:create(Port, self()), 27 | ekcp:wndsize(Kcp, 128, 128), 28 | ekcp:nodelay(Kcp, 0, 10, 0, 0), 29 | gen_udp:send(Socket, "127.0.0.1", Tar, <<0:8, Port:32>>), 30 | ``` 31 | - 本地打开一个UDP端口 32 | - 创建kcp,这里要注意conv也就是ekcp:create的第一个参数必须前后端相同,否则接收到消息也不会处理 33 | - 设置工作模式 34 | - 单纯用udp层发起一个建立连接的请求 35 | 36 | 上面的行为会导致服务端拉起一个ekcp_conn_srv来专门处理这个“连接”,以后从这个本地端口和IP发往服务器的消息 37 | 都会由对应的ekcp_conn_srv来处理。 38 | ekcp_conn_srv通过服务端的监听端口,和第一条udp消息中的Port字段,也就是后32位来区分,你可以把Port替换成用户标识 39 | 40 | 3. 交互 41 | 连接建立后,就可以通过kcp来实现稳定可靠的消息交互了 42 | - 客户端 43 | ```erlang 44 | ekcp:send(Kcp, Msg) 45 | ... 46 | do_handle_info({kcp_msg, Msg}, #state{socket = Socket, port = Port, tar = Tar} = State) -> 47 | io:format("kcp want send msg ~p, Tar:~p~n", [Msg, Tar]), 48 | gen_udp:send(Socket, "127.0.0.1", Tar, <<1:8, Port:32, Msg/binary>>), 49 | {noreply, State}; 50 | ``` 51 | 因为UDP是无连接的,所以每条消息必须带上用户标识,使用ekcp.send来完成消息的kcp打包 52 | 打包后的消息会通过{kcp_msg, Msg} 发送给接收进程,接收进行再加上应用层的打包,就可以通过UDP进行发送 53 | - 服务端 54 | 服务端的UDP消息都是由监听进程来统一处理,监听进程会调用handle_module指定的处理模块提供的解包方法来做应用层的解包 55 | 解包后通过当前Ref和用户标识将解包后的消息转发给对应的ekcp_conn_srv来处理 56 | ```erlang 57 | ekcp:input(Kcp, Binary), 58 | flush(Kcp, From, HandleModule) 59 | ... 60 | flush(Kcp, From, HandleModule) -> 61 | Msg = ekcp:recv_data(Kcp), 62 | case Msg of 63 | nil -> 64 | pass; 65 | _ -> 66 | HandleModule:route_to_server(From, Msg), 67 | flush(Kcp, From, HandleModule) 68 | end. 69 | 70 | ``` 71 | ekcp_conn_srv在收到消息后调用ekpc:input将消息入栈,之后不断调用ekcp:recv_data来将消息解包取出 72 | 取出后调用HandleModule:route_to_server(From, Msg)将消息体路由给要处理的进程 73 | 74 | 4. 测试 75 | `./rebar3 shell` 启动命令行 76 | ```erlang 77 | ekcp:start_listener([#{port=>9000,handle_module=>ekcp_handle, ref=>9000, sndwnd=>128, rcvwnd=>128}]). 78 | ``` 79 | 启动服务器 80 | 81 | ```erlang 82 | ekcp_test_server:start_link([9003,9000]). 83 | srv_9003!init. 84 | ``` 85 | 初始化客户端 86 | 之后调用 `srv_9003!{send_msg, <<"abc">>}.`来完成消息的发送 87 | 88 | 服务端想主动发送消息可以调用 89 | `ekcp_lib:worker(9000,9003)!{send_msg, <<"abc">>}` 90 | 91 | # API 92 | ekcp模块提供了KCP的基础API,大多数和KCP本身没什么区别,这里只介绍必须有所改动的API 93 | 94 | - ekcp:create 95 | ```erlang 96 | -spec create(ID::integer(), Pid::pid()) -> {ok, kcp_res()}|create_err. 97 | create(_ID, _Pid) -> 98 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 99 | ``` 100 | create函数要求传对话描述和一个PID,对话描述就是ikcp的对话描述,这里传入的Pid是为了kcp->output的回调用, 101 | kcp消息都是通过回调返回的,这里的实现方式是在回调触发的时候,通过发送消息,发送给注册时指定的Pid来完成 102 | 如果你想让其他进程来处理kcp消息,可以替换上面例子中的self调用。 103 | - ekcp:recv_data 104 | 这里不像ikcp必须要求提供长度,但是实际上长度也是有上限的,也就是单条消息的长度是有限制的,定义在ekcp.c中的 105 | RECV_BUF_LEN,现在的值是1Kb,这里不要随意的扩展这个定义,而应该专注于让应用层能确保每个包都很小,单个包体大了后在kcp分包,udp分包 106 | flsuh的多余recv_data操作都会造成性能的下降 107 | recv_data返回的数据必然是可靠保序的,如果3包先到了2包还没到,那么recv_data不会返回3包的消息,必须等2包到了才会返回。 108 | 109 | 因此有多种情况都会造成udp层是通的,但是消息却无法传输接收,比如客户端的kcp逻辑已经重启过了,但是服务器接收的kcp逻辑还是老的, 110 | 或者客户端的kcp逻辑是老的,服务器的是新的,都会造成这种状况。kcp逻辑的重新注册是很廉价的,客户端什么时候该进行“重启”是很重要的话题 111 | 112 | 113 | - ekcp:update 114 | update的调用时机很有说法,但是建议就是简单的循环调用即可,erlang多进程的支持可以让update很廉价,不要尝试发送消息后立刻调用update, 115 | 这并不一定能优化性能,如果一个时间片内有多次小包的发送,这样频繁的调用update反而会造成性能下降,当然也不是不允许。最好在理解update和check之间要如何配合后 116 | 在根据自己项目的需求,进行自定义 。 117 | 118 | 119 | - ekcp:send 120 | ekcp:send 在erlang层面并没有做长度参数的设定,和c层面的不同,是直接通过传入的binary的长度决定的,但是还是要注意ikcp_send的长度使用的是int,也就是说 121 | 虽然binary长度没有做限制,但是这里传入的binary长度是不能超过int的最大值的,但是说回来,单条消息也不推荐太长。 122 | 123 | - ekcp:input 124 | 和send类似,erlang层面都没有做长度限制,ikcp_input使用的是long来表示长度的,也就是input的binary参数长度不能超过long的限制 125 | 126 | # 使用 127 | 仿照ekcp_handle实现一份你自己的处理模块,注意最好套用kcp_handle行为检查,定义完成后调用start_listener就可以启动服务器监听 128 | 也就是加上-behavior(kcp_handle). 129 | 130 | # 重要 131 | nif出错会直接导致虚拟机crash,这些内容都没有线上项目验证,如果要使用,请自己进行完整的测试! -------------------------------------------------------------------------------- /c_src/Makefile: -------------------------------------------------------------------------------- 1 | # Based on c_src.mk from erlang.mk by Loic Hoguin 2 | 3 | CURDIR := $(shell pwd) 4 | BASEDIR := $(abspath $(CURDIR)/..) 5 | 6 | PROJECT ?= $(notdir $(BASEDIR)) 7 | PROJECT := $(strip $(PROJECT)) 8 | 9 | ERTS_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts/erts-~ts/include/\", [code:root_dir(), erlang:system_info(version)]).") 10 | ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, include)]).") 11 | ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -s init stop -eval "io:format(\"~ts\", [code:lib_dir(erl_interface, lib)]).") 12 | 13 | C_SRC_DIR = $(CURDIR) 14 | C_SRC_OUTPUT ?= $(CURDIR)/../priv/$(PROJECT).so 15 | 16 | # System type and C compiler/flags. 17 | 18 | UNAME_SYS := $(shell uname -s) 19 | ifeq ($(UNAME_SYS), Darwin) 20 | CC ?= cc 21 | CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes 22 | CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall 23 | LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress 24 | else ifeq ($(UNAME_SYS), FreeBSD) 25 | CC ?= cc 26 | CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes 27 | CXXFLAGS ?= -O3 -finline-functions -Wall 28 | else ifeq ($(UNAME_SYS), Linux) 29 | CC ?= gcc 30 | CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes 31 | CXXFLAGS ?= -O3 -finline-functions -Wall 32 | endif 33 | 34 | CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) 35 | CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR) 36 | 37 | LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei 38 | LDFLAGS += -shared 39 | 40 | # Verbosity. 41 | 42 | c_verbose_0 = @echo " C " $(?F); 43 | c_verbose = $(c_verbose_$(V)) 44 | 45 | cpp_verbose_0 = @echo " CPP " $(?F); 46 | cpp_verbose = $(cpp_verbose_$(V)) 47 | 48 | link_verbose_0 = @echo " LD " $(@F); 49 | link_verbose = $(link_verbose_$(V)) 50 | 51 | SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \)) 52 | OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) 53 | 54 | COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c 55 | COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c 56 | 57 | $(C_SRC_OUTPUT): $(OBJECTS) 58 | @mkdir -p $(BASEDIR)/priv/ 59 | $(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT) 60 | 61 | %.o: %.c 62 | $(COMPILE_C) $(OUTPUT_OPTION) $< 63 | 64 | %.o: %.cc 65 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 66 | 67 | %.o: %.C 68 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 69 | 70 | %.o: %.cpp 71 | $(COMPILE_CPP) $(OUTPUT_OPTION) $< 72 | 73 | clean: 74 | @rm -f $(C_SRC_OUTPUT) $(OBJECTS) 75 | -------------------------------------------------------------------------------- /c_src/ekcp.c: -------------------------------------------------------------------------------- 1 | #include "erl_nif.h" 2 | #include "ikcp.h" 3 | #include 4 | 5 | #define RECV_BUF_LEN (1024 * 1024) /* 单条消息接收缓冲区大小 */ 6 | 7 | struct Callback { 8 | ErlNifPid current_pid; 9 | }; 10 | 11 | typedef struct _res_t { 12 | ikcpcb *kcp; 13 | } res_t; 14 | 15 | static ErlNifResourceType *KCP_RESOURCE; 16 | 17 | #define GET_RESOURCE(env, argc, argv) res_t *res; \ 18 | if(!argc || !enif_get_resource(env, argv[0], KCP_RESOURCE, (void**)&res)) \ 19 | return enif_make_atom(env, "arg_err"); \ 20 | if(!res->kcp) return enif_make_atom(env, "res_null"); 21 | 22 | #define LOG() printf("file=%s,func=%s,line=%d\n",__FILE__,__FUNCTION__,__LINE__); 23 | 24 | int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg); 25 | 26 | //发送动作由C发送消息给erlang PID PID调用gen_udp来完成或者定义你自己的发送函数 27 | int kcp_output_callback(const char *buf, int len, ikcpcb *kcp, void *arg) { 28 | struct Callback* c = (struct Callback*)arg; 29 | ErlNifBinary nif_binary; 30 | enif_alloc_binary(len, &nif_binary); 31 | memcpy(nif_binary.data, (unsigned char *)buf, len); 32 | ErlNifEnv* msg_env = enif_alloc_env(); 33 | enif_send(NULL, &(c->current_pid), msg_env, enif_make_tuple(msg_env, 2, 34 | enif_make_atom(msg_env, "kcp_msg"), 35 | enif_make_binary(msg_env, &nif_binary))); 36 | enif_free_env(msg_env); 37 | return 0; 38 | } 39 | 40 | static 41 | ERL_NIF_TERM ekcp_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 42 | unsigned int conv = 0; 43 | if(!enif_get_uint(env, argv[0], &conv)){ 44 | return enif_make_badarg(env); 45 | } 46 | 47 | struct Callback* c = enif_alloc(sizeof(struct Callback)); 48 | if(!enif_get_local_pid(env, argv[1], &(c -> current_pid))) { 49 | return enif_make_badarg(env); 50 | } 51 | 52 | ikcpcb* kcp = ikcp_create(conv, (void*)c); 53 | if (kcp == NULL) { 54 | return enif_make_atom(env, "kcp_create_fail"); 55 | } 56 | kcp->output = kcp_output_callback; 57 | res_t *res = (res_t*)enif_alloc_resource(KCP_RESOURCE, sizeof(res_t)); 58 | res->kcp = kcp; 59 | return enif_make_tuple(env, 2, enif_make_atom(env, "ok"), enif_make_resource(env, res)); 60 | } 61 | 62 | 63 | static 64 | ERL_NIF_TERM ekcp_recv(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 65 | GET_RESOURCE(env, argc, argv); 66 | char *buf = (char *)enif_priv_data(env); 67 | memset(buf, 0, RECV_BUF_LEN); 68 | int32_t hr = ikcp_recv(res->kcp, buf, RECV_BUF_LEN); 69 | if (hr <= 0) { 70 | return enif_make_atom(env, "nil"); 71 | } 72 | ErlNifBinary nif_binary; 73 | enif_alloc_binary(hr, &nif_binary); 74 | memcpy(nif_binary.data, (unsigned char *)buf, hr); 75 | return enif_make_binary(env, &nif_binary); 76 | } 77 | 78 | static 79 | ERL_NIF_TERM ekcp_send(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 80 | GET_RESOURCE(env, argc, argv); 81 | ErlNifBinary buf; 82 | if (!enif_inspect_binary(env, argv[1], &buf)) { 83 | return enif_make_badarg(env); 84 | } 85 | int32_t hr = ikcp_send(res->kcp, (const char *)buf.data, buf.size); 86 | return enif_make_int(env, hr); 87 | } 88 | 89 | 90 | static 91 | ERL_NIF_TERM ekcp_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 92 | GET_RESOURCE(env, argc, argv); 93 | IUINT32 current; 94 | if(!enif_get_uint(env, argv[1], ¤t)){ 95 | return enif_make_badarg(env); 96 | } 97 | ikcp_update(res->kcp, current); 98 | return enif_make_atom(env, "ok"); 99 | } 100 | 101 | static 102 | ERL_NIF_TERM ekcp_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 103 | GET_RESOURCE(env, argc, argv); 104 | IUINT32 current; 105 | if(!enif_get_uint(env, argv[1], ¤t)){ 106 | return enif_make_badarg(env); 107 | } 108 | IUINT32 hr = ikcp_check(res->kcp, current); 109 | return enif_make_int(env, hr); 110 | } 111 | 112 | static 113 | ERL_NIF_TERM ekcp_input(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 114 | GET_RESOURCE(env, argc, argv); 115 | ErlNifBinary buf; 116 | 117 | if (!enif_inspect_binary(env, argv[1], &buf)) { 118 | return enif_make_badarg(env); 119 | } 120 | int32_t hr = ikcp_input(res->kcp, (const char *)buf.data, buf.size); 121 | return enif_make_int(env, hr); 122 | } 123 | 124 | static 125 | ERL_NIF_TERM ekcp_flush(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 126 | GET_RESOURCE(env, argc, argv); 127 | ikcp_flush(res->kcp); 128 | return enif_make_atom(env, "ok"); 129 | } 130 | 131 | static 132 | ERL_NIF_TERM ekcp_wndsize(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 133 | GET_RESOURCE(env, argc, argv); 134 | ikcp_flush(res->kcp); 135 | int32_t sndwnd; 136 | int32_t rcvwnd; 137 | if(!enif_get_int(env, argv[1], &sndwnd)){ 138 | return enif_make_badarg(env); 139 | } 140 | if(!enif_get_int(env, argv[2], &rcvwnd)){ 141 | return enif_make_badarg(env); 142 | } 143 | ikcp_wndsize(res->kcp, sndwnd, rcvwnd); 144 | return enif_make_atom(env, "ok"); 145 | } 146 | 147 | static 148 | ERL_NIF_TERM ekcp_nodelay(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 149 | GET_RESOURCE(env, argc, argv); 150 | ikcp_flush(res->kcp); 151 | int32_t nodelay,interval,resend,nc; 152 | if(!enif_get_int(env, argv[1], &nodelay)){ 153 | return enif_make_badarg(env); 154 | } 155 | if(!enif_get_int(env, argv[2], &interval)){ 156 | return enif_make_badarg(env); 157 | } 158 | if(!enif_get_int(env, argv[3], &resend)){ 159 | return enif_make_badarg(env); 160 | } 161 | if(!enif_get_int(env, argv[4], &nc)){ 162 | return enif_make_badarg(env); 163 | } 164 | int32_t hr = ikcp_nodelay(res->kcp, nodelay, interval, resend, nc); 165 | return enif_make_int(env, hr); 166 | } 167 | 168 | static 169 | ERL_NIF_TERM ekcp_release(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 170 | GET_RESOURCE(env, argc, argv); 171 | ikcp_release(res->kcp); 172 | return enif_make_atom(env, "ok"); 173 | } 174 | 175 | static 176 | ERL_NIF_TERM ekcp_set_mtu(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 177 | GET_RESOURCE(env, argc, argv); 178 | int mtu; 179 | if(!enif_get_int(env, argv[1], &mtu)){ 180 | return enif_make_badarg(env); 181 | } 182 | ikcp_setmtu(res->kcp, mtu); 183 | return enif_make_atom(env, "ok"); 184 | } 185 | 186 | static void nifs_kcp_resource_cleanup(ErlNifEnv* env, void* arg){ 187 | res_t *res = (res_t*)arg; 188 | if ( NULL != res->kcp) { 189 | ikcp_release(res->kcp); 190 | res->kcp = NULL;} 191 | } 192 | 193 | /* 194 | * 所有NIF函数列表定义 195 | */ 196 | static ErlNifFunc nif_funcs[] = { 197 | {"create", 2, ekcp_create} 198 | , {"recv_data", 1, ekcp_recv} 199 | , {"send", 2, ekcp_send} 200 | , {"update", 2, ekcp_update} 201 | , {"check", 2, ekcp_check} 202 | , {"input", 2, ekcp_input} 203 | , {"flush", 1, ekcp_flush} 204 | , {"wndsize", 3, ekcp_wndsize} 205 | , {"nodelay", 5, ekcp_nodelay} 206 | , {"release", 1, ekcp_release} 207 | , {"set_mtu", 2, ekcp_set_mtu} 208 | }; 209 | 210 | static int nif_load(ErlNifEnv * env, void ** priv_data, ERL_NIF_TERM load_info) { 211 | //ikcp_malloc_hook = enif_alloc; 212 | //ikcp_free_hook = enif_free; 213 | KCP_RESOURCE = enif_open_resource_type(env,"ekcp", "kcp_resource", &nifs_kcp_resource_cleanup, ERL_NIF_RT_CREATE, NULL); 214 | ikcp_allocator(enif_alloc, enif_free); 215 | *priv_data = enif_alloc(RECV_BUF_LEN); 216 | //这里不调用了,后面每次使用前都会set 217 | //memset(buf, 0, RECV_BUF_LEN); 218 | return 0; 219 | } 220 | 221 | static void nif_unload(ErlNifEnv * env, void * priv_data) { 222 | if(NULL != priv_data){ 223 | enif_free(priv_data); 224 | priv_data = NULL; 225 | } 226 | } 227 | 228 | /* 229 | * 执行初始化NIF库的宏 230 | */ 231 | ERL_NIF_INIT(ekcp, nif_funcs, nif_load, NULL, NULL, nif_unload) -------------------------------------------------------------------------------- /c_src/ikcp.c: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // KCP - A Better ARQ Protocol Implementation 4 | // skywind3000 (at) gmail.com, 2010-2011 5 | // 6 | // Features: 7 | // + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. 8 | // + Maximum RTT reduce three times vs tcp. 9 | // + Lightweight, distributed as a single source file. 10 | // 11 | //===================================================================== 12 | #include "ikcp.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | 22 | //===================================================================== 23 | // KCP BASIC 24 | //===================================================================== 25 | const IUINT32 IKCP_RTO_NDL = 30; // no delay min rto 26 | const IUINT32 IKCP_RTO_MIN = 100; // normal min rto 27 | const IUINT32 IKCP_RTO_DEF = 200; 28 | const IUINT32 IKCP_RTO_MAX = 60000; 29 | const IUINT32 IKCP_CMD_PUSH = 81; // cmd: push data 30 | const IUINT32 IKCP_CMD_ACK = 82; // cmd: ack 31 | const IUINT32 IKCP_CMD_WASK = 83; // cmd: window probe (ask) 32 | const IUINT32 IKCP_CMD_WINS = 84; // cmd: window size (tell) 33 | const IUINT32 IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK 34 | const IUINT32 IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS 35 | const IUINT32 IKCP_WND_SND = 32; 36 | const IUINT32 IKCP_WND_RCV = 128; // must >= max fragment size 37 | const IUINT32 IKCP_MTU_DEF = 1400; 38 | const IUINT32 IKCP_ACK_FAST = 3; 39 | const IUINT32 IKCP_INTERVAL = 100; 40 | const IUINT32 IKCP_OVERHEAD = 24; 41 | const IUINT32 IKCP_DEADLINK = 20; 42 | const IUINT32 IKCP_THRESH_INIT = 2; 43 | const IUINT32 IKCP_THRESH_MIN = 2; 44 | const IUINT32 IKCP_PROBE_INIT = 7000; // 7 secs to probe window size 45 | const IUINT32 IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window 46 | 47 | 48 | //--------------------------------------------------------------------- 49 | // encode / decode 50 | //--------------------------------------------------------------------- 51 | 52 | /* encode 8 bits unsigned int */ 53 | static inline char *ikcp_encode8u(char *p, unsigned char c) 54 | { 55 | *(unsigned char*)p++ = c; 56 | return p; 57 | } 58 | 59 | /* decode 8 bits unsigned int */ 60 | static inline const char *ikcp_decode8u(const char *p, unsigned char *c) 61 | { 62 | *c = *(unsigned char*)p++; 63 | return p; 64 | } 65 | 66 | /* encode 16 bits unsigned int (lsb) */ 67 | static inline char *ikcp_encode16u(char *p, unsigned short w) 68 | { 69 | #if IWORDS_BIG_ENDIAN 70 | *(unsigned char*)(p + 0) = (w & 255); 71 | *(unsigned char*)(p + 1) = (w >> 8); 72 | #else 73 | *(unsigned short*)(p) = w; 74 | #endif 75 | p += 2; 76 | return p; 77 | } 78 | 79 | /* decode 16 bits unsigned int (lsb) */ 80 | static inline const char *ikcp_decode16u(const char *p, unsigned short *w) 81 | { 82 | #if IWORDS_BIG_ENDIAN 83 | *w = *(const unsigned char*)(p + 1); 84 | *w = *(const unsigned char*)(p + 0) + (*w << 8); 85 | #else 86 | *w = *(const unsigned short*)p; 87 | #endif 88 | p += 2; 89 | return p; 90 | } 91 | 92 | /* encode 32 bits unsigned int (lsb) */ 93 | static inline char *ikcp_encode32u(char *p, IUINT32 l) 94 | { 95 | #if IWORDS_BIG_ENDIAN 96 | *(unsigned char*)(p + 0) = (unsigned char)((l >> 0) & 0xff); 97 | *(unsigned char*)(p + 1) = (unsigned char)((l >> 8) & 0xff); 98 | *(unsigned char*)(p + 2) = (unsigned char)((l >> 16) & 0xff); 99 | *(unsigned char*)(p + 3) = (unsigned char)((l >> 24) & 0xff); 100 | #else 101 | *(IUINT32*)p = l; 102 | #endif 103 | p += 4; 104 | return p; 105 | } 106 | 107 | /* decode 32 bits unsigned int (lsb) */ 108 | static inline const char *ikcp_decode32u(const char *p, IUINT32 *l) 109 | { 110 | #if IWORDS_BIG_ENDIAN 111 | *l = *(const unsigned char*)(p + 3); 112 | *l = *(const unsigned char*)(p + 2) + (*l << 8); 113 | *l = *(const unsigned char*)(p + 1) + (*l << 8); 114 | *l = *(const unsigned char*)(p + 0) + (*l << 8); 115 | #else 116 | *l = *(const IUINT32*)p; 117 | #endif 118 | p += 4; 119 | return p; 120 | } 121 | 122 | static inline IUINT32 _imin_(IUINT32 a, IUINT32 b) { 123 | return a <= b ? a : b; 124 | } 125 | 126 | static inline IUINT32 _imax_(IUINT32 a, IUINT32 b) { 127 | return a >= b ? a : b; 128 | } 129 | 130 | static inline IUINT32 _ibound_(IUINT32 lower, IUINT32 middle, IUINT32 upper) 131 | { 132 | return _imin_(_imax_(lower, middle), upper); 133 | } 134 | 135 | static inline long _itimediff(IUINT32 later, IUINT32 earlier) 136 | { 137 | return ((IINT32)(later - earlier)); 138 | } 139 | 140 | //--------------------------------------------------------------------- 141 | // manage segment 142 | //--------------------------------------------------------------------- 143 | typedef struct IKCPSEG IKCPSEG; 144 | 145 | static void* (*ikcp_malloc_hook)(size_t) = NULL; 146 | static void (*ikcp_free_hook)(void *) = NULL; 147 | 148 | // internal malloc 149 | static void* ikcp_malloc(size_t size) { 150 | if (ikcp_malloc_hook) 151 | return ikcp_malloc_hook(size); 152 | return malloc(size); 153 | } 154 | 155 | // internal free 156 | static void ikcp_free(void *ptr) { 157 | if (ikcp_free_hook) { 158 | ikcp_free_hook(ptr); 159 | } else { 160 | free(ptr); 161 | } 162 | } 163 | 164 | // redefine allocator 165 | void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)) 166 | { 167 | ikcp_malloc_hook = new_malloc; 168 | ikcp_free_hook = new_free; 169 | } 170 | 171 | // allocate a new kcp segment 172 | static IKCPSEG* ikcp_segment_new(ikcpcb *kcp, int size) 173 | { 174 | return (IKCPSEG*)ikcp_malloc(sizeof(IKCPSEG) + size); 175 | } 176 | 177 | // delete a segment 178 | static void ikcp_segment_delete(ikcpcb *kcp, IKCPSEG *seg) 179 | { 180 | ikcp_free(seg); 181 | } 182 | 183 | // write log 184 | void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...) 185 | { 186 | char buffer[1024]; 187 | va_list argptr; 188 | if ((mask & kcp->logmask) == 0 || kcp->writelog == 0) return; 189 | va_start(argptr, fmt); 190 | vsprintf(buffer, fmt, argptr); 191 | va_end(argptr); 192 | kcp->writelog(buffer, kcp, kcp->user); 193 | } 194 | 195 | // check log mask 196 | static int ikcp_canlog(const ikcpcb *kcp, int mask) 197 | { 198 | if ((mask & kcp->logmask) == 0 || kcp->writelog == NULL) return 0; 199 | return 1; 200 | } 201 | 202 | // output segment 203 | static int ikcp_output(ikcpcb *kcp, const void *data, int size) 204 | { 205 | assert(kcp); 206 | assert(kcp->output); 207 | if (ikcp_canlog(kcp, IKCP_LOG_OUTPUT)) { 208 | ikcp_log(kcp, IKCP_LOG_OUTPUT, "[RO] %ld bytes", (long)size); 209 | } 210 | if (size == 0) return 0; 211 | return kcp->output((const char*)data, size, kcp, kcp->user); 212 | } 213 | 214 | // output queue 215 | void ikcp_qprint(const char *name, const struct IQUEUEHEAD *head) 216 | { 217 | #if 0 218 | const struct IQUEUEHEAD *p; 219 | printf("<%s>: [", name); 220 | for (p = head->next; p != head; p = p->next) { 221 | const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); 222 | printf("(%lu %d)", (unsigned long)seg->sn, (int)(seg->ts % 10000)); 223 | if (p->next != head) printf(","); 224 | } 225 | printf("]\n"); 226 | #endif 227 | } 228 | 229 | 230 | //--------------------------------------------------------------------- 231 | // create a new kcpcb 232 | //--------------------------------------------------------------------- 233 | ikcpcb* ikcp_create(IUINT32 conv, void *user) 234 | { 235 | ikcpcb *kcp = (ikcpcb*)ikcp_malloc(sizeof(struct IKCPCB)); 236 | if (kcp == NULL) return NULL; 237 | kcp->conv = conv; 238 | kcp->user = user; 239 | kcp->snd_una = 0; 240 | kcp->snd_nxt = 0; 241 | kcp->rcv_nxt = 0; 242 | kcp->ts_recent = 0; 243 | kcp->ts_lastack = 0; 244 | kcp->ts_probe = 0; 245 | kcp->probe_wait = 0; 246 | kcp->snd_wnd = IKCP_WND_SND; 247 | kcp->rcv_wnd = IKCP_WND_RCV; 248 | kcp->rmt_wnd = IKCP_WND_RCV; 249 | kcp->cwnd = 0; 250 | kcp->incr = 0; 251 | kcp->probe = 0; 252 | kcp->mtu = IKCP_MTU_DEF; 253 | kcp->mss = kcp->mtu - IKCP_OVERHEAD; 254 | kcp->stream = 0; 255 | 256 | kcp->buffer = (char*)ikcp_malloc((kcp->mtu + IKCP_OVERHEAD) * 3); 257 | if (kcp->buffer == NULL) { 258 | ikcp_free(kcp); 259 | return NULL; 260 | } 261 | 262 | iqueue_init(&kcp->snd_queue); 263 | iqueue_init(&kcp->rcv_queue); 264 | iqueue_init(&kcp->snd_buf); 265 | iqueue_init(&kcp->rcv_buf); 266 | kcp->nrcv_buf = 0; 267 | kcp->nsnd_buf = 0; 268 | kcp->nrcv_que = 0; 269 | kcp->nsnd_que = 0; 270 | kcp->state = 0; 271 | kcp->acklist = NULL; 272 | kcp->ackblock = 0; 273 | kcp->ackcount = 0; 274 | kcp->rx_srtt = 0; 275 | kcp->rx_rttval = 0; 276 | kcp->rx_rto = IKCP_RTO_DEF; 277 | kcp->rx_minrto = IKCP_RTO_MIN; 278 | kcp->current = 0; 279 | kcp->interval = IKCP_INTERVAL; 280 | kcp->ts_flush = IKCP_INTERVAL; 281 | kcp->nodelay = 0; 282 | kcp->updated = 0; 283 | kcp->logmask = 0; 284 | kcp->ssthresh = IKCP_THRESH_INIT; 285 | kcp->fastresend = 0; 286 | kcp->nocwnd = 0; 287 | kcp->xmit = 0; 288 | kcp->dead_link = IKCP_DEADLINK; 289 | kcp->output = NULL; 290 | kcp->writelog = NULL; 291 | 292 | return kcp; 293 | } 294 | 295 | 296 | //--------------------------------------------------------------------- 297 | // release a new kcpcb 298 | //--------------------------------------------------------------------- 299 | void ikcp_release(ikcpcb *kcp) 300 | { 301 | assert(kcp); 302 | if (kcp) { 303 | IKCPSEG *seg; 304 | while (!iqueue_is_empty(&kcp->snd_buf)) { 305 | seg = iqueue_entry(kcp->snd_buf.next, IKCPSEG, node); 306 | iqueue_del(&seg->node); 307 | ikcp_segment_delete(kcp, seg); 308 | } 309 | while (!iqueue_is_empty(&kcp->rcv_buf)) { 310 | seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); 311 | iqueue_del(&seg->node); 312 | ikcp_segment_delete(kcp, seg); 313 | } 314 | while (!iqueue_is_empty(&kcp->snd_queue)) { 315 | seg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); 316 | iqueue_del(&seg->node); 317 | ikcp_segment_delete(kcp, seg); 318 | } 319 | while (!iqueue_is_empty(&kcp->rcv_queue)) { 320 | seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); 321 | iqueue_del(&seg->node); 322 | ikcp_segment_delete(kcp, seg); 323 | } 324 | if (kcp->buffer) { 325 | ikcp_free(kcp->buffer); 326 | } 327 | if (kcp->acklist) { 328 | ikcp_free(kcp->acklist); 329 | } 330 | 331 | kcp->nrcv_buf = 0; 332 | kcp->nsnd_buf = 0; 333 | kcp->nrcv_que = 0; 334 | kcp->nsnd_que = 0; 335 | kcp->ackcount = 0; 336 | kcp->buffer = NULL; 337 | kcp->acklist = NULL; 338 | ikcp_free(kcp); 339 | } 340 | } 341 | 342 | 343 | //--------------------------------------------------------------------- 344 | // set output callback, which will be invoked by kcp 345 | //--------------------------------------------------------------------- 346 | void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, 347 | ikcpcb *kcp, void *user)) 348 | { 349 | kcp->output = output; 350 | } 351 | 352 | 353 | //--------------------------------------------------------------------- 354 | // user/upper level recv: returns size, returns below zero for EAGAIN 355 | //--------------------------------------------------------------------- 356 | int ikcp_recv(ikcpcb *kcp, char *buffer, int len) 357 | { 358 | struct IQUEUEHEAD *p; 359 | int ispeek = (len < 0)? 1 : 0; 360 | int peeksize; 361 | int recover = 0; 362 | IKCPSEG *seg; 363 | assert(kcp); 364 | 365 | if (iqueue_is_empty(&kcp->rcv_queue)) 366 | return -1; 367 | 368 | if (len < 0) len = -len; 369 | 370 | peeksize = ikcp_peeksize(kcp); 371 | 372 | if (peeksize < 0) 373 | return -2; 374 | 375 | if (peeksize > len) 376 | return -3; 377 | 378 | if (kcp->nrcv_que >= kcp->rcv_wnd) 379 | recover = 1; 380 | 381 | // merge fragment 382 | for (len = 0, p = kcp->rcv_queue.next; p != &kcp->rcv_queue; ) { 383 | int fragment; 384 | seg = iqueue_entry(p, IKCPSEG, node); 385 | p = p->next; 386 | 387 | if (buffer) { 388 | memcpy(buffer, seg->data, seg->len); 389 | buffer += seg->len; 390 | } 391 | 392 | len += seg->len; 393 | fragment = seg->frg; 394 | 395 | if (ikcp_canlog(kcp, IKCP_LOG_RECV)) { 396 | ikcp_log(kcp, IKCP_LOG_RECV, "recv sn=%lu", seg->sn); 397 | } 398 | 399 | if (ispeek == 0) { 400 | iqueue_del(&seg->node); 401 | ikcp_segment_delete(kcp, seg); 402 | kcp->nrcv_que--; 403 | } 404 | 405 | if (fragment == 0) 406 | break; 407 | } 408 | 409 | assert(len == peeksize); 410 | 411 | // move available data from rcv_buf -> rcv_queue 412 | while (! iqueue_is_empty(&kcp->rcv_buf)) { 413 | IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); 414 | if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { 415 | iqueue_del(&seg->node); 416 | kcp->nrcv_buf--; 417 | iqueue_add_tail(&seg->node, &kcp->rcv_queue); 418 | kcp->nrcv_que++; 419 | kcp->rcv_nxt++; 420 | } else { 421 | break; 422 | } 423 | } 424 | 425 | // fast recover 426 | if (kcp->nrcv_que < kcp->rcv_wnd && recover) { 427 | // ready to send back IKCP_CMD_WINS in ikcp_flush 428 | // tell remote my window size 429 | kcp->probe |= IKCP_ASK_TELL; 430 | } 431 | 432 | return len; 433 | } 434 | 435 | 436 | //--------------------------------------------------------------------- 437 | // peek data size 438 | //--------------------------------------------------------------------- 439 | int ikcp_peeksize(const ikcpcb *kcp) 440 | { 441 | struct IQUEUEHEAD *p; 442 | IKCPSEG *seg; 443 | int length = 0; 444 | 445 | assert(kcp); 446 | 447 | if (iqueue_is_empty(&kcp->rcv_queue)) return -1; 448 | 449 | seg = iqueue_entry(kcp->rcv_queue.next, IKCPSEG, node); 450 | if (seg->frg == 0) return seg->len; 451 | 452 | if (kcp->nrcv_que < seg->frg + 1) return -1; 453 | 454 | for (p = kcp->rcv_queue.next; p != &kcp->rcv_queue; p = p->next) { 455 | seg = iqueue_entry(p, IKCPSEG, node); 456 | length += seg->len; 457 | if (seg->frg == 0) break; 458 | } 459 | 460 | return length; 461 | } 462 | 463 | 464 | //--------------------------------------------------------------------- 465 | // user/upper level send, returns below zero for error 466 | //--------------------------------------------------------------------- 467 | int ikcp_send(ikcpcb *kcp, const char *buffer, int len) 468 | { 469 | IKCPSEG *seg; 470 | int count, i; 471 | 472 | assert(kcp->mss > 0); 473 | if (len < 0) return -1; 474 | 475 | // append to previous segment in streaming mode (if possible) 476 | if (kcp->stream != 0) { 477 | if (!iqueue_is_empty(&kcp->snd_queue)) { 478 | IKCPSEG *old = iqueue_entry(kcp->snd_queue.prev, IKCPSEG, node); 479 | if (old->len < kcp->mss) { 480 | int capacity = kcp->mss - old->len; 481 | int extend = (len < capacity)? len : capacity; 482 | seg = ikcp_segment_new(kcp, old->len + extend); 483 | assert(seg); 484 | if (seg == NULL) { 485 | return -2; 486 | } 487 | iqueue_add_tail(&seg->node, &kcp->snd_queue); 488 | memcpy(seg->data, old->data, old->len); 489 | if (buffer) { 490 | memcpy(seg->data + old->len, buffer, extend); 491 | buffer += extend; 492 | } 493 | seg->len = old->len + extend; 494 | seg->frg = 0; 495 | len -= extend; 496 | iqueue_del_init(&old->node); 497 | ikcp_segment_delete(kcp, old); 498 | } 499 | } 500 | if (len <= 0) { 501 | return 0; 502 | } 503 | } 504 | 505 | if (len <= (int)kcp->mss) count = 1; 506 | else count = (len + kcp->mss - 1) / kcp->mss; 507 | 508 | if (count >= IKCP_WND_RCV) return -2; 509 | 510 | if (count == 0) count = 1; 511 | 512 | // fragment 513 | for (i = 0; i < count; i++) { 514 | int size = len > (int)kcp->mss ? (int)kcp->mss : len; 515 | seg = ikcp_segment_new(kcp, size); 516 | assert(seg); 517 | if (seg == NULL) { 518 | return -2; 519 | } 520 | if (buffer && len > 0) { 521 | memcpy(seg->data, buffer, size); 522 | } 523 | seg->len = size; 524 | seg->frg = (kcp->stream == 0)? (count - i - 1) : 0; 525 | iqueue_init(&seg->node); 526 | iqueue_add_tail(&seg->node, &kcp->snd_queue); 527 | kcp->nsnd_que++; 528 | if (buffer) { 529 | buffer += size; 530 | } 531 | len -= size; 532 | } 533 | 534 | return 0; 535 | } 536 | 537 | 538 | //--------------------------------------------------------------------- 539 | // parse ack 540 | //--------------------------------------------------------------------- 541 | static void ikcp_update_ack(ikcpcb *kcp, IINT32 rtt) 542 | { 543 | IINT32 rto = 0; 544 | if (kcp->rx_srtt == 0) { 545 | kcp->rx_srtt = rtt; 546 | kcp->rx_rttval = rtt / 2; 547 | } else { 548 | long delta = rtt - kcp->rx_srtt; 549 | if (delta < 0) delta = -delta; 550 | kcp->rx_rttval = (3 * kcp->rx_rttval + delta) / 4; 551 | kcp->rx_srtt = (7 * kcp->rx_srtt + rtt) / 8; 552 | if (kcp->rx_srtt < 1) kcp->rx_srtt = 1; 553 | } 554 | rto = kcp->rx_srtt + _imax_(kcp->interval, 4 * kcp->rx_rttval); 555 | kcp->rx_rto = _ibound_(kcp->rx_minrto, rto, IKCP_RTO_MAX); 556 | } 557 | 558 | static void ikcp_shrink_buf(ikcpcb *kcp) 559 | { 560 | struct IQUEUEHEAD *p = kcp->snd_buf.next; 561 | if (p != &kcp->snd_buf) { 562 | IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); 563 | kcp->snd_una = seg->sn; 564 | } else { 565 | kcp->snd_una = kcp->snd_nxt; 566 | } 567 | } 568 | 569 | static void ikcp_parse_ack(ikcpcb *kcp, IUINT32 sn) 570 | { 571 | struct IQUEUEHEAD *p, *next; 572 | 573 | if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) 574 | return; 575 | 576 | for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { 577 | IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); 578 | next = p->next; 579 | if (sn == seg->sn) { 580 | iqueue_del(p); 581 | ikcp_segment_delete(kcp, seg); 582 | kcp->nsnd_buf--; 583 | break; 584 | } 585 | if (_itimediff(sn, seg->sn) < 0) { 586 | break; 587 | } 588 | } 589 | } 590 | 591 | static void ikcp_parse_una(ikcpcb *kcp, IUINT32 una) 592 | { 593 | struct IQUEUEHEAD *p, *next; 594 | for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { 595 | IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); 596 | next = p->next; 597 | if (_itimediff(una, seg->sn) > 0) { 598 | iqueue_del(p); 599 | ikcp_segment_delete(kcp, seg); 600 | kcp->nsnd_buf--; 601 | } else { 602 | break; 603 | } 604 | } 605 | } 606 | 607 | static void ikcp_parse_fastack(ikcpcb *kcp, IUINT32 sn) 608 | { 609 | struct IQUEUEHEAD *p, *next; 610 | 611 | if (_itimediff(sn, kcp->snd_una) < 0 || _itimediff(sn, kcp->snd_nxt) >= 0) 612 | return; 613 | 614 | for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = next) { 615 | IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); 616 | next = p->next; 617 | if (_itimediff(sn, seg->sn) < 0) { 618 | break; 619 | } 620 | else if (sn != seg->sn) { 621 | seg->fastack++; 622 | } 623 | } 624 | } 625 | 626 | 627 | //--------------------------------------------------------------------- 628 | // ack append 629 | //--------------------------------------------------------------------- 630 | static void ikcp_ack_push(ikcpcb *kcp, IUINT32 sn, IUINT32 ts) 631 | { 632 | size_t newsize = kcp->ackcount + 1; 633 | IUINT32 *ptr; 634 | 635 | if (newsize > kcp->ackblock) { 636 | IUINT32 *acklist; 637 | size_t newblock; 638 | 639 | for (newblock = 8; newblock < newsize; newblock <<= 1); 640 | acklist = (IUINT32*)ikcp_malloc(newblock * sizeof(IUINT32) * 2); 641 | 642 | if (acklist == NULL) { 643 | assert(acklist != NULL); 644 | abort(); 645 | } 646 | 647 | if (kcp->acklist != NULL) { 648 | size_t x; 649 | for (x = 0; x < kcp->ackcount; x++) { 650 | acklist[x * 2 + 0] = kcp->acklist[x * 2 + 0]; 651 | acklist[x * 2 + 1] = kcp->acklist[x * 2 + 1]; 652 | } 653 | ikcp_free(kcp->acklist); 654 | } 655 | 656 | kcp->acklist = acklist; 657 | kcp->ackblock = newblock; 658 | } 659 | 660 | ptr = &kcp->acklist[kcp->ackcount * 2]; 661 | ptr[0] = sn; 662 | ptr[1] = ts; 663 | kcp->ackcount++; 664 | } 665 | 666 | static void ikcp_ack_get(const ikcpcb *kcp, int p, IUINT32 *sn, IUINT32 *ts) 667 | { 668 | if (sn) sn[0] = kcp->acklist[p * 2 + 0]; 669 | if (ts) ts[0] = kcp->acklist[p * 2 + 1]; 670 | } 671 | 672 | 673 | //--------------------------------------------------------------------- 674 | // parse data 675 | //--------------------------------------------------------------------- 676 | void ikcp_parse_data(ikcpcb *kcp, IKCPSEG *newseg) 677 | { 678 | struct IQUEUEHEAD *p, *prev; 679 | IUINT32 sn = newseg->sn; 680 | int repeat = 0; 681 | 682 | if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) >= 0 || 683 | _itimediff(sn, kcp->rcv_nxt) < 0) { 684 | ikcp_segment_delete(kcp, newseg); 685 | return; 686 | } 687 | 688 | for (p = kcp->rcv_buf.prev; p != &kcp->rcv_buf; p = prev) { 689 | IKCPSEG *seg = iqueue_entry(p, IKCPSEG, node); 690 | prev = p->prev; 691 | if (seg->sn == sn) { 692 | repeat = 1; 693 | break; 694 | } 695 | if (_itimediff(sn, seg->sn) > 0) { 696 | break; 697 | } 698 | } 699 | 700 | if (repeat == 0) { 701 | iqueue_init(&newseg->node); 702 | iqueue_add(&newseg->node, p); 703 | kcp->nrcv_buf++; 704 | } else { 705 | ikcp_segment_delete(kcp, newseg); 706 | } 707 | 708 | #if 0 709 | ikcp_qprint("rcvbuf", &kcp->rcv_buf); 710 | printf("rcv_nxt=%lu\n", kcp->rcv_nxt); 711 | #endif 712 | 713 | // move available data from rcv_buf -> rcv_queue 714 | while (! iqueue_is_empty(&kcp->rcv_buf)) { 715 | IKCPSEG *seg = iqueue_entry(kcp->rcv_buf.next, IKCPSEG, node); 716 | if (seg->sn == kcp->rcv_nxt && kcp->nrcv_que < kcp->rcv_wnd) { 717 | iqueue_del(&seg->node); 718 | kcp->nrcv_buf--; 719 | iqueue_add_tail(&seg->node, &kcp->rcv_queue); 720 | kcp->nrcv_que++; 721 | kcp->rcv_nxt++; 722 | } else { 723 | break; 724 | } 725 | } 726 | 727 | #if 0 728 | ikcp_qprint("queue", &kcp->rcv_queue); 729 | printf("rcv_nxt=%lu\n", kcp->rcv_nxt); 730 | #endif 731 | 732 | #if 1 733 | // printf("snd(buf=%d, queue=%d)\n", kcp->nsnd_buf, kcp->nsnd_que); 734 | // printf("rcv(buf=%d, queue=%d)\n", kcp->nrcv_buf, kcp->nrcv_que); 735 | #endif 736 | } 737 | 738 | 739 | //--------------------------------------------------------------------- 740 | // input data 741 | //--------------------------------------------------------------------- 742 | int ikcp_input(ikcpcb *kcp, const char *data, long size) 743 | { 744 | IUINT32 una = kcp->snd_una; 745 | IUINT32 maxack = 0; 746 | int flag = 0; 747 | 748 | if (ikcp_canlog(kcp, IKCP_LOG_INPUT)) { 749 | ikcp_log(kcp, IKCP_LOG_INPUT, "[RI] %d bytes", size); 750 | } 751 | 752 | if (data == NULL || (int)size < (int)IKCP_OVERHEAD) return -1; 753 | 754 | while (1) { 755 | IUINT32 ts, sn, len, una, conv; 756 | IUINT16 wnd; 757 | IUINT8 cmd, frg; 758 | IKCPSEG *seg; 759 | 760 | if (size < (int)IKCP_OVERHEAD) break; 761 | 762 | data = ikcp_decode32u(data, &conv); 763 | if (conv != kcp->conv) return -1; 764 | 765 | data = ikcp_decode8u(data, &cmd); 766 | data = ikcp_decode8u(data, &frg); 767 | data = ikcp_decode16u(data, &wnd); 768 | data = ikcp_decode32u(data, &ts); 769 | data = ikcp_decode32u(data, &sn); 770 | data = ikcp_decode32u(data, &una); 771 | data = ikcp_decode32u(data, &len); 772 | 773 | size -= IKCP_OVERHEAD; 774 | 775 | if ((long)size < (long)len || (int)len < 0) return -2; 776 | 777 | if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && 778 | cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) 779 | return -3; 780 | 781 | kcp->rmt_wnd = wnd; 782 | ikcp_parse_una(kcp, una); 783 | ikcp_shrink_buf(kcp); 784 | 785 | if (cmd == IKCP_CMD_ACK) { 786 | if (_itimediff(kcp->current, ts) >= 0) { 787 | ikcp_update_ack(kcp, _itimediff(kcp->current, ts)); 788 | } 789 | ikcp_parse_ack(kcp, sn); 790 | ikcp_shrink_buf(kcp); 791 | if (flag == 0) { 792 | flag = 1; 793 | maxack = sn; 794 | } else { 795 | if (_itimediff(sn, maxack) > 0) { 796 | maxack = sn; 797 | } 798 | } 799 | if (ikcp_canlog(kcp, IKCP_LOG_IN_ACK)) { 800 | ikcp_log(kcp, IKCP_LOG_IN_DATA, 801 | "input ack: sn=%lu rtt=%ld rto=%ld", sn, 802 | (long)_itimediff(kcp->current, ts), 803 | (long)kcp->rx_rto); 804 | } 805 | } 806 | else if (cmd == IKCP_CMD_PUSH) { 807 | if (ikcp_canlog(kcp, IKCP_LOG_IN_DATA)) { 808 | ikcp_log(kcp, IKCP_LOG_IN_DATA, 809 | "input psh: sn=%lu ts=%lu", sn, ts); 810 | } 811 | if (_itimediff(sn, kcp->rcv_nxt + kcp->rcv_wnd) < 0) { 812 | ikcp_ack_push(kcp, sn, ts); 813 | if (_itimediff(sn, kcp->rcv_nxt) >= 0) { 814 | seg = ikcp_segment_new(kcp, len); 815 | seg->conv = conv; 816 | seg->cmd = cmd; 817 | seg->frg = frg; 818 | seg->wnd = wnd; 819 | seg->ts = ts; 820 | seg->sn = sn; 821 | seg->una = una; 822 | seg->len = len; 823 | 824 | if (len > 0) { 825 | memcpy(seg->data, data, len); 826 | } 827 | 828 | ikcp_parse_data(kcp, seg); 829 | } 830 | } 831 | } 832 | else if (cmd == IKCP_CMD_WASK) { 833 | // ready to send back IKCP_CMD_WINS in ikcp_flush 834 | // tell remote my window size 835 | kcp->probe |= IKCP_ASK_TELL; 836 | if (ikcp_canlog(kcp, IKCP_LOG_IN_PROBE)) { 837 | ikcp_log(kcp, IKCP_LOG_IN_PROBE, "input probe"); 838 | } 839 | } 840 | else if (cmd == IKCP_CMD_WINS) { 841 | // do nothing 842 | if (ikcp_canlog(kcp, IKCP_LOG_IN_WINS)) { 843 | ikcp_log(kcp, IKCP_LOG_IN_WINS, 844 | "input wins: %lu", (IUINT32)(wnd)); 845 | } 846 | } 847 | else { 848 | return -3; 849 | } 850 | 851 | data += len; 852 | size -= len; 853 | } 854 | 855 | if (flag != 0) { 856 | ikcp_parse_fastack(kcp, maxack); 857 | } 858 | 859 | if (_itimediff(kcp->snd_una, una) > 0) { 860 | if (kcp->cwnd < kcp->rmt_wnd) { 861 | IUINT32 mss = kcp->mss; 862 | if (kcp->cwnd < kcp->ssthresh) { 863 | kcp->cwnd++; 864 | kcp->incr += mss; 865 | } else { 866 | if (kcp->incr < mss) kcp->incr = mss; 867 | kcp->incr += (mss * mss) / kcp->incr + (mss / 16); 868 | if ((kcp->cwnd + 1) * mss <= kcp->incr) { 869 | kcp->cwnd++; 870 | } 871 | } 872 | if (kcp->cwnd > kcp->rmt_wnd) { 873 | kcp->cwnd = kcp->rmt_wnd; 874 | kcp->incr = kcp->rmt_wnd * mss; 875 | } 876 | } 877 | } 878 | 879 | return 0; 880 | } 881 | 882 | 883 | //--------------------------------------------------------------------- 884 | // ikcp_encode_seg 885 | //--------------------------------------------------------------------- 886 | static char *ikcp_encode_seg(char *ptr, const IKCPSEG *seg) 887 | { 888 | ptr = ikcp_encode32u(ptr, seg->conv); 889 | ptr = ikcp_encode8u(ptr, (IUINT8)seg->cmd); 890 | ptr = ikcp_encode8u(ptr, (IUINT8)seg->frg); 891 | ptr = ikcp_encode16u(ptr, (IUINT16)seg->wnd); 892 | ptr = ikcp_encode32u(ptr, seg->ts); 893 | ptr = ikcp_encode32u(ptr, seg->sn); 894 | ptr = ikcp_encode32u(ptr, seg->una); 895 | ptr = ikcp_encode32u(ptr, seg->len); 896 | return ptr; 897 | } 898 | 899 | static int ikcp_wnd_unused(const ikcpcb *kcp) 900 | { 901 | if (kcp->nrcv_que < kcp->rcv_wnd) { 902 | return kcp->rcv_wnd - kcp->nrcv_que; 903 | } 904 | return 0; 905 | } 906 | 907 | 908 | //--------------------------------------------------------------------- 909 | // ikcp_flush 910 | //--------------------------------------------------------------------- 911 | void ikcp_flush(ikcpcb *kcp) 912 | { 913 | IUINT32 current = kcp->current; 914 | char *buffer = kcp->buffer; 915 | char *ptr = buffer; 916 | int count, size, i; 917 | IUINT32 resent, cwnd; 918 | IUINT32 rtomin; 919 | struct IQUEUEHEAD *p; 920 | int change = 0; 921 | int lost = 0; 922 | IKCPSEG seg; 923 | 924 | // 'ikcp_update' haven't been called. 925 | if (kcp->updated == 0) return; 926 | 927 | seg.conv = kcp->conv; 928 | seg.cmd = IKCP_CMD_ACK; 929 | seg.frg = 0; 930 | seg.wnd = ikcp_wnd_unused(kcp); 931 | seg.una = kcp->rcv_nxt; 932 | seg.len = 0; 933 | seg.sn = 0; 934 | seg.ts = 0; 935 | 936 | // flush acknowledges 937 | count = kcp->ackcount; 938 | for (i = 0; i < count; i++) { 939 | size = (int)(ptr - buffer); 940 | if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { 941 | ikcp_output(kcp, buffer, size); 942 | ptr = buffer; 943 | } 944 | ikcp_ack_get(kcp, i, &seg.sn, &seg.ts); 945 | ptr = ikcp_encode_seg(ptr, &seg); 946 | } 947 | 948 | kcp->ackcount = 0; 949 | 950 | // probe window size (if remote window size equals zero) 951 | if (kcp->rmt_wnd == 0) { 952 | if (kcp->probe_wait == 0) { 953 | kcp->probe_wait = IKCP_PROBE_INIT; 954 | kcp->ts_probe = kcp->current + kcp->probe_wait; 955 | } 956 | else { 957 | if (_itimediff(kcp->current, kcp->ts_probe) >= 0) { 958 | if (kcp->probe_wait < IKCP_PROBE_INIT) 959 | kcp->probe_wait = IKCP_PROBE_INIT; 960 | kcp->probe_wait += kcp->probe_wait / 2; 961 | if (kcp->probe_wait > IKCP_PROBE_LIMIT) 962 | kcp->probe_wait = IKCP_PROBE_LIMIT; 963 | kcp->ts_probe = kcp->current + kcp->probe_wait; 964 | kcp->probe |= IKCP_ASK_SEND; 965 | } 966 | } 967 | } else { 968 | kcp->ts_probe = 0; 969 | kcp->probe_wait = 0; 970 | } 971 | 972 | // flush window probing commands 973 | if (kcp->probe & IKCP_ASK_SEND) { 974 | seg.cmd = IKCP_CMD_WASK; 975 | size = (int)(ptr - buffer); 976 | if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { 977 | ikcp_output(kcp, buffer, size); 978 | ptr = buffer; 979 | } 980 | ptr = ikcp_encode_seg(ptr, &seg); 981 | } 982 | 983 | // flush window probing commands 984 | if (kcp->probe & IKCP_ASK_TELL) { 985 | seg.cmd = IKCP_CMD_WINS; 986 | size = (int)(ptr - buffer); 987 | if (size + (int)IKCP_OVERHEAD > (int)kcp->mtu) { 988 | ikcp_output(kcp, buffer, size); 989 | ptr = buffer; 990 | } 991 | ptr = ikcp_encode_seg(ptr, &seg); 992 | } 993 | 994 | kcp->probe = 0; 995 | 996 | // calculate window size 997 | cwnd = _imin_(kcp->snd_wnd, kcp->rmt_wnd); 998 | if (kcp->nocwnd == 0) cwnd = _imin_(kcp->cwnd, cwnd); 999 | 1000 | // move data from snd_queue to snd_buf 1001 | while (_itimediff(kcp->snd_nxt, kcp->snd_una + cwnd) < 0) { 1002 | IKCPSEG *newseg; 1003 | if (iqueue_is_empty(&kcp->snd_queue)) break; 1004 | 1005 | newseg = iqueue_entry(kcp->snd_queue.next, IKCPSEG, node); 1006 | 1007 | iqueue_del(&newseg->node); 1008 | iqueue_add_tail(&newseg->node, &kcp->snd_buf); 1009 | kcp->nsnd_que--; 1010 | kcp->nsnd_buf++; 1011 | 1012 | newseg->conv = kcp->conv; 1013 | newseg->cmd = IKCP_CMD_PUSH; 1014 | newseg->wnd = seg.wnd; 1015 | newseg->ts = current; 1016 | newseg->sn = kcp->snd_nxt++; 1017 | newseg->una = kcp->rcv_nxt; 1018 | newseg->resendts = current; 1019 | newseg->rto = kcp->rx_rto; 1020 | newseg->fastack = 0; 1021 | newseg->xmit = 0; 1022 | } 1023 | 1024 | // calculate resent 1025 | resent = (kcp->fastresend > 0)? (IUINT32)kcp->fastresend : 0xffffffff; 1026 | rtomin = (kcp->nodelay == 0)? (kcp->rx_rto >> 3) : 0; 1027 | 1028 | // flush data segments 1029 | for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { 1030 | IKCPSEG *segment = iqueue_entry(p, IKCPSEG, node); 1031 | int needsend = 0; 1032 | if (segment->xmit == 0) { 1033 | needsend = 1; 1034 | segment->xmit++; 1035 | segment->rto = kcp->rx_rto; 1036 | segment->resendts = current + segment->rto + rtomin; 1037 | } 1038 | else if (_itimediff(current, segment->resendts) >= 0) { 1039 | needsend = 1; 1040 | segment->xmit++; 1041 | kcp->xmit++; 1042 | if (kcp->nodelay == 0) { 1043 | segment->rto += kcp->rx_rto; 1044 | } else { 1045 | segment->rto += kcp->rx_rto / 2; 1046 | } 1047 | segment->resendts = current + segment->rto; 1048 | lost = 1; 1049 | } 1050 | else if (segment->fastack >= resent) { 1051 | needsend = 1; 1052 | segment->xmit++; 1053 | segment->fastack = 0; 1054 | segment->resendts = current + segment->rto; 1055 | change++; 1056 | } 1057 | 1058 | if (needsend) { 1059 | int size, need; 1060 | segment->ts = current; 1061 | segment->wnd = seg.wnd; 1062 | segment->una = kcp->rcv_nxt; 1063 | 1064 | size = (int)(ptr - buffer); 1065 | need = IKCP_OVERHEAD + segment->len; 1066 | 1067 | if (size + need > (int)kcp->mtu) { 1068 | ikcp_output(kcp, buffer, size); 1069 | ptr = buffer; 1070 | } 1071 | 1072 | ptr = ikcp_encode_seg(ptr, segment); 1073 | 1074 | if (segment->len > 0) { 1075 | memcpy(ptr, segment->data, segment->len); 1076 | ptr += segment->len; 1077 | } 1078 | 1079 | if (segment->xmit >= kcp->dead_link) { 1080 | kcp->state = -1; 1081 | } 1082 | } 1083 | } 1084 | 1085 | // flash remain segments 1086 | size = (int)(ptr - buffer); 1087 | if (size > 0) { 1088 | ikcp_output(kcp, buffer, size); 1089 | } 1090 | 1091 | // update ssthresh 1092 | if (change) { 1093 | IUINT32 inflight = kcp->snd_nxt - kcp->snd_una; 1094 | kcp->ssthresh = inflight / 2; 1095 | if (kcp->ssthresh < IKCP_THRESH_MIN) 1096 | kcp->ssthresh = IKCP_THRESH_MIN; 1097 | kcp->cwnd = kcp->ssthresh + resent; 1098 | kcp->incr = kcp->cwnd * kcp->mss; 1099 | } 1100 | 1101 | if (lost) { 1102 | kcp->ssthresh = cwnd / 2; 1103 | if (kcp->ssthresh < IKCP_THRESH_MIN) 1104 | kcp->ssthresh = IKCP_THRESH_MIN; 1105 | kcp->cwnd = 1; 1106 | kcp->incr = kcp->mss; 1107 | } 1108 | 1109 | if (kcp->cwnd < 1) { 1110 | kcp->cwnd = 1; 1111 | kcp->incr = kcp->mss; 1112 | } 1113 | } 1114 | 1115 | 1116 | //--------------------------------------------------------------------- 1117 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 1118 | // ikcp_check when to call it again (without ikcp_input/_send calling). 1119 | // 'current' - current timestamp in millisec. 1120 | //--------------------------------------------------------------------- 1121 | void ikcp_update(ikcpcb *kcp, IUINT32 current) 1122 | { 1123 | IINT32 slap; 1124 | 1125 | kcp->current = current; 1126 | 1127 | if (kcp->updated == 0) { 1128 | kcp->updated = 1; 1129 | kcp->ts_flush = kcp->current; 1130 | } 1131 | 1132 | slap = _itimediff(kcp->current, kcp->ts_flush); 1133 | 1134 | if (slap >= 10000 || slap < -10000) { 1135 | kcp->ts_flush = kcp->current; 1136 | slap = 0; 1137 | } 1138 | 1139 | if (slap >= 0) { 1140 | kcp->ts_flush += kcp->interval; 1141 | if (_itimediff(kcp->current, kcp->ts_flush) >= 0) { 1142 | kcp->ts_flush = kcp->current + kcp->interval; 1143 | } 1144 | ikcp_flush(kcp); 1145 | } 1146 | } 1147 | 1148 | 1149 | //--------------------------------------------------------------------- 1150 | // Determine when should you invoke ikcp_update: 1151 | // returns when you should invoke ikcp_update in millisec, if there 1152 | // is no ikcp_input/_send calling. you can call ikcp_update in that 1153 | // time, instead of call update repeatly. 1154 | // Important to reduce unnacessary ikcp_update invoking. use it to 1155 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 1156 | // or optimize ikcp_update when handling massive kcp connections) 1157 | //--------------------------------------------------------------------- 1158 | IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current) 1159 | { 1160 | IUINT32 ts_flush = kcp->ts_flush; 1161 | IINT32 tm_flush = 0x7fffffff; 1162 | IINT32 tm_packet = 0x7fffffff; 1163 | IUINT32 minimal = 0; 1164 | struct IQUEUEHEAD *p; 1165 | 1166 | if (kcp->updated == 0) { 1167 | return current; 1168 | } 1169 | 1170 | if (_itimediff(current, ts_flush) >= 10000 || 1171 | _itimediff(current, ts_flush) < -10000) { 1172 | ts_flush = current; 1173 | } 1174 | 1175 | if (_itimediff(current, ts_flush) >= 0) { 1176 | return current; 1177 | } 1178 | 1179 | tm_flush = _itimediff(ts_flush, current); 1180 | 1181 | for (p = kcp->snd_buf.next; p != &kcp->snd_buf; p = p->next) { 1182 | const IKCPSEG *seg = iqueue_entry(p, const IKCPSEG, node); 1183 | IINT32 diff = _itimediff(seg->resendts, current); 1184 | if (diff <= 0) { 1185 | return current; 1186 | } 1187 | if (diff < tm_packet) tm_packet = diff; 1188 | } 1189 | 1190 | minimal = (IUINT32)(tm_packet < tm_flush ? tm_packet : tm_flush); 1191 | if (minimal >= kcp->interval) minimal = kcp->interval; 1192 | 1193 | return current + minimal; 1194 | } 1195 | 1196 | 1197 | 1198 | int ikcp_setmtu(ikcpcb *kcp, int mtu) 1199 | { 1200 | char *buffer; 1201 | if (mtu < 50 || mtu < (int)IKCP_OVERHEAD) 1202 | return -1; 1203 | buffer = (char*)ikcp_malloc((mtu + IKCP_OVERHEAD) * 3); 1204 | if (buffer == NULL) 1205 | return -2; 1206 | kcp->mtu = mtu; 1207 | kcp->mss = kcp->mtu - IKCP_OVERHEAD; 1208 | ikcp_free(kcp->buffer); 1209 | kcp->buffer = buffer; 1210 | return 0; 1211 | } 1212 | 1213 | int ikcp_interval(ikcpcb *kcp, int interval) 1214 | { 1215 | if (interval > 5000) interval = 5000; 1216 | else if (interval < 10) interval = 10; 1217 | kcp->interval = interval; 1218 | return 0; 1219 | } 1220 | 1221 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc) 1222 | { 1223 | if (nodelay >= 0) { 1224 | kcp->nodelay = nodelay; 1225 | if (nodelay) { 1226 | kcp->rx_minrto = IKCP_RTO_NDL; 1227 | } 1228 | else { 1229 | kcp->rx_minrto = IKCP_RTO_MIN; 1230 | } 1231 | } 1232 | if (interval >= 0) { 1233 | if (interval > 5000) interval = 5000; 1234 | else if (interval < 10) interval = 10; 1235 | kcp->interval = interval; 1236 | } 1237 | if (resend >= 0) { 1238 | kcp->fastresend = resend; 1239 | } 1240 | if (nc >= 0) { 1241 | kcp->nocwnd = nc; 1242 | } 1243 | return 0; 1244 | } 1245 | 1246 | 1247 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd) 1248 | { 1249 | if (kcp) { 1250 | if (sndwnd > 0) { 1251 | kcp->snd_wnd = sndwnd; 1252 | } 1253 | if (rcvwnd > 0) { // must >= max fragment size 1254 | kcp->rcv_wnd = _imax_(rcvwnd, IKCP_WND_RCV); 1255 | } 1256 | } 1257 | return 0; 1258 | } 1259 | 1260 | int ikcp_waitsnd(const ikcpcb *kcp) 1261 | { 1262 | return kcp->nsnd_buf + kcp->nsnd_que; 1263 | } 1264 | 1265 | 1266 | // read conv 1267 | IUINT32 ikcp_getconv(const void *ptr) 1268 | { 1269 | IUINT32 conv; 1270 | ikcp_decode32u((const char*)ptr, &conv); 1271 | return conv; 1272 | } 1273 | 1274 | 1275 | -------------------------------------------------------------------------------- /c_src/ikcp.h: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // KCP - A Better ARQ Protocol Implementation 4 | // skywind3000 (at) gmail.com, 2010-2011 5 | // 6 | // Features: 7 | // + Average RTT reduce 30% - 40% vs traditional ARQ like tcp. 8 | // + Maximum RTT reduce three times vs tcp. 9 | // + Lightweight, distributed as a single source file. 10 | // 11 | //===================================================================== 12 | #ifndef __IKCP_H__ 13 | #define __IKCP_H__ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | //===================================================================== 21 | // 32BIT INTEGER DEFINITION 22 | //===================================================================== 23 | #ifndef __INTEGER_32_BITS__ 24 | #define __INTEGER_32_BITS__ 25 | #if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \ 26 | defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \ 27 | defined(_M_AMD64) 28 | typedef unsigned int ISTDUINT32; 29 | typedef int ISTDINT32; 30 | #elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \ 31 | defined(__i386) || defined(_M_X86) 32 | typedef unsigned long ISTDUINT32; 33 | typedef long ISTDINT32; 34 | #elif defined(__MACOS__) 35 | typedef UInt32 ISTDUINT32; 36 | typedef SInt32 ISTDINT32; 37 | #elif defined(__APPLE__) && defined(__MACH__) 38 | #include 39 | typedef u_int32_t ISTDUINT32; 40 | typedef int32_t ISTDINT32; 41 | #elif defined(__BEOS__) 42 | #include 43 | typedef u_int32_t ISTDUINT32; 44 | typedef int32_t ISTDINT32; 45 | #elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__)) 46 | typedef unsigned __int32 ISTDUINT32; 47 | typedef __int32 ISTDINT32; 48 | #elif defined(__GNUC__) 49 | #include 50 | typedef uint32_t ISTDUINT32; 51 | typedef int32_t ISTDINT32; 52 | #else 53 | typedef unsigned long ISTDUINT32; 54 | typedef long ISTDINT32; 55 | #endif 56 | #endif 57 | 58 | 59 | //===================================================================== 60 | // Integer Definition 61 | //===================================================================== 62 | #ifndef __IINT8_DEFINED 63 | #define __IINT8_DEFINED 64 | typedef char IINT8; 65 | #endif 66 | 67 | #ifndef __IUINT8_DEFINED 68 | #define __IUINT8_DEFINED 69 | typedef unsigned char IUINT8; 70 | #endif 71 | 72 | #ifndef __IUINT16_DEFINED 73 | #define __IUINT16_DEFINED 74 | typedef unsigned short IUINT16; 75 | #endif 76 | 77 | #ifndef __IINT16_DEFINED 78 | #define __IINT16_DEFINED 79 | typedef short IINT16; 80 | #endif 81 | 82 | #ifndef __IINT32_DEFINED 83 | #define __IINT32_DEFINED 84 | typedef ISTDINT32 IINT32; 85 | #endif 86 | 87 | #ifndef __IUINT32_DEFINED 88 | #define __IUINT32_DEFINED 89 | typedef ISTDUINT32 IUINT32; 90 | #endif 91 | 92 | #ifndef __IINT64_DEFINED 93 | #define __IINT64_DEFINED 94 | #if defined(_MSC_VER) || defined(__BORLANDC__) 95 | typedef __int64 IINT64; 96 | #else 97 | typedef long long IINT64; 98 | #endif 99 | #endif 100 | 101 | #ifndef __IUINT64_DEFINED 102 | #define __IUINT64_DEFINED 103 | #if defined(_MSC_VER) || defined(__BORLANDC__) 104 | typedef unsigned __int64 IUINT64; 105 | #else 106 | typedef unsigned long long IUINT64; 107 | #endif 108 | #endif 109 | 110 | #ifndef INLINE 111 | #if defined(__GNUC__) 112 | 113 | #if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)) 114 | #define INLINE __inline__ __attribute__((always_inline)) 115 | #else 116 | #define INLINE __inline__ 117 | #endif 118 | 119 | #elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__)) 120 | #define INLINE __inline 121 | #else 122 | #define INLINE 123 | #endif 124 | #endif 125 | 126 | #if (!defined(__cplusplus)) && (!defined(inline)) 127 | #define inline INLINE 128 | #endif 129 | 130 | 131 | //===================================================================== 132 | // QUEUE DEFINITION 133 | //===================================================================== 134 | #ifndef __IQUEUE_DEF__ 135 | #define __IQUEUE_DEF__ 136 | 137 | struct IQUEUEHEAD { 138 | struct IQUEUEHEAD *next, *prev; 139 | }; 140 | 141 | typedef struct IQUEUEHEAD iqueue_head; 142 | 143 | 144 | //--------------------------------------------------------------------- 145 | // queue init 146 | //--------------------------------------------------------------------- 147 | #define IQUEUE_HEAD_INIT(name) { &(name), &(name) } 148 | #define IQUEUE_HEAD(name) \ 149 | struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name) 150 | 151 | #define IQUEUE_INIT(ptr) ( \ 152 | (ptr)->next = (ptr), (ptr)->prev = (ptr)) 153 | 154 | #define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) 155 | 156 | #define ICONTAINEROF(ptr, type, member) ( \ 157 | (type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) ) 158 | 159 | #define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member) 160 | 161 | 162 | //--------------------------------------------------------------------- 163 | // queue operation 164 | //--------------------------------------------------------------------- 165 | #define IQUEUE_ADD(node, head) ( \ 166 | (node)->prev = (head), (node)->next = (head)->next, \ 167 | (head)->next->prev = (node), (head)->next = (node)) 168 | 169 | #define IQUEUE_ADD_TAIL(node, head) ( \ 170 | (node)->prev = (head)->prev, (node)->next = (head), \ 171 | (head)->prev->next = (node), (head)->prev = (node)) 172 | 173 | #define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n)) 174 | 175 | #define IQUEUE_DEL(entry) (\ 176 | (entry)->next->prev = (entry)->prev, \ 177 | (entry)->prev->next = (entry)->next, \ 178 | (entry)->next = 0, (entry)->prev = 0) 179 | 180 | #define IQUEUE_DEL_INIT(entry) do { \ 181 | IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0) 182 | 183 | #define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next) 184 | 185 | #define iqueue_init IQUEUE_INIT 186 | #define iqueue_entry IQUEUE_ENTRY 187 | #define iqueue_add IQUEUE_ADD 188 | #define iqueue_add_tail IQUEUE_ADD_TAIL 189 | #define iqueue_del IQUEUE_DEL 190 | #define iqueue_del_init IQUEUE_DEL_INIT 191 | #define iqueue_is_empty IQUEUE_IS_EMPTY 192 | 193 | #define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \ 194 | for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \ 195 | &((iterator)->MEMBER) != (head); \ 196 | (iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER)) 197 | 198 | #define iqueue_foreach(iterator, head, TYPE, MEMBER) \ 199 | IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) 200 | 201 | #define iqueue_foreach_entry(pos, head) \ 202 | for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next ) 203 | 204 | 205 | #define __iqueue_splice(list, head) do { \ 206 | iqueue_head *first = (list)->next, *last = (list)->prev; \ 207 | iqueue_head *at = (head)->next; \ 208 | (first)->prev = (head), (head)->next = (first); \ 209 | (last)->next = (at), (at)->prev = (last); } while (0) 210 | 211 | #define iqueue_splice(list, head) do { \ 212 | if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0) 213 | 214 | #define iqueue_splice_init(list, head) do { \ 215 | iqueue_splice(list, head); iqueue_init(list); } while (0) 216 | 217 | 218 | #ifdef _MSC_VER 219 | #pragma warning(disable:4311) 220 | #pragma warning(disable:4312) 221 | #pragma warning(disable:4996) 222 | #endif 223 | 224 | #endif 225 | 226 | 227 | //--------------------------------------------------------------------- 228 | // WORD ORDER 229 | //--------------------------------------------------------------------- 230 | #ifndef IWORDS_BIG_ENDIAN 231 | #ifdef _BIG_ENDIAN_ 232 | #if _BIG_ENDIAN_ 233 | #define IWORDS_BIG_ENDIAN 1 234 | #endif 235 | #endif 236 | #ifndef IWORDS_BIG_ENDIAN 237 | #if defined(__hppa__) || \ 238 | defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ 239 | (defined(__MIPS__) && defined(__MIPSEB__)) || \ 240 | defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ 241 | defined(__sparc__) || defined(__powerpc__) || \ 242 | defined(__mc68000__) || defined(__s390x__) || defined(__s390__) 243 | #define IWORDS_BIG_ENDIAN 1 244 | #endif 245 | #endif 246 | #ifndef IWORDS_BIG_ENDIAN 247 | #define IWORDS_BIG_ENDIAN 0 248 | #endif 249 | #endif 250 | 251 | 252 | 253 | //===================================================================== 254 | // SEGMENT 255 | //===================================================================== 256 | struct IKCPSEG 257 | { 258 | struct IQUEUEHEAD node; 259 | IUINT32 conv; 260 | IUINT32 cmd; 261 | IUINT32 frg; 262 | IUINT32 wnd; 263 | IUINT32 ts; 264 | IUINT32 sn; 265 | IUINT32 una; 266 | IUINT32 len; 267 | IUINT32 resendts; 268 | IUINT32 rto; 269 | IUINT32 fastack; 270 | IUINT32 xmit; 271 | char data[1]; 272 | }; 273 | 274 | 275 | //--------------------------------------------------------------------- 276 | // IKCPCB 277 | //--------------------------------------------------------------------- 278 | struct IKCPCB 279 | { 280 | IUINT32 conv, mtu, mss, state; 281 | IUINT32 snd_una, snd_nxt, rcv_nxt; 282 | IUINT32 ts_recent, ts_lastack, ssthresh; 283 | IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto; 284 | IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe; 285 | IUINT32 current, interval, ts_flush, xmit; 286 | IUINT32 nrcv_buf, nsnd_buf; 287 | IUINT32 nrcv_que, nsnd_que; 288 | IUINT32 nodelay, updated; 289 | IUINT32 ts_probe, probe_wait; 290 | IUINT32 dead_link, incr; 291 | struct IQUEUEHEAD snd_queue; 292 | struct IQUEUEHEAD rcv_queue; 293 | struct IQUEUEHEAD snd_buf; 294 | struct IQUEUEHEAD rcv_buf; 295 | IUINT32 *acklist; 296 | IUINT32 ackcount; 297 | IUINT32 ackblock; 298 | void *user; 299 | char *buffer; 300 | int fastresend; 301 | int nocwnd, stream; 302 | int logmask; 303 | int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user); 304 | void (*writelog)(const char *log, struct IKCPCB *kcp, void *user); 305 | }; 306 | 307 | 308 | typedef struct IKCPCB ikcpcb; 309 | 310 | #define IKCP_LOG_OUTPUT 1 311 | #define IKCP_LOG_INPUT 2 312 | #define IKCP_LOG_SEND 4 313 | #define IKCP_LOG_RECV 8 314 | #define IKCP_LOG_IN_DATA 16 315 | #define IKCP_LOG_IN_ACK 32 316 | #define IKCP_LOG_IN_PROBE 64 317 | #define IKCP_LOG_IN_WINS 128 318 | #define IKCP_LOG_OUT_DATA 256 319 | #define IKCP_LOG_OUT_ACK 512 320 | #define IKCP_LOG_OUT_PROBE 1024 321 | #define IKCP_LOG_OUT_WINS 2048 322 | 323 | #ifdef __cplusplus 324 | extern "C" { 325 | #endif 326 | 327 | //--------------------------------------------------------------------- 328 | // interface 329 | //--------------------------------------------------------------------- 330 | 331 | // create a new kcp control object, 'conv' must equal in two endpoint 332 | // from the same connection. 'user' will be passed to the output callback 333 | // output callback can be setup like this: 'kcp->output = my_udp_output' 334 | ikcpcb* ikcp_create(IUINT32 conv, void *user); 335 | 336 | // release kcp control object 337 | void ikcp_release(ikcpcb *kcp); 338 | 339 | // set output callback, which will be invoked by kcp 340 | void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, 341 | ikcpcb *kcp, void *user)); 342 | 343 | // user/upper level recv: returns size, returns below zero for EAGAIN 344 | int ikcp_recv(ikcpcb *kcp, char *buffer, int len); 345 | 346 | // user/upper level send, returns below zero for error 347 | int ikcp_send(ikcpcb *kcp, const char *buffer, int len); 348 | 349 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 350 | // ikcp_check when to call it again (without ikcp_input/_send calling). 351 | // 'current' - current timestamp in millisec. 352 | void ikcp_update(ikcpcb *kcp, IUINT32 current); 353 | 354 | // Determine when should you invoke ikcp_update: 355 | // returns when you should invoke ikcp_update in millisec, if there 356 | // is no ikcp_input/_send calling. you can call ikcp_update in that 357 | // time, instead of call update repeatly. 358 | // Important to reduce unnacessary ikcp_update invoking. use it to 359 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 360 | // or optimize ikcp_update when handling massive kcp connections) 361 | IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current); 362 | 363 | // when you received a low level packet (eg. UDP packet), call it 364 | int ikcp_input(ikcpcb *kcp, const char *data, long size); 365 | 366 | // flush pending data 367 | void ikcp_flush(ikcpcb *kcp); 368 | 369 | // check the size of next message in the recv queue 370 | int ikcp_peeksize(const ikcpcb *kcp); 371 | 372 | // change MTU size, default is 1400 373 | int ikcp_setmtu(ikcpcb *kcp, int mtu); 374 | 375 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 376 | int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd); 377 | 378 | // get how many packet is waiting to be sent 379 | int ikcp_waitsnd(const ikcpcb *kcp); 380 | 381 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 382 | // nodelay: 0:disable(default), 1:enable 383 | // interval: internal update timer interval in millisec, default is 100ms 384 | // resend: 0:disable fast resend(default), 1:enable fast resend 385 | // nc: 0:normal congestion control(default), 1:disable congestion control 386 | int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc); 387 | 388 | 389 | void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...); 390 | 391 | // setup allocator 392 | void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*)); 393 | 394 | // read conv 395 | IUINT32 ikcp_getconv(const void *ptr); 396 | 397 | 398 | #ifdef __cplusplus 399 | } 400 | #endif 401 | 402 | #endif 403 | 404 | 405 | -------------------------------------------------------------------------------- /include/ekcp.hrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 26. 二月 2019 11:22 8 | %%%------------------------------------------------------------------- 9 | 10 | -record(kcp_conn, { 11 | ref, %% 连接标识,可以理解为信道,用来区分不同端口的连接 12 | key, %% 单个连接内的链接标识 13 | ip, %% 链接对端IP 14 | port, %% 对端端口 15 | worker %% 链接的处理进程 16 | }). 17 | 18 | -define(TAB, ets_kcp). 19 | 20 | -define(ACT_REG, 0). 21 | -define(ACT_MSG, 1). 22 | -define(ACT_OFF, 2). 23 | -define(ACT_LOGIN, 3). 24 | -define(ACT_LOGIN_AGAIN, 4). 25 | -define(ACT_HEART, 5). 26 | -define(ACT_NOT_REG, 6). 27 | -define(ACT_ALREADY_LOGIN, 7). 28 | -define(ACT_LOGIN_OTHER, 8). 29 | 30 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [ 2 | debug_info, 3 | {src_dirs, ["src", "test"]} 4 | ]}. 5 | 6 | {deps, []}. 7 | 8 | {shell, [ 9 | % {config, "config/sys.config"}, 10 | {apps, [ekcp]} 11 | ]}. 12 | 13 | %%{port_specs, [ 14 | %% {"priv/ekcp.so", [ 15 | %% "c_src/ekcp.c" 16 | %% ]} 17 | %%]}. 18 | 19 | {pre_hooks, 20 | [{"(linux|darwin|solaris)", compile, "make -C c_src"}, 21 | {"(freebsd)", compile, "gmake -C c_src"}]}. 22 | {post_hooks, 23 | [{"(linux|darwin|solaris)", clean, "make -C c_src clean"}, 24 | {"(freebsd)", clean, "gmake -C c_src clean"}]}. -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yidayoung/ekcp/29d44f1606dd92b8a6add3122aa6b70349363649/rebar3 -------------------------------------------------------------------------------- /src/ekcp.app.src: -------------------------------------------------------------------------------- 1 | {application, ekcp, 2 | [{description, "An OTP application"}, 3 | {vsn, "0.1.0"}, 4 | {registered, []}, 5 | {mod, {ekcp_app, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, []}, 12 | 13 | {maintainers, []}, 14 | {licenses, ["Apache 2.0"]}, 15 | {links, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /src/ekcp.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 22. 二月 2019 18:18 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp). 10 | -on_load(init/0). 11 | 12 | -type kcp_res() :: binary(). %% Kcp create by eckp:create 13 | 14 | %% API 15 | -export([init/0, create/2, recv_data/1, send/2, update/2, check/2, input/2, flush/1, wndsize/3, nodelay/5, release/1, set_mtu/2]). 16 | -export([start_listener/1]). 17 | 18 | init() -> 19 | case code:priv_dir(ekcp) of 20 | {'error', 'bad_name'} -> 21 | erlang:load_nif("./priv/ekcp", 0); 22 | Path -> 23 | erlang:load_nif(filename:join([filename:dirname(Path), "priv/ekcp"]), 0) 24 | end. 25 | 26 | 27 | -spec create(ID::integer(), Pid::pid()) -> {ok, kcp_res()}|create_err. 28 | create(_ID, _Pid) -> 29 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 30 | 31 | -spec recv_data(kcp_res()) -> binary()|nil|res_null. 32 | recv_data(_Kcp) -> 33 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 34 | 35 | -spec send(kcp_res(), binary()) -> integer()|res_null. 36 | send(_Kcp, _Msg) -> 37 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 38 | 39 | -spec update(kcp_res(), integer()) -> ok|res_null. 40 | update(_Kcp, _Current) -> 41 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 42 | 43 | -spec check(kcp_res(), integer()) -> integer()|res_null. 44 | check(_Kcp, _Current) -> 45 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 46 | 47 | -spec input(kcp_res(), binary()) -> integer()|res_null. 48 | input(_Kcp, _Msg) -> 49 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 50 | 51 | -spec flush(kcp_res()) -> ok|res_null. 52 | flush(_Kcp) -> 53 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 54 | 55 | -spec wndsize(kcp_res(), integer(), integer()) -> ok|res_null. 56 | wndsize(_Kcp, _SndWnd, _RcvWnd) -> 57 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 58 | 59 | -spec nodelay(kcp_res(), integer(), integer(), integer(), integer()) -> ok|res_null. 60 | nodelay(_Kcp, _NoDelay, _InterVal, _ReSend, _Nc) -> 61 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 62 | 63 | -spec release(kcp_res()) -> ok|res_null. 64 | release(_Kcp) -> 65 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 66 | 67 | -spec set_mtu(kcp_res(), integer()) -> ok|res_null. 68 | set_mtu(_Kcp, _Mtu) -> 69 | erlang:nif_error({module, ?MODULE}, {line, ?LINE}). 70 | 71 | %%------------------------------- 72 | start_listener(Args) -> 73 | ekcp_listener_srv:start(Args). 74 | -------------------------------------------------------------------------------- /src/ekcp_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc ekcp public API 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(ekcp_app). 7 | -include("ekcp.hrl"). 8 | -behaviour(application). 9 | 10 | %% Application callbacks 11 | -export([start/2, stop/1]). 12 | 13 | %%==================================================================== 14 | %% API 15 | %%==================================================================== 16 | 17 | start(_StartType, _StartArgs) -> 18 | ?TAB = ets:new(?TAB, [ 19 | ordered_set, public, named_table]), 20 | ekcp_sup:start_link(). 21 | 22 | %%-------------------------------------------------------------------- 23 | stop(_State) -> 24 | ok. 25 | 26 | %%==================================================================== 27 | %% Internal functions 28 | %%==================================================================== 29 | -------------------------------------------------------------------------------- /src/ekcp_conn_srv.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 23. 二月 2019 15:53 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_conn_srv). 10 | 11 | 12 | -behaviour(gen_server). 13 | 14 | %% API 15 | -export([start_link/1, start/1, rereg/2, stop/1]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3]). 24 | 25 | -define(SERVER, ?MODULE). 26 | 27 | -record(state, {kcp, mark, opts, ref, handle_module, last_tick}). 28 | 29 | -define(INTERVAL, 10). 30 | -define(HEART_TIME_OUT, 60). 31 | -define(HEART_CHECK_INTERVAL, 1000 * 10). 32 | 33 | %%%=================================================================== 34 | %%% API 35 | %%%=================================================================== 36 | 37 | start(#{mark:=Mark, ref:=Ref} = Args) -> 38 | {ok, Child} = supervisor:start_child(ekcp_conn_sup 39 | , {{?MODULE, Ref, Mark}, {?MODULE, start_link, [Args]} 40 | , temporary, 60000, worker, [?MODULE]}), 41 | Child ! start_init, 42 | {ok, Child}. 43 | 44 | %%-------------------------------------------------------------------- 45 | %% @doc 46 | %% Starts the server 47 | %% 48 | %% @end 49 | %%-------------------------------------------------------------------- 50 | -spec(start_link(Args :: term()) -> 51 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 52 | start_link(Opts) -> 53 | gen_server:start_link(?MODULE, Opts, []). 54 | 55 | 56 | %%%=================================================================== 57 | %%% gen_server callbacks 58 | %%%=================================================================== 59 | 60 | %%-------------------------------------------------------------------- 61 | %% @private 62 | %% @doc 63 | %% Initializes the server 64 | %% 65 | %% @spec init(Args) -> {ok, State} | 66 | %% {ok, State, Timeout} | 67 | %% ignore | 68 | %% {stop, Reason} 69 | %% @end 70 | %%-------------------------------------------------------------------- 71 | -spec(init(Args :: term()) -> 72 | {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | 73 | {stop, Reason :: term()} | ignore). 74 | init(Opts) -> 75 | process_flag(trap_exit, true), 76 | {ok, #state{opts = Opts}}. 77 | 78 | %%-------------------------------------------------------------------- 79 | %% @private 80 | %% @doc 81 | %% Handling call messages 82 | %% 83 | %% @end 84 | %%-------------------------------------------------------------------- 85 | -spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, 86 | State :: #state{}) -> 87 | {reply, Reply :: term(), NewState :: #state{}} | 88 | {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | 89 | {noreply, NewState :: #state{}} | 90 | {noreply, NewState :: #state{}, timeout() | hibernate} | 91 | {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | 92 | {stop, Reason :: term(), NewState :: #state{}}). 93 | handle_call(_Request, _From, State) -> 94 | {reply, ok, State}. 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @private 98 | %% @doc 99 | %% Handling cast messages 100 | %% 101 | %% @end 102 | %%-------------------------------------------------------------------- 103 | -spec(handle_cast(Request :: term(), State :: #state{}) -> 104 | {noreply, NewState :: #state{}} | 105 | {noreply, NewState :: #state{}, timeout() | hibernate} | 106 | {stop, Reason :: term(), NewState :: #state{}}). 107 | handle_cast(stop, State) -> 108 | {stop, normal, State}; 109 | handle_cast({rerege,Opts}, #state{kcp=Kcp} = _State) -> 110 | ekcp:release(Kcp), 111 | {ok, NewState} = init_state(Opts), 112 | {noreply, NewState}; 113 | handle_cast(_Request, State) -> 114 | {noreply, State}. 115 | 116 | %%-------------------------------------------------------------------- 117 | %% @private 118 | %% @doc 119 | %% Handling all non call/cast messages 120 | %% 121 | %% @spec handle_info(Info, State) -> {noreply, State} | 122 | %% {noreply, State, Timeout} | 123 | %% {stop, Reason, State} 124 | %% @end 125 | %%-------------------------------------------------------------------- 126 | -spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> 127 | {noreply, NewState :: #state{}} | 128 | {noreply, NewState :: #state{}, timeout() | hibernate} | 129 | {stop, Reason :: term(), NewState :: #state{}}). 130 | handle_info(start_init, #state{opts = Opts} = _State) -> 131 | {ok, NewState} = init_state(Opts), 132 | erlang:send_after(?INTERVAL, self(), update), 133 | erlang:send_after(?HEART_CHECK_INTERVAL, self(), heart_check), 134 | {noreply, NewState}; 135 | handle_info(Info, State) -> 136 | case catch (do_handle_info(Info, State)) of 137 | {noreply, NewState} -> 138 | {noreply, NewState}; 139 | Err -> 140 | io:format("Err:~p~n", [Err]), 141 | {noreply, State} 142 | end. 143 | 144 | %%-------------------------------------------------------------------- 145 | %% @private 146 | %% @doc 147 | %% This function is called by a gen_server when it is about to 148 | %% terminate. It should be the opposite of Module:init/1 and do any 149 | %% necessary cleaning up. When it returns, the gen_server terminates 150 | %% with Reason. The return value is ignored. 151 | %% 152 | %% @spec terminate(Reason, State) -> void() 153 | %% @end 154 | %%-------------------------------------------------------------------- 155 | -spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), 156 | State :: #state{}) -> 157 | term()). 158 | terminate(_Reason, #state{kcp = Kcp, ref = Ref, mark = Mark} = _State) -> 159 | ekcp:release(Kcp), 160 | ekcp_lib:clear(Ref, Mark, self()), 161 | ok. 162 | 163 | %%-------------------------------------------------------------------- 164 | %% @private 165 | %% @doc 166 | %% Convert process state when code is changed 167 | %% 168 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 169 | %% @end 170 | %%-------------------------------------------------------------------- 171 | -spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, 172 | Extra :: term()) -> 173 | {ok, NewState :: #state{}} | {error, Reason :: term()}). 174 | code_change(_OldVsn, State, _Extra) -> 175 | {ok, State}. 176 | 177 | %%%=================================================================== 178 | %%% Internal functions 179 | %%%=================================================================== 180 | 181 | %% 这里获得的值必须不能超过int32,否则c逻辑那边会出错 182 | millis() -> 183 | {_MegaSecs, Secs, MicroSecs} = erlang:timestamp(), 184 | (Secs * 1000 + MicroSecs div 1000) rem 36000000. 185 | nowsec() -> 186 | {M, S, _} = os:timestamp(), 187 | M * 1000000 + S. 188 | 189 | 190 | %% 从udp层解包后得到的kcp包体 191 | do_handle_info({from_udp_msg, From, Binary}, #state{kcp = Kcp, handle_module = HandleModule} = State) -> 192 | %% io:format("got msg from udp:~p~n", [Binary]), 193 | ekcp:input(Kcp, Binary), 194 | flush(Kcp, From, HandleModule), 195 | {noreply, State#state{last_tick = nowsec()}}; 196 | do_handle_info(update, #state{kcp = Kcp} = State) -> 197 | ekcp:update(Kcp, millis()), 198 | erlang:send_after(?INTERVAL, self(), update), 199 | {noreply, State}; 200 | do_handle_info(heart_beat, State) -> 201 | {noreply, State#state{last_tick = nowsec()}}; 202 | do_handle_info(heart_check, #state{last_tick = LastTick} = State) -> 203 | NowSec = nowsec(), 204 | if 205 | NowSec - LastTick > ?HEART_TIME_OUT -> 206 | gen_server:cast(self(), stop); 207 | true -> 208 | erlang:send_after(?HEART_CHECK_INTERVAL, self(), heart_check) 209 | end, 210 | {noreply, State}; 211 | %% 这里的Msg并不一定和ekcp:send的入参一样,如果单条消息很长,会被拆分成多个包,而一个kcp_msg只是一个包 212 | %% 回复的ack包也是走这条逻辑 213 | do_handle_info({kcp_msg, Msg}, #state{ref = Ref, mark = Mark, handle_module = HandleModule} = State) -> 214 | %% io:format("receive kcp_msg:~p~n",[Msg]), 215 | Listener = ekcp_lib:listener(Ref), 216 | MsgBody = HandleModule:encode_packet(Mark, Msg), 217 | Listener ! {send_udp_msg, Mark, MsgBody}, 218 | {noreply, State}; 219 | do_handle_info({send_msg, Msg}, #state{kcp = Kcp} = State) -> 220 | %% io:format("send msg ~p~n", [Msg]), 221 | ekcp:send(Kcp, Msg), 222 | ekcp:update(Kcp, millis()), 223 | {noreply, State}. 224 | 225 | rereg(Pid, Opts) -> 226 | gen_server:cast(Pid, {rerege, Opts}). 227 | 228 | init_state(Opts) -> 229 | Mark = maps:get(mark, Opts), 230 | {ok, Kcp} = ekcp:create(Mark, self()), 231 | 232 | SndWnd = maps:get(sndwnd, Opts, 32), 233 | RcvWnd = maps:get(rcvwnd, Opts, 32), 234 | ekcp:wndsize(Kcp, SndWnd, RcvWnd), 235 | 236 | NoDelay = maps:get(nodelay, Opts, 0), 237 | Interval = maps:get(interval, Opts, 10), 238 | Resend = maps:get(resend, Opts, 0), 239 | Nc = maps:get(nc, Opts, 0), 240 | ekcp:nodelay(Kcp, NoDelay, Interval, Resend, Nc), 241 | 242 | HandleModule = maps:get(handle_module, Opts), 243 | Ref = maps:get(ref, Opts), 244 | {ok, #state{kcp = Kcp, mark = Mark, handle_module = HandleModule, ref = Ref, last_tick = nowsec(), opts = Opts}}. 245 | 246 | flush(Kcp, From, HandleModule) -> 247 | Msg = ekcp:recv_data(Kcp), 248 | case Msg of 249 | nil -> 250 | pass; 251 | _ -> 252 | HandleModule:route_to_server(From, Msg), 253 | flush(Kcp, From, HandleModule) 254 | end. 255 | 256 | stop(Pid) -> 257 | gen_server:cast(Pid, stop). -------------------------------------------------------------------------------- /src/ekcp_conn_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 25. 二月 2019 16:28 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_conn_sup). 10 | 11 | 12 | -behaviour(supervisor). 13 | 14 | %% API 15 | -export([start_link/0]). 16 | 17 | %% Supervisor callbacks 18 | -export([init/1]). 19 | 20 | -define(SERVER, ?MODULE). 21 | 22 | %%%=================================================================== 23 | %%% API functions 24 | %%%=================================================================== 25 | 26 | %%-------------------------------------------------------------------- 27 | %% @doc 28 | %% Starts the supervisor 29 | %% 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | -spec(start_link() -> 33 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 34 | start_link() -> 35 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 36 | 37 | %%%=================================================================== 38 | %%% Supervisor callbacks 39 | %%%=================================================================== 40 | 41 | %%-------------------------------------------------------------------- 42 | %% @private 43 | %% @doc 44 | %% Whenever a supervisor is started using supervisor:start_link/[2,3], 45 | %% this function is called by the new process to find out about 46 | %% restart strategy, maximum restart frequency and child 47 | %% specifications. 48 | %% 49 | %% @end 50 | %%-------------------------------------------------------------------- 51 | -spec(init(Args :: term()) -> 52 | {ok, {SupFlags :: {RestartStrategy :: supervisor:strategy(), 53 | MaxR :: non_neg_integer(), MaxT :: non_neg_integer()}, 54 | [ChildSpec :: supervisor:child_spec()] 55 | }} | 56 | ignore | 57 | {error, Reason :: term()}). 58 | init([]) -> 59 | {ok, {{one_for_one, 10, 10}, []}}. 60 | 61 | %%%=================================================================== 62 | %%% Internal functions 63 | %%%=================================================================== 64 | -------------------------------------------------------------------------------- /src/ekcp_handle.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 25. 二月 2019 18:36 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_handle). 10 | -include("ekcp.hrl"). 11 | -behavior(kcp_handle). 12 | %% API 13 | -export([reg/6, route/6, send/4, decode_packet/1, encode_packet/2, 14 | route_to_server/2, heart/5, open/1, disconnect/5]). 15 | 16 | open(Port) -> 17 | gen_udp:open(Port, [binary, {active, true}]). 18 | 19 | %% 收到玩家的udp连接请求,将RoleID和IP端口进行绑定,并创建进程 20 | %% 如果已经存在玩家的信息则进行更新 21 | reg(Ref, From, Socket, IP, InPortNo, Opts) -> 22 | Opts2 = Opts#{mark => From, ref => Ref}, 23 | case ekcp_lib:worker(Ref, From) of 24 | Worker when is_pid(Worker) -> 25 | ekcp_conn_srv:rereg(Worker, Opts2), 26 | ets:insert(?TAB, {{connection, Ref, From}, #kcp_conn{ref = Ref, key = From, ip = IP, port = InPortNo, worker = Worker}}), 27 | gen_udp:send(Socket, IP, InPortNo, <>), 28 | {OldIP, OldPort} = ekcp_lib:get_address(Ref, From), 29 | gen_udp:send(Socket, OldIP, OldPort, <>); 30 | _ -> 31 | {ok, GwPid} = ekcp_conn_srv:start(Opts2), 32 | ets:insert(?TAB, {{connection, Ref, From}, #kcp_conn{ref = Ref, key = From, ip = IP, port = InPortNo, worker = GwPid}}), 33 | gen_udp:send(Socket, IP, InPortNo, <>) 34 | end, 35 | ok. 36 | 37 | 38 | %% 收到的kcp包,转发给handle处理进行进行解包 39 | route(Ref, From, Socket, IP, InPortNo, Binary) -> 40 | case ekcp_lib:worker(Ref, From) of 41 | Worker when is_pid(Worker) -> 42 | case ekcp_lib:get_address(Ref, From) of 43 | {IP, InPortNo} -> 44 | Worker ! {from_udp_msg, From, Binary}; 45 | _ -> 46 | gen_udp:send(Socket, IP, InPortNo, <>) 47 | end; 48 | _ -> 49 | gen_udp:send(Socket, IP, InPortNo, <>) 50 | end, 51 | ok. 52 | 53 | %% 收到玩家的回复消息,将消息发送给UDP对端 54 | send(Ref, Socket, Tar, Msg) -> 55 | case ekcp_lib:get_address(Ref, Tar) of 56 | {IP, Port} -> 57 | gen_udp:send(Socket, IP, Port, Msg); 58 | _ -> 59 | io:format("want send msg by udp to ~p, buf cant find address~n", [Tar]) 60 | end, 61 | ok. 62 | 63 | %% udp包解包到kcp包 64 | decode_packet(<<0:8, From:32, Msg/binary>>) -> 65 | {reg, From, Msg}; 66 | decode_packet(<<1:8, From:32, Msg/binary>>) -> 67 | {normal_msg, From, Msg}; 68 | decode_packet(<<5:8, From:32, Msg/binary>>) -> 69 | {heart, From, Msg}. 70 | 71 | %% kcp包打包成udp发送包 72 | encode_packet(Tar, Msg) -> 73 | <<1:8, Tar:32, Msg/binary>>. 74 | 75 | route_to_server(From, Msg) -> 76 | io:format("RoleID ~p got msg:~p~n", [From, Msg]). 77 | 78 | heart(Ref, From, Socket, IP, InPortNo) -> 79 | case ekcp_lib:worker(Ref, From) of 80 | Worker when is_pid(Worker) -> 81 | case ekcp_lib:get_address(Ref, From) of 82 | {IP, InPortNo} -> 83 | NowSec = ekcp_lib:nowsec(), 84 | gen_udp:send(Socket, IP, InPortNo, <>), 85 | Worker ! heart_beat; 86 | _ -> 87 | gen_udp:send(Socket, IP, InPortNo, <>) 88 | end; 89 | _ -> 90 | gen_udp:send(Socket, IP, InPortNo, <>) 91 | end, 92 | ok. 93 | 94 | disconnect(Ref, From, Socket, IP, InPortNo) -> 95 | case ekcp_lib:worker(Ref, From) of 96 | Worker when is_pid(Worker) -> 97 | case ekcp_lib:get_address(Ref, From) of 98 | {IP, InPortNo} -> 99 | ekcp_conn_srv:stop(Worker), 100 | gen_udp:send(Socket, IP, InPortNo, <>); 101 | _ -> 102 | gen_udp:send(Socket, IP, InPortNo, <>) 103 | end; 104 | _ -> 105 | gen_udp:send(Socket, IP, InPortNo, <>) 106 | end. -------------------------------------------------------------------------------- /src/ekcp_lib.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 26. 二月 2019 11:45 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_lib). 10 | -include("ekcp.hrl"). 11 | 12 | %% API 13 | -export([worker/2, get_address/2, clear/3, nowsec/0, listener/1]). 14 | 15 | worker(Ref, RoleID) -> 16 | case ets:lookup(?TAB, {connection, Ref, RoleID}) of 17 | [{_,#kcp_conn{worker = Worker}}] -> 18 | Worker; 19 | _ -> 20 | undefined 21 | end. 22 | 23 | get_address(Ref, RoleID) -> 24 | case ets:lookup(?TAB, {connection, Ref, RoleID}) of 25 | [{_, #kcp_conn{ip = IP, port = Port}}] -> 26 | {IP, Port}; 27 | _ -> 28 | undefined 29 | end. 30 | 31 | listener(Ref) -> 32 | case ets:lookup(?TAB, {listener, Ref}) of 33 | [{_,Listener}] when is_pid(Listener) -> 34 | Listener; 35 | _ -> 36 | undefined 37 | end. 38 | 39 | clear(Ref, RoleID, Pid) -> 40 | case worker(Ref, RoleID) of 41 | Pid -> 42 | ets:delete(?TAB, {connection, Ref, RoleID}); 43 | _ -> 44 | pass 45 | end. 46 | 47 | nowsec() -> 48 | {M, S, _} = os:timestamp(), 49 | M * 1000000 + S. 50 | -------------------------------------------------------------------------------- /src/ekcp_listener_srv.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 25. 二月 2019 18:13 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_listener_srv). 10 | -include("ekcp.hrl"). 11 | 12 | -behaviour(gen_server). 13 | 14 | %% API 15 | -export([start_link/1, start/1]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3]). 24 | 25 | -define(SERVER, ?MODULE). 26 | 27 | -record(state, {handle_module, port, socket, opts, ref}). 28 | 29 | %%%=================================================================== 30 | %%% API 31 | %%%=================================================================== 32 | start([#{port:=Port}] = Args) -> 33 | supervisor:start_child(ekcp_sup 34 | , {{?MODULE, Port}, {?MODULE, start_link, Args} 35 | , permanent, 60000, worker, [?MODULE]}). 36 | 37 | %%-------------------------------------------------------------------- 38 | %% @doc 39 | %% Starts the server 40 | %% 41 | %% @end 42 | %%-------------------------------------------------------------------- 43 | -spec(start_link(term()) -> 44 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 45 | start_link(Args) -> 46 | gen_server:start_link({local, ?SERVER}, ?MODULE, Args, []). 47 | 48 | %%%=================================================================== 49 | %%% gen_server callbacks 50 | %%%=================================================================== 51 | 52 | %%-------------------------------------------------------------------- 53 | %% @private 54 | %% @doc 55 | %% Initializes the server 56 | %% 57 | %% @spec init(Args) -> {ok, State} | 58 | %% {ok, State, Timeout} | 59 | %% ignore | 60 | %% {stop, Reason} 61 | %% @end 62 | %%-------------------------------------------------------------------- 63 | -spec(init(Args :: term()) -> 64 | {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | 65 | {stop, Reason :: term()} | ignore). 66 | init(Opts) -> 67 | #{port:=Port, handle_module:= HandleModule, ref:= Ref} = Opts, 68 | %% {ok, Socket} = gen_udp:open(Port, [binary, {active, true}]), 69 | {ok, Socket} = HandleModule:open(Port), 70 | ets:insert(?TAB, {{listener, Ref}, self()}), 71 | {ok, #state{handle_module = HandleModule, port = Port, socket = Socket, ref = Ref, opts = Opts}}. 72 | 73 | %%-------------------------------------------------------------------- 74 | %% @private 75 | %% @doc 76 | %% Handling call messages 77 | %% 78 | %% @end 79 | %%-------------------------------------------------------------------- 80 | -spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, 81 | State :: #state{}) -> 82 | {reply, Reply :: term(), NewState :: #state{}} | 83 | {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | 84 | {noreply, NewState :: #state{}} | 85 | {noreply, NewState :: #state{}, timeout() | hibernate} | 86 | {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | 87 | {stop, Reason :: term(), NewState :: #state{}}). 88 | handle_call(_Request, _From, State) -> 89 | {reply, ok, State}. 90 | 91 | %%-------------------------------------------------------------------- 92 | %% @private 93 | %% @doc 94 | %% Handling cast messages 95 | %% 96 | %% @end 97 | %%-------------------------------------------------------------------- 98 | -spec(handle_cast(Request :: term(), State :: #state{}) -> 99 | {noreply, NewState :: #state{}} | 100 | {noreply, NewState :: #state{}, timeout() | hibernate} | 101 | {stop, Reason :: term(), NewState :: #state{}}). 102 | handle_cast(_Request, State) -> 103 | {noreply, State}. 104 | 105 | %%-------------------------------------------------------------------- 106 | %% @private 107 | %% @doc 108 | %% Handling all non call/cast messages 109 | %% 110 | %% @spec handle_info(Info, State) -> {noreply, State} | 111 | %% {noreply, State, Timeout} | 112 | %% {stop, Reason, State} 113 | %% @end 114 | %%-------------------------------------------------------------------- 115 | -spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> 116 | {noreply, NewState :: #state{}} | 117 | {noreply, NewState :: #state{}, timeout() | hibernate} | 118 | {stop, Reason :: term(), NewState :: #state{}}). 119 | %% 单个包体最好不要太大,否则每次input了并不一定可以recv_data成功 120 | %% 如果消息被kcp分包了,每次都会去尝试解包但是必须等到最后一个包到了才能解完 121 | handle_info({udp, Socket, IP, InPortNo, Binary}, #state{socket = Socket, handle_module = HandleModule, ref = Ref, opts = Opts} = State) -> 122 | case HandleModule:decode_packet(Binary) of 123 | {reg, From, _Binary2} -> 124 | HandleModule:reg(Ref, From, Socket, IP, InPortNo, Opts); 125 | {normal_msg, From, Binary2} -> 126 | HandleModule:route(Ref, From, Socket, IP, InPortNo, Binary2); 127 | {heart, From, _Binary2} -> 128 | HandleModule:heart(Ref, From, Socket, IP, InPortNo); 129 | {disconnect, From, _Binary2} -> 130 | HandleModule:disconnect(Ref, From, Socket, IP, InPortNo); 131 | _ -> 132 | pass 133 | end, 134 | {noreply, State}; 135 | handle_info({send_udp_msg, Tar, Msg}, #state{handle_module = HandleModule, socket = Socket, ref = Ref} = State) -> 136 | HandleModule:send(Ref, Socket, Tar, Msg), 137 | {noreply, State}; 138 | handle_info(_Info, State) -> 139 | {noreply, State}. 140 | 141 | %%-------------------------------------------------------------------- 142 | %% @private 143 | %% @doc 144 | %% This function is called by a gen_server when it is about to 145 | %% terminate. It should be the opposite of Module:init/1 and do any 146 | %% necessary cleaning up. When it returns, the gen_server terminates 147 | %% with Reason. The return value is ignored. 148 | %% 149 | %% @spec terminate(Reason, State) -> void() 150 | %% @end 151 | %%-------------------------------------------------------------------- 152 | -spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), 153 | State :: #state{}) -> 154 | term()). 155 | terminate(_Reason, _State) -> 156 | ok. 157 | 158 | %%-------------------------------------------------------------------- 159 | %% @private 160 | %% @doc 161 | %% Convert process state when code is changed 162 | %% 163 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 164 | %% @end 165 | %%-------------------------------------------------------------------- 166 | -spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, 167 | Extra :: term()) -> 168 | {ok, NewState :: #state{}} | {error, Reason :: term()}). 169 | code_change(_OldVsn, State, _Extra) -> 170 | {ok, State}. 171 | 172 | %%%=================================================================== 173 | %%% Internal functions 174 | %%%=================================================================== 175 | -------------------------------------------------------------------------------- /src/ekcp_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %% @doc ekcp top level supervisor. 3 | %% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(ekcp_sup). 7 | 8 | -behaviour(supervisor). 9 | 10 | %% API 11 | -export([start_link/0]). 12 | 13 | %% Supervisor callbacks 14 | -export([init/1]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | %%==================================================================== 19 | %% API functions 20 | %%==================================================================== 21 | 22 | start_link() -> 23 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 24 | 25 | %%==================================================================== 26 | %% Supervisor callbacks 27 | %%==================================================================== 28 | 29 | %% Child :: #{id => Id, start => {M, F, A}} 30 | %% Optional keys are restart, shutdown, type, modules. 31 | %% Before OTP 18 tuples must be used to specify a child. e.g. 32 | %% Child :: {Id,StartFunc,Restart,Shutdown,Type,Modules} 33 | init([]) -> 34 | Procs = [ 35 | {ekcp_conn_sup, {ekcp_conn_sup, start_link, []}, 36 | permanent, 5000, worker, [ekcp_conn_sup]} 37 | ], 38 | {ok, {{one_for_one, 1, 5}, Procs}}. 39 | 40 | %%==================================================================== 41 | %% Internal functions 42 | %%==================================================================== 43 | -------------------------------------------------------------------------------- /src/kcp_handle.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 28. 二月 2019 11:11 8 | %%%------------------------------------------------------------------- 9 | -module(kcp_handle). 10 | -type socket() :: port(). 11 | 12 | %% API 13 | -export([]). 14 | 15 | -callback open(Port :: integer()) -> 16 | {ok, Socket} | {error, Reason} when 17 | Socket :: socket(), 18 | Reason :: inet:posix(). 19 | 20 | -callback reg(Ref, From, Socket, IP, InPortNo, Opts) -> 21 | ok when 22 | Ref :: term(), 23 | From :: term(), 24 | Socket :: socket(), 25 | IP :: list(), 26 | InPortNo :: number(), 27 | Opts :: map(). 28 | 29 | -callback route(Ref, From, Socket, IP, InPortNo, Binary) -> 30 | ok when 31 | Ref :: term(), 32 | From :: term(), 33 | Socket :: socket(), 34 | IP :: list(), 35 | InPortNo :: number(), 36 | Binary :: binary(). 37 | 38 | -callback send(Ref, Socket, Tar, Msg) -> 39 | ok when 40 | Ref :: term(), 41 | Socket :: socket(), 42 | Tar :: term(), 43 | Msg :: binary(). 44 | 45 | -callback decode_packet(Packet) -> 46 | {reg, From, Msg}| 47 | {normal_msg, From, Msg}| 48 | {heart, From, Msg}| 49 | {disconnect, From, Msg} when 50 | Packet :: binary(). 51 | 52 | -callback encode_packet(Tar, Msg) -> 53 | Packet when 54 | Tar :: term(), 55 | Msg :: binary(), 56 | Packet :: binary(). 57 | 58 | -callback heart(Ref, From, Socket, IP, InPortNo) -> 59 | ok when 60 | Ref :: term(), 61 | From :: term(), 62 | Socket :: term(), 63 | IP :: list(), 64 | InPortNo :: integer(). 65 | 66 | -callback route_to_server(From, Msg) -> 67 | ok when 68 | From :: term(), 69 | Msg :: binary(). 70 | 71 | -callback disconnect(Ref, From, Socket, IP, InPortNo) -> 72 | ok when 73 | Ref :: term(), 74 | From :: term(), 75 | Socket :: socket(), 76 | IP :: list(), 77 | InPortNo :: integer(). 78 | -------------------------------------------------------------------------------- /test/ekcp_test_server.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author wangyida 3 | %%% @copyright (C) 2019, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 28. 二月 2019 13:42 8 | %%%------------------------------------------------------------------- 9 | -module(ekcp_test_server). 10 | 11 | 12 | -behaviour(gen_server). 13 | 14 | %% API 15 | -export([start_link/1]). 16 | 17 | %% gen_server callbacks 18 | -export([init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2, 23 | code_change/3]). 24 | -define(SERVER, ?MODULE). 25 | 26 | -record(state, {kcp, socket, port, tar}). 27 | 28 | -define(INTERVAL, 100). 29 | -define(HEART_INTERVAL, 1000 * 10). 30 | 31 | %%%=================================================================== 32 | %%% API 33 | %%%=================================================================== 34 | 35 | %%-------------------------------------------------------------------- 36 | %% @doc 37 | %% Starts the server 38 | %% 39 | %% @end 40 | %%-------------------------------------------------------------------- 41 | -spec(start_link(Args :: term()) -> 42 | {ok, Pid :: pid()} | ignore | {error, Reason :: term()}). 43 | start_link([Port,_] = Args) -> 44 | gen_server:start_link({local, erlang:list_to_atom("srv_" ++ erlang:integer_to_list(Port))}, ?MODULE, Args, []). 45 | 46 | %%%=================================================================== 47 | %%% gen_server callbacks 48 | %%%=================================================================== 49 | 50 | %%-------------------------------------------------------------------- 51 | %% @private 52 | %% @doc 53 | %% Initializes the server 54 | %% 55 | %% @spec init(Args) -> {ok, State} | 56 | %% {ok, State, Timeout} | 57 | %% ignore | 58 | %% {stop, Reason} 59 | %% @end 60 | %%-------------------------------------------------------------------- 61 | -spec(init(Args :: term()) -> 62 | {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | hibernate} | 63 | {stop, Reason :: term()} | ignore). 64 | init([Port, Tar]) -> 65 | process_flag(trap_exit, true), 66 | erlang:send_after(?INTERVAL, self(), update), 67 | {ok, Socket} = gen_udp:open(Port, [binary, {active, true}]), 68 | {ok, Kcp} = ekcp:create(Port, self()), 69 | ekcp:wndsize(Kcp, 128, 128), 70 | ekcp:nodelay(Kcp, 0, 10, 0, 0), 71 | erlang:send_after(?HEART_INTERVAL, self(), heart), 72 | {ok, #state{kcp = Kcp, socket = Socket, port = Port, tar = Tar}}. 73 | 74 | %%-------------------------------------------------------------------- 75 | %% @private 76 | %% @doc 77 | %% Handling call messages 78 | %% 79 | %% @end 80 | %%-------------------------------------------------------------------- 81 | -spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()}, 82 | State :: #state{}) -> 83 | {reply, Reply :: term(), NewState :: #state{}} | 84 | {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} | 85 | {noreply, NewState :: #state{}} | 86 | {noreply, NewState :: #state{}, timeout() | hibernate} | 87 | {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} | 88 | {stop, Reason :: term(), NewState :: #state{}}). 89 | handle_call(_Request, _From, State) -> 90 | {reply, ok, State}. 91 | 92 | %%-------------------------------------------------------------------- 93 | %% @private 94 | %% @doc 95 | %% Handling cast messages 96 | %% 97 | %% @end 98 | %%-------------------------------------------------------------------- 99 | -spec(handle_cast(Request :: term(), State :: #state{}) -> 100 | {noreply, NewState :: #state{}} | 101 | {noreply, NewState :: #state{}, timeout() | hibernate} | 102 | {stop, Reason :: term(), NewState :: #state{}}). 103 | handle_cast(_Request, State) -> 104 | {noreply, State}. 105 | 106 | %%-------------------------------------------------------------------- 107 | %% @private 108 | %% @doc 109 | %% Handling all non call/cast messages 110 | %% 111 | %% @spec handle_info(Info, State) -> {noreply, State} | 112 | %% {noreply, State, Timeout} | 113 | %% {stop, Reason, State} 114 | %% @end 115 | %%-------------------------------------------------------------------- 116 | -spec(handle_info(Info :: timeout() | term(), State :: #state{}) -> 117 | {noreply, NewState :: #state{}} | 118 | {noreply, NewState :: #state{}, timeout() | hibernate} | 119 | {stop, Reason :: term(), NewState :: #state{}}). 120 | handle_info(Info, State) -> 121 | case catch (do_handle_info(Info, State)) of 122 | {noreply, NewState} -> 123 | {noreply, NewState}; 124 | Err -> 125 | io:format("Err:~p~n", [Err]), 126 | {noreply, State} 127 | end. 128 | 129 | %%-------------------------------------------------------------------- 130 | %% @private 131 | %% @doc 132 | %% This function is called by a gen_server when it is about to 133 | %% terminate. It should be the opposite of Module:init/1 and do any 134 | %% necessary cleaning up. When it returns, the gen_server terminates 135 | %% with Reason. The return value is ignored. 136 | %% 137 | %% @spec terminate(Reason, State) -> void() 138 | %% @end 139 | %%-------------------------------------------------------------------- 140 | -spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()), 141 | State :: #state{}) -> 142 | term()). 143 | terminate(_Reason, #state{kcp = Kcp} = _State) -> 144 | ekcp:release(Kcp), 145 | ok. 146 | 147 | %%-------------------------------------------------------------------- 148 | %% @private 149 | %% @doc 150 | %% Convert process state when code is changed 151 | %% 152 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 153 | %% @end 154 | %%-------------------------------------------------------------------- 155 | -spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{}, 156 | Extra :: term()) -> 157 | {ok, NewState :: #state{}} | {error, Reason :: term()}). 158 | code_change(_OldVsn, State, _Extra) -> 159 | {ok, State}. 160 | 161 | %%%=================================================================== 162 | %%% Internal functions 163 | %%%=================================================================== 164 | 165 | millis() -> 166 | {_MegaSecs, Secs, MicroSecs} = erlang:timestamp(), 167 | (Secs * 1000 + MicroSecs div 1000) rem 36000000. 168 | 169 | do_handle_info({udp, Socket, IP, InPortNo, Binary}, #state{kcp = Kcp, socket = Socket} = State) -> 170 | io:format("receive Binary from udp ~p:~p, ~p~n", [IP, InPortNo, Binary]), 171 | case Binary of 172 | <<1:8, _:32, Binary2/binary>> -> 173 | ekcp:input(Kcp, Binary2), 174 | flush(Kcp); 175 | _ -> 176 | pass 177 | end, 178 | {noreply, State}; 179 | do_handle_info({kcp_msg, Msg}, #state{socket = Socket, port = Port, tar = Tar} = State) -> 180 | io:format("kcp want send msg ~p, Tar:~p~n", [Msg, Tar]), 181 | gen_udp:send(Socket, "127.0.0.1", Tar, <<1:8, Port:32, Msg/binary>>), 182 | {noreply, State}; 183 | do_handle_info(update, #state{kcp = Kcp} = State) -> 184 | ekcp:update(Kcp, millis()), 185 | erlang:send_after(?INTERVAL, self(), update), 186 | {noreply, State}; 187 | do_handle_info({send_msg, Msg}, #state{kcp = Kcp} = State) -> 188 | io:format("send msg ~p~n", [Msg]), 189 | ekcp:send(Kcp, Msg), 190 | {noreply, State}; 191 | do_handle_info(init, #state{socket = Socket, port = Port, tar = Tar} = State) -> 192 | io:format("send reg ~p~n", [Port]), 193 | gen_udp:send(Socket, "127.0.0.1", Tar, <<0:8, Port:32>>), 194 | {noreply, State}; 195 | do_handle_info(reload, #state{socket = Socket, port = Port, kcp = Kcp, tar = Tar} = State) -> 196 | io:format("reload ~p~n", [Port]), 197 | ekcp:release(Kcp), 198 | {ok, NewKcp} = ekcp:create(Port, self()), 199 | gen_udp:send(Socket, "127.0.0.1", Tar, <<0:8, Port:32>>), 200 | {noreply, State#state{kcp = NewKcp}}; 201 | do_handle_info(heart, #state{socket = Socket, port = Port, tar = Tar} = State) -> 202 | gen_udp:send(Socket, "127.0.0.1", Tar, <<5:8, Port:32>>), 203 | erlang:send_after(?HEART_INTERVAL, self(), heart), 204 | {noreply, State}. 205 | 206 | flush(Kcp) -> 207 | Msg = ekcp:recv_data(Kcp), 208 | case Msg of 209 | nil -> 210 | pass; 211 | _ -> 212 | io:format("receive msg from kcp ~p~n", [Msg]), 213 | flush(Kcp) 214 | end. -------------------------------------------------------------------------------- /windows下编译方法.md: -------------------------------------------------------------------------------- 1 | # windows 下可以将kcp库编译成dll,也可以在windows下进行调试 2 | 3 | # 基础准备 4 | - vs任意版本 5 | - erlang环境 6 | 7 | 8 | # 编译步骤 9 | 1. 在vs中新建Visual C++ 的空项目 10 | 2. 将eckp.c, ikcp.c ikcp.h 分别复制到源文件和头文件目录 11 | 3. 修改项目属性,注意是项目不是解决方案,在项目上右键->属性 12 | 4. 首先将最上面的配置选择改成所有配置和所有平台 13 | 5. 在配置属性->常规->项目默认值->配置类型 中将项目类型改成dll 14 | 6. 在配置属性->VC++目录->包含目录 中添加你的erlang,inculde目录, 15 | 比如你的erlang安装在D:\Program Files\erl8.0,那么include目录对应就是D:\Program Files\erl8.0\erts-8.0\include。 16 | 注意是添加,不是覆盖 17 | 7. 修改当前配置,选择为Release,X64(看你自己当前环境,如果是32位操作系统就选X86) 18 | 8. 在项目上右键点击生成,或者F6,输出窗口那会显示产生的dll所在目录 19 | 20 | # 使用 21 | 将编译好的dll拷贝到priv目录中就可以和linux下一样进行调用了 22 | 23 | # 注意点 24 | - 选择erlang inculde目录的时候要选你将来运行erlang的版本,虽然说不一定会出问题,但是最好保持一致 25 | - 如果dll要分享给别人使用,同样要注意操作系统是否是X64,还有erlang版本是否一致 26 | 27 | --------------------------------------------------------------------------------