├── LICENSE ├── Makefile ├── README.md ├── lualib └── socket_proxy.lua ├── service └── socket_proxyd.lua ├── service_package.c └── test ├── client.lua ├── config └── main.lua /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 codingnow.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Use your path 2 | SKYNET_PATH = $(HOME)/skynet 3 | TARGET = $(SKYNET_PATH)/cservice/package.so 4 | 5 | $(TARGET) : service_package.c 6 | gcc -Wall -g --shared -fPIC -o $@ $^ -I$(SKYNET_PATH)/skynet-src 7 | 8 | clean : 9 | rm $(TARGET) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What's this ? 2 | 3 | It's a example to show how to split the packages from a tcp data stream in skynet. 4 | 5 | `service/socket_proxyd.lua` is a manager service of sockets, and `lualib/socket_proxy.lua` is the library. Read the test/main.lua for usecase. 6 | 7 | If you want to manage a tcp data stream, you can call `proxy.subscribe(fd)` first. It register the fd into the manager (the unique service socket_proxyd) , and launch a C service for it to split the tcp data stream. 8 | 9 | `proxy.read(fd)` would request a package (WORD in big endian + DATA) from the C service. 10 | 11 | `proxy.write(fd)` would forward the package to the tcp socket. 12 | 13 | These C services haven't list in debug console, but you can call `info` by `socket_proxyd` in debug console to show these services. 14 | 15 | ## Build 16 | 17 | Modify the Makefile, change SKYNET_PATH to your path of skynet. The default path is $(HOME)/skynet . 18 | 19 | If you are not using linux, make it by yourself. 20 | 21 | ## Test 22 | 23 | Run skynet first : 24 | 25 | ``` 26 | $(HOME)/skynet test/config 27 | ``` 28 | 29 | And then use the simple client in ./test : 30 | 31 | ``` 32 | lua test/client.lua 33 | ``` 34 | 35 | The test/main.lua is a simple echo server, so you can try to type something now. 36 | -------------------------------------------------------------------------------- /lualib/socket_proxy.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local proxyd 4 | 5 | skynet.init(function() 6 | proxyd = skynet.uniqueservice "socket_proxyd" 7 | end) 8 | 9 | local proxy = {} 10 | local map = {} 11 | 12 | skynet.register_protocol { 13 | name = "text", 14 | id = skynet.PTYPE_TEXT, 15 | pack = function(text) return text end, 16 | unpack = function(buf, sz) return skynet.tostring(buf,sz) end, 17 | } 18 | 19 | skynet.register_protocol { 20 | name = "client", 21 | id = skynet.PTYPE_CLIENT, 22 | pack = function(buf, sz) return buf, sz end, 23 | } 24 | 25 | local function get_addr(fd) 26 | return assert(map[fd], "subscribe first") 27 | end 28 | 29 | function proxy.subscribe(fd) 30 | local addr = map[fd] 31 | if not addr then 32 | addr = skynet.call(proxyd, "lua", fd) 33 | map[fd] = addr 34 | end 35 | end 36 | 37 | function proxy.read(fd) 38 | return skynet.rawcall(get_addr(fd), "text", "R") 39 | end 40 | 41 | function proxy.write(fd, msg, sz) 42 | skynet.send(get_addr(fd), "client", msg, sz) 43 | end 44 | 45 | function proxy.close(fd) 46 | skynet.send(get_addr(fd), "text", "K") 47 | end 48 | 49 | function proxy.info(fd) 50 | return skynet.call(get_addr(fd), "text", "I") 51 | end 52 | 53 | return proxy 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /service/socket_proxyd.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | require "skynet.manager" 3 | require "skynet.debug" 4 | 5 | skynet.register_protocol { 6 | name = "text", 7 | id = skynet.PTYPE_TEXT, 8 | unpack = skynet.tostring, 9 | pack = function(text) return text end, 10 | } 11 | 12 | local socket_fd_addr = {} 13 | local socket_addr_fd = {} 14 | local socket_init = {} 15 | 16 | local function close_agent(addr) 17 | local fd = assert(socket_addr_fd[addr]) 18 | socket_fd_addr[fd] = nil 19 | socket_addr_fd[addr] = nil 20 | end 21 | 22 | local function subscribe(fd) 23 | local addr = socket_fd_addr[fd] 24 | if addr then 25 | return addr 26 | end 27 | addr = assert(skynet.launch("package", skynet.self(), fd)) 28 | socket_fd_addr[fd] = addr 29 | socket_addr_fd[addr] = fd 30 | socket_init[addr] = skynet.response() 31 | end 32 | 33 | local function get_status(addr) 34 | local ok, info = pcall(skynet.call,addr, "text", "I") 35 | if ok then 36 | return info 37 | else 38 | return "EXIT" 39 | end 40 | end 41 | 42 | skynet.info_func(function() 43 | local tmp = {} 44 | for fd,addr in pairs(socket_fd_addr) do 45 | if socket_init[addr] then 46 | table.insert(tmp, { fd = fd, addr = skynet.address(addr), status = "NOTREADY" }) 47 | else 48 | table.insert(tmp, { fd = fd, addr = skynet.address(addr), status = get_status(addr) }) 49 | end 50 | end 51 | return tmp 52 | end) 53 | 54 | skynet.start(function() 55 | skynet.dispatch("text", function (session, source, cmd) 56 | if cmd == "CLOSED" then 57 | close_agent(source) 58 | elseif cmd == "SUCC" then 59 | socket_init[source](true, source) 60 | socket_init[source] = nil 61 | elseif cmd == "FAIL" then 62 | socket_init[source](false) 63 | socket_init[source] = nil 64 | else 65 | skynet.error("Invalid command " .. cmd) 66 | end 67 | end) 68 | skynet.dispatch("lua", function (session, source, fd) 69 | assert(type(fd) == "number") 70 | local addr = subscribe(fd) 71 | if addr then 72 | skynet.ret(skynet.pack(addr)) 73 | end 74 | end) 75 | end) 76 | -------------------------------------------------------------------------------- /service_package.c: -------------------------------------------------------------------------------- 1 | #include "skynet.h" 2 | #include "skynet_socket.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define TIMEOUT "1000" // 10s 9 | 10 | struct response { 11 | size_t sz; 12 | void * msg; 13 | }; 14 | 15 | struct request { 16 | uint32_t source; 17 | int session; 18 | }; 19 | 20 | struct queue { 21 | int cap; 22 | int sz; 23 | int head; 24 | int tail; 25 | char * buffer; 26 | }; 27 | 28 | struct package { 29 | uint32_t manager; 30 | int fd; 31 | int heartbeat; 32 | int recv; 33 | int init; 34 | int closed; 35 | 36 | int header_sz; 37 | uint8_t header[2]; 38 | int uncomplete_sz; 39 | struct response uncomplete; 40 | 41 | struct queue request; 42 | struct queue response; 43 | }; 44 | 45 | static void 46 | queue_init(struct queue *q, int sz) { 47 | q->head = 0; 48 | q->tail = 0; 49 | q->sz = sz; 50 | q->cap = 4; 51 | q->buffer = skynet_malloc(q->cap * q->sz); 52 | } 53 | 54 | static void 55 | queue_exit(struct queue *q) { 56 | skynet_free(q->buffer); 57 | q->buffer = NULL; 58 | } 59 | 60 | static int 61 | queue_empty(struct queue *q) { 62 | return q->head == q->tail; 63 | } 64 | 65 | static int 66 | queue_pop(struct queue *q, void *result) { 67 | if (q->head == q->tail) { 68 | return 1; 69 | } 70 | memcpy(result, q->buffer + q->head * q->sz, q->sz); 71 | q->head++; 72 | if (q->head >= q->cap) 73 | q->head = 0; 74 | return 0; 75 | } 76 | 77 | static void 78 | queue_push(struct queue *q, const void *value) { 79 | void * slot = q->buffer + q->tail * q->sz; 80 | ++q->tail; 81 | if (q->tail >= q->cap) 82 | q->tail = 0; 83 | if (q->head == q->tail) { 84 | // full 85 | assert(q->sz > 0); 86 | int cap = q->cap * 2; 87 | char * tmp = skynet_malloc(cap * q->sz); 88 | int i; 89 | int head = q->head; 90 | for (i=0;icap;i++) { 91 | memcpy(tmp + i * q->sz, q->buffer + head * q->sz, q->sz); 92 | ++head; 93 | if (head >= q->cap) { 94 | head = 0; 95 | } 96 | } 97 | skynet_free(q->buffer); 98 | q->head = 0; 99 | slot = tmp + (q->cap-1) * q->sz; 100 | q->tail = q->cap; 101 | q->cap = cap; 102 | q->buffer = tmp; 103 | } 104 | memcpy(slot, value, q->sz); 105 | } 106 | 107 | static int 108 | queue_size(struct queue *q) { 109 | if (q->head > q->tail) { 110 | return q->tail + q->cap - q->head; 111 | } 112 | return q->tail - q->head; 113 | } 114 | 115 | static void 116 | service_exit(struct skynet_context *ctx, struct package *P) { 117 | // report manager 118 | P->closed = 1; 119 | while (!queue_empty(&P->request)) { 120 | struct request req; 121 | queue_pop(&P->request, &req); 122 | skynet_send(ctx, 0, req.source, PTYPE_ERROR, req.session, NULL, 0); 123 | } 124 | // free unsend response buffer on package_release 125 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "CLOSED", 6); 126 | skynet_command(ctx, "EXIT", NULL); 127 | } 128 | 129 | static void 130 | report_info(struct skynet_context *ctx, struct package *P, int session, uint32_t source) { 131 | int uncomplete; 132 | int uncomplete_sz; 133 | if (P->header_sz != 0) { 134 | uncomplete = -1; 135 | uncomplete_sz = 0; 136 | } else if (P->uncomplete_sz == 0) { 137 | uncomplete = 0; 138 | uncomplete_sz = 0; 139 | } else { 140 | uncomplete = P->uncomplete_sz; 141 | uncomplete_sz = P->uncomplete.sz; 142 | } 143 | char tmp[128]; 144 | int n = sprintf(tmp,"req=%d resp=%d uncomplete=%d/%d", queue_size(&P->request), queue_size(&P->response),uncomplete,uncomplete_sz); 145 | skynet_send(ctx, 0, source, PTYPE_RESPONSE, session, tmp, n); 146 | } 147 | 148 | static void 149 | command(struct skynet_context *ctx, struct package *P, int session, uint32_t source, const char *msg, size_t sz) { 150 | switch (msg[0]) { 151 | case 'R': 152 | // request a package 153 | if (P->closed) { 154 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 155 | break; 156 | } 157 | if (!queue_empty(&P->response)) { 158 | assert(queue_empty(&P->request)); 159 | struct response resp; 160 | queue_pop(&P->response, &resp); 161 | skynet_send(ctx, 0, source, PTYPE_RESPONSE | PTYPE_TAG_DONTCOPY, session, resp.msg, resp.sz); 162 | } else { 163 | struct request req; 164 | req.source = source; 165 | req.session = session; 166 | queue_push(&P->request, &req); 167 | } 168 | break; 169 | case 'K': 170 | // shutdown the connection 171 | skynet_socket_shutdown(ctx, P->fd); 172 | break; 173 | case 'I': 174 | report_info(ctx, P, session, source); 175 | break; 176 | default: 177 | // invalid command 178 | skynet_error(ctx, "Invalid command %.*s", (int)sz, msg); 179 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 180 | break; 181 | }; 182 | } 183 | 184 | static void 185 | new_message(struct package *P, const uint8_t *msg, int sz) { 186 | ++P->recv; 187 | for (;;) { 188 | if (P->uncomplete_sz >= 0) { 189 | if (sz >= P->uncomplete_sz) { 190 | memcpy(P->uncomplete.msg + P->uncomplete.sz - P->uncomplete_sz, msg, P->uncomplete_sz); 191 | msg += P->uncomplete_sz; 192 | sz -= P->uncomplete_sz; 193 | queue_push(&P->response, &P->uncomplete); 194 | P->uncomplete_sz = -1; 195 | } else { 196 | memcpy(P->uncomplete.msg + P->uncomplete.sz - P->uncomplete_sz, msg, sz); 197 | P->uncomplete_sz -= sz; 198 | return; 199 | } 200 | } 201 | 202 | if (sz <= 0) 203 | return; 204 | 205 | if (P->header_sz == 0) { 206 | if (sz == 1) { 207 | P->header[0] = msg[0]; 208 | P->header_sz = 1; 209 | return; 210 | } 211 | P->header[0] = msg[0]; 212 | P->header[1] = msg[1]; 213 | msg+=2; 214 | sz-=2; 215 | } else { 216 | assert(P->header_sz == 1); 217 | P->header[1] = msg[0]; 218 | P->header_sz = 0; 219 | ++msg; 220 | --sz; 221 | } 222 | P->uncomplete.sz = P->header[0] * 256 + P->header[1]; 223 | P->uncomplete.msg = skynet_malloc(P->uncomplete.sz); 224 | P->uncomplete_sz = P->uncomplete.sz; 225 | } 226 | } 227 | 228 | static void 229 | response(struct skynet_context *ctx, struct package *P) { 230 | while (!queue_empty(&P->request)) { 231 | if (queue_empty(&P->response)) { 232 | break; 233 | } 234 | struct request req; 235 | struct response resp; 236 | queue_pop(&P->request, &req); 237 | queue_pop(&P->response, &resp); 238 | skynet_send(ctx, 0, req.source, PTYPE_RESPONSE | PTYPE_TAG_DONTCOPY, req.session, resp.msg, resp.sz); 239 | } 240 | } 241 | 242 | static void 243 | socket_message(struct skynet_context *ctx, struct package *P, const struct skynet_socket_message * smsg) { 244 | switch (smsg->type) { 245 | case SKYNET_SOCKET_TYPE_CONNECT: 246 | if (P->init == 0 && smsg->id == P->fd) { 247 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "SUCC", 4); 248 | P->init = 1; 249 | } 250 | break; 251 | case SKYNET_SOCKET_TYPE_CLOSE: 252 | case SKYNET_SOCKET_TYPE_ERROR: 253 | if (P->init == 0 && smsg->id == P->fd) { 254 | skynet_send(ctx, 0, P->manager, PTYPE_TEXT, 0, "FAIL", 4); 255 | P->init = 1; 256 | } 257 | if (smsg->id != P->fd) { 258 | skynet_error(ctx, "Invalid fd (%d), should be (%d)", smsg->id, P->fd); 259 | } else { 260 | // todo: log when SKYNET_SOCKET_TYPE_ERROR 261 | response(ctx, P); 262 | service_exit(ctx, P); 263 | } 264 | break; 265 | case SKYNET_SOCKET_TYPE_DATA: 266 | new_message(P, (const uint8_t *)smsg->buffer, smsg->ud); 267 | skynet_free(smsg->buffer); 268 | response(ctx, P); 269 | break; 270 | case SKYNET_SOCKET_TYPE_WARNING: 271 | skynet_error(ctx, "Overload on %d", P->fd); 272 | break; 273 | default: 274 | // ignore 275 | break; 276 | } 277 | } 278 | 279 | static void 280 | heartbeat(struct skynet_context *ctx, struct package *P) { 281 | if (P->recv == P->heartbeat) { 282 | if (!P->closed) { 283 | skynet_socket_shutdown(ctx, P->fd); 284 | skynet_error(ctx, "timeout %d", P->fd); 285 | } 286 | } else { 287 | P->heartbeat = P->recv = 0; 288 | skynet_command(ctx, "TIMEOUT", TIMEOUT); 289 | } 290 | } 291 | 292 | static void 293 | send_out(struct skynet_context *ctx, struct package *P, const void *msg, size_t sz) { 294 | if (sz > 0xffff) { 295 | skynet_error(ctx, "package too long (%08x)", (uint32_t)sz); 296 | return; 297 | } 298 | uint8_t *p = skynet_malloc(sz + 2); 299 | p[0] = (sz & 0xff00) >> 8; 300 | p[1] = sz & 0xff; 301 | memcpy(p+2, msg, sz); 302 | skynet_socket_send(ctx, P->fd, p, sz+2); 303 | } 304 | 305 | static int 306 | message_handler(struct skynet_context * ctx, void *ud, int type, int session, uint32_t source, const void * msg, size_t sz) { 307 | struct package *P = ud; 308 | switch (type) { 309 | case PTYPE_TEXT: 310 | command(ctx, P, session, source, msg, sz); 311 | break; 312 | case PTYPE_CLIENT: 313 | send_out(ctx, P, msg, sz); 314 | break; 315 | case PTYPE_RESPONSE: 316 | // It's timer 317 | heartbeat(ctx, P); 318 | break; 319 | case PTYPE_SOCKET: 320 | socket_message(ctx, P, msg); 321 | break; 322 | case PTYPE_ERROR: 323 | // ignore error 324 | break; 325 | default: 326 | if (session > 0) { 327 | // unsupport type, raise error 328 | skynet_send(ctx, 0, source, PTYPE_ERROR, session, NULL, 0); 329 | } 330 | break; 331 | } 332 | return 0; 333 | } 334 | 335 | struct package * 336 | package_create(void) { 337 | struct package * P = skynet_malloc(sizeof(*P)); 338 | memset(P, 0, sizeof(*P)); 339 | P->heartbeat = -1; 340 | P->uncomplete_sz = -1; 341 | queue_init(&P->request, sizeof(struct request)); 342 | queue_init(&P->response, sizeof(struct response)); 343 | return P; 344 | } 345 | 346 | void 347 | package_release(struct package *P) { 348 | while (!queue_empty(&P->response)) { 349 | // drop the message 350 | struct response resp; 351 | queue_pop(&P->response, &resp); 352 | skynet_free(resp.msg); 353 | } 354 | if (P->uncomplete_sz >= 0) 355 | skynet_free(P->uncomplete.msg); 356 | queue_exit(&P->request); 357 | queue_exit(&P->response); 358 | skynet_free(P); 359 | } 360 | 361 | int 362 | package_init(struct package * P, struct skynet_context *ctx, const char * param) { 363 | int n = sscanf(param ? param : "", "%u %d", &P->manager, &P->fd); 364 | if (n != 2 || P->manager == 0 || P->fd == 0) { 365 | skynet_error(ctx, "Invalid param [%s]", param); 366 | return 1; 367 | } 368 | skynet_socket_start(ctx, P->fd); 369 | skynet_socket_nodelay(ctx, P->fd); 370 | heartbeat(ctx, P); 371 | skynet_callback(ctx, P, message_handler); 372 | 373 | return 0; 374 | } 375 | -------------------------------------------------------------------------------- /test/client.lua: -------------------------------------------------------------------------------- 1 | if _VERSION ~= "Lua 5.3" then 2 | error "Use lua 5.3" 3 | end 4 | 5 | package.cpath = (os.getenv "HOME") .. "/skynet/luaclib/?.so" 6 | 7 | local socket = require "clientsocket" 8 | local fd = assert(socket.connect("127.0.0.1", 8888)) 9 | 10 | local function send_package(fd, pack) 11 | local package = string.pack(">s2", pack) 12 | socket.send(fd, package) 13 | end 14 | 15 | local function unpack_package(text) 16 | local size = #text 17 | if size < 2 then 18 | return nil, text 19 | end 20 | local s = text:byte(1) * 256 + text:byte(2) 21 | if size < s+2 then 22 | return nil, text 23 | end 24 | 25 | return text:sub(3,2+s), text:sub(3+s) 26 | end 27 | 28 | local function recv_package(last) 29 | local result 30 | result, last = unpack_package(last) 31 | if result then 32 | return result, last 33 | end 34 | local r = socket.recv(fd) 35 | if not r then 36 | return nil, last 37 | end 38 | if r == "" then 39 | error "Server closed" 40 | end 41 | return unpack_package(last .. r) 42 | end 43 | 44 | local last = "" 45 | 46 | local function dispatch_package() 47 | while true do 48 | local v 49 | v, last = recv_package(last) 50 | if not v then 51 | break 52 | end 53 | 54 | print(v) 55 | end 56 | end 57 | 58 | local function recv_package(last) 59 | local result 60 | result, last = unpack_package(last) 61 | if result then 62 | return result, last 63 | end 64 | local r = socket.recv(fd) 65 | if not r then 66 | return nil, last 67 | end 68 | if r == "" then 69 | error "Server closed" 70 | end 71 | return unpack_package(last .. r) 72 | end 73 | 74 | while true do 75 | dispatch_package() 76 | local cmd = socket.readstdin() 77 | if cmd then 78 | send_package(fd,cmd) 79 | else 80 | socket.usleep(100) 81 | end 82 | end 83 | 84 | -------------------------------------------------------------------------------- /test/config: -------------------------------------------------------------------------------- 1 | root = "$HOME/skynet/" 2 | thread = 8 3 | harbor = 0 4 | luaservice = root.."service/?.lua;./service/?.lua;./test/?.lua" 5 | lualoader = root.."lualib/loader.lua" 6 | cpath = root.."cservice/?.so" 7 | lua_path = root.."lualib/?.lua;./lualib/?.lua" 8 | lua_cpath = root.."luaclib/?.so" 9 | start = "main" 10 | -------------------------------------------------------------------------------- /test/main.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local socket = require "socket" 3 | local proxy = require "socket_proxy" 4 | 5 | local function read(fd) 6 | return skynet.tostring(proxy.read(fd)) 7 | end 8 | 9 | skynet.start(function() 10 | skynet.newservice("debug_console",8000) 11 | local id = assert(socket.listen("127.0.0.1", 8888)) 12 | socket.start(id, function (fd, addr) 13 | skynet.error(string.format("%s connected as %d" , addr, fd)) 14 | proxy.subscribe(fd) 15 | while true do 16 | local ok, s = pcall(read, fd) 17 | if not ok then 18 | skynet.error("CLOSE") 19 | break 20 | end 21 | if s == "quit" then 22 | proxy.close(fd) 23 | break 24 | end 25 | skynet.error(s) 26 | end 27 | end) 28 | end) 29 | --------------------------------------------------------------------------------