diff --git a/HISTORY.md b/HISTORY.md index 5926bc02..5c52c767 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,8 @@ +Dev version +----------- +* skynet.exit will quit service immediately. +* Add snax.gateserver, snax.loginserver, snax.msgserver + v0.4.2 (2014-7-14) ----------- * Bugfix : invalid negative socket id diff --git a/examples/login/client.lua b/examples/login/client.lua index 537f614b..82b0a182 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -1,18 +1,5 @@ package.cpath = "luaclib/?.so" ---[[ Status code - -200 OK - -400 Bad Request (通常是登陆协议错误) -401 Unauthorized (通常是登陆服务器或游戏服务器验证错误) -403 Forbidden (通常是连接游戏服务器的 index 已经过期) -404 Not Found (通常是游戏服务器未获得登陆服务器的通知) -406 Not Acceptable (通常是登陆服务器转发游戏服务器拒绝登陆) -412 Precondition Failed (通常是遗漏了和游戏服务器前次通讯的请求) - -]] - local socket = require "clientsocket" local crypt = require "crypt" @@ -72,16 +59,38 @@ local b = crypt.base64encode(etoken) socket.writeline(fd, crypt.base64encode(etoken)) local result = readline() +print(result) local code = tonumber(string.sub(result, 1, 3)) assert(code == 200) socket.close(fd) local subid = crypt.base64decode(string.sub(result, 5)) +print("login ok, subid=", subid) + ----- connect to game server +local session = 0 + +local function send_request(v) + session = session + 1 + local s = string.char(bit32.extract(session,24,8), bit32.extract(session,16,8), bit32.extract(session,8,8), bit32.extract(session,0,8)) + socket.send(fd , v..s) + return session, v +end + +local function recv_response(v) + local content = v:sub(1,-6) + local ok = v:sub(-5,-5):byte() + local session = 0 + for i=-4,-1 do + local c = v:byte(i) + session = session + bit32.lshift(c,(-1-i) * 8) + end + return ok ~=0 , session, content +end + local input = {} -local fd = assert(socket.connect("127.0.0.1", 8888)) local function readpackage() local line = table.remove(input, 1) @@ -106,20 +115,32 @@ local function readpackage() end end -local index = 0 -local request = 0 -local handshake = string.format("%s#%s@%s:%d:%d", crypt.base64encode(token.user), crypt.base64encode(subid),crypt.base64encode(token.server) , index, request) -local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) - -socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) - -print(readpackage()) - -socket.send(fd , "echo") -print(readpackage()) - +local index = 1 +local function echo(text) + print("connect") + local fd = assert(socket.connect("127.0.0.1", 8888)) + input = {} + local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) + local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) + + print(readpackage()) + print("===>",send_request(text)) + print("===>",send_request(text .. " again")) + print("<===",recv_response(readpackage())) + print("<===",recv_response(readpackage())) + + print("disconnect") + socket.close(fd) +end + +echo "hello" + +index = index + 1 + +echo "world" diff --git a/examples/login/gated.lua b/examples/login/gated.lua new file mode 100644 index 00000000..aeaa2013 --- /dev/null +++ b/examples/login/gated.lua @@ -0,0 +1,88 @@ +local msgserver = require "snax.msgserver" +local crypt = require "crypt" +local skynet = require "skynet" + +local loginservice = tonumber(...) + +local server = {} +local users = {} +local username_map = {} +local internal_id = 0 + +-- login server disallow multi login, so login_handler never be reentry +-- call by login server +function server.login_handler(uid, secret) + if users[uid] then + error(string.format("%s is already login", uid)) + end + + internal_id = internal_id + 1 + local username = msgserver.username(uid, internal_id, servername) + + -- you can use a pool to alloc new agent + local agent = skynet.newservice "msgagent" + local u = { + username = username, + agent = agent, + uid = uid, + subid = internal_id, + } + + -- trash subid (no used) + skynet.call(agent, "lua", "login", uid, internal_id, secret) + + users[uid] = u + username_map[username] = u + + msgserver.login(username, secret) + + -- you should return unique subid + return internal_id +end + +-- call by agent +function server.logout_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + msgserver.logout(u.username) + users[uid] = nil + username_map[u.username] = nil + skynet.call(loginservice, "lua", "logout",uid, subid) + end +end + +-- call by login server +function server.kick_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + -- NOTICE: logout may call skynet.exit, so you should use pcall. + pcall(skynet.call, u.agent, "lua", "logout") + end +end + +-- call by self (when socket disconnect) +function server.disconnect_handler(username) + local u = username_map[username] + if u then + skynet.call(u.agent, "lua", "afk") + end +end + +-- call by self (when recv a request from client) +function server.request_handler(username, msg, sz) + local u = username_map[username] + return skynet.tostring(skynet.rawcall(u.agent, "client", msg, sz)) +end + +-- call by self (when gate open) +function server.register_handler(name) + servername = name + skynet.call(loginservice, "lua", "register_gate", servername, skynet.self()) +end + +msgserver.start(server) + diff --git a/examples/login/logind.lua b/examples/login/logind.lua index df7f5fd8..e6e4c10f 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,26 +1,17 @@ -local login = require "gamefw.loginserver" +local login = require "snax.loginserver" local crypt = require "crypt" local skynet = require "skynet" local server = { host = "127.0.0.1", port = 8001, + multilogin = false, -- disallow multilogin name = "login_master", } local server_list = {} local user_online = {} - -local server_mt = {} -server_mt.__index = server_mt - -function server_mt:kick(uid) - return skynet.call(self.address, "lua", "kick", self.name, uid) -end - -function server_mt:login(uid, secret) - return skynet.call(self.address, "lua", "login", self.name, uid, secret) -end +local user_login = {} function server.auth_handler(token) -- the token is base64(user)@base64(server):base64(password) @@ -34,27 +25,31 @@ end function server.login_handler(server, uid, secret) print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) - local u = user_online[uid] - if u then - u:kick(uid) - end - assert(user_online[uid] == nil, "kick failed") local gameserver = assert(server_list[server], "Unknown server") - local subid = gameserver:login(uid, secret) - user_online[uid] = gameserver - return tostring(subid) + -- only one can login, because disallow multilogin + local last = user_online[uid] + if last then + skynet.call(last.address, "lua", "kick", uid, last.subid) + end + if user_online[uid] then + error(string.format("user %s is already online", uid)) + end + + local subid = tostring(skynet.call(gameserver, "lua", "login", uid, secret)) + user_online[uid] = { address = gameserver, subid = subid , server = server} + return subid end local CMD = {} function CMD.register_gate(server, address) - server_list[server] = setmetatable( { name = server, address = address }, server_mt ) + server_list[server] = address end -function CMD.logout(uid) +function CMD.logout(uid, subid) local u = user_online[uid] if u then - print(string.format("%s@%s is logout", uid, u.name)) + print(string.format("%s@%s is logout", uid, u.server)) user_online[uid] = nil end end diff --git a/examples/login/main.lua b/examples/login/main.lua index 6359cb96..1eef6f4d 100644 --- a/examples/login/main.lua +++ b/examples/login/main.lua @@ -1,12 +1,12 @@ local skynet = require "skynet" skynet.start(function() - local loginserver = skynet.newservice "logind" - local gate = skynet.newservice "msggate" + local loginserver = skynet.newservice("logind") + local gate = skynet.newservice("gated", loginserver) + skynet.call(gate, "lua", "open" , { port = 8888, maxclient = 64, - loginserver = loginserver, servername = "sample", }) end) diff --git a/examples/login/msgagent.lua b/examples/login/msgagent.lua index 30545c68..90b4ac08 100644 --- a/examples/login/msgagent.lua +++ b/examples/login/msgagent.lua @@ -7,21 +7,46 @@ skynet.register_protocol { } local gate +local userid, subid local CMD = {} -function CMD.init(source , uid, server) +function CMD.login(source, uid, sid, secret) + -- you may use secret to make a encrypted data stream + skynet.error(string.format("%s is login", uid)) gate = source - skynet.error(string.format("%s is coming", uid)) + userid = uid + subid = sid + -- you may load user data from database +end + +local function logout() + if gate then + skynet.call(gate, "lua", "logout", userid, subid) + end + skynet.exit() +end + +function CMD.logout(source) + -- NOTICE: The logout MAY be reentry + skynet.error(string.format("%s is logout", userid)) + logout() +end + +function CMD.afk(source) + -- the connection is broken, but the user may back + skynet.error(string.format("AFK")) end skynet.start(function() - skynet.dispatch("lua", function(_, source, command, ...) + -- If you want to fork a work thread , you MUST do it in CMD.login + skynet.dispatch("lua", function(session, source, command, ...) local f = assert(CMD[command]) skynet.ret(skynet.pack(f(source, ...))) end) skynet.dispatch("client", function(_,_, msg) + -- the simple ehco service skynet.ret(msg) end) end) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua deleted file mode 100644 index 32b3518f..00000000 --- a/examples/login/msggate.lua +++ /dev/null @@ -1,189 +0,0 @@ -local skynet = require "skynet" -local gateserver = require "gamefw.gateserver" -local netpack = require "netpack" -local crypt = require "crypt" -local socketdriver = require "socketdriver" -local datacenter = require "datacenter" - -local users = {} - -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, -} - -local handler = {} -local handshake = {} -local connection = {} -local agent = {} -local login_master - --- launch an agent service to handle message from client -local function launch_agent(c) - local agent = assert(skynet.newservice "msgagent") - skynet.call(agent, "lua", "init", c.uid, c.server) - return agent -end - -function handler.connect(fd, addr) - skynet.error(string.format("new connection from %s (fd=%d)", addr, fd)) - handshake[fd] = true - gateserver.openclient(fd) -end - -local function auth(fd, msg, sz) --- base64(uid)@base64(server)#base64(subid):index:request:base64(hmac) - local message = netpack.tostring(msg, sz) - local username, index, request , hmac = string.match(message, "([^:]*):([^:]*):([^:]*):([^:]*)") - local content = users[username] - if content == nil then - return "404 Not Found" - end - local idx = assert(tonumber(index)) - local req = assert(tonumber(request)) - hmac = crypt.base64decode(hmac) - - if idx < content.index then - return "403 Forbidden" - end - if req > content.request or req < content.reserved then - return "412 Precondition Failed" - end - - local text = string.format("%s:%s:%s", username, index, request) - local v = crypt.hmac64(crypt.hashkey(text), content.secret) - if v ~= hmac then - return "401 Unauthorized" - end - - content.index = idx - for i=content.reserved, request do - content.response[i] = nil - end - content.reserved = request - content.reqest = request - connection[fd] = content - - if content.fd then - local last_fd = content.fd - connection[last_fd] = nil - gateserver.closeclient(last_fd) - end - content.fd = fd - - if content.agent == nil then - content.agent = true - local ok, agent = pcall(launch_agent, content) - if ok then - content.agent = agent - else - content.agent = nil - skynet.error(string.format("Launch agent %s failed : %s", content.uid, agent)) - connection[fd] = nil - gateserver.closeclient(fd) - end - end -end - -function handler.message(fd, msg, sz) - if handshake[fd] then - local ok, result = pcall(auth, fd, msg, sz) - if not ok then - result = "400 Bad Request" - end - - local close = result ~= nil - - if result == nil then - result = "200 OK" - end - - socketdriver.send(fd, netpack.pack(result)) - - if close then - gateserver.closeclient(fd) - end - handshake[fd] = nil - else - local c = connection[fd] - if c == nil or c.agent == nil then - local message = netpack.tostring(msg, sz) - skynet.error(string.format("Unknown fd = %d, message (%s) size = %d", fd, crypt.hexencode(message):sub(1,80), #message)) - return - end - c.request = c.request + 1 - local ret = c.response[c.request] - if ret == nil then - local ok, msg, sz = pcall(skynet.rawcall, c.agent, "client", msg, sz) - if ok then - ret = netpack.pack_string(msg, sz) - else - skynet.error(string.format("%s request error : %s", c.username, msg)) - ret = netpack.pack_string "" - end - c.response[c.request] = ret - end - socketdriver.send(c.fd, ret) - end -end - -function handler.close(fd) - handshake[fd] = nil - local c = connection[fd] - if c then - c.fd = nil - end -end - -handler.error = handler.close - -function handler.open(source, conf) - login_master = assert(conf.loginserver) - local servername = assert(conf.servername) - skynet.call(login_master, "lua", "register_gate", servername, skynet.self()) -end - - -local CMD = {} - -function CMD.login(source, server, uid, secret) - local subid = "1" - local username = crypt.base64encode(uid) .. '#'..crypt.base64encode(subid)..'@' .. crypt.base64encode(server) - users[username] = { - server = server, - uid = uid, - secret = secret, - index = 0, - request = 0, - reserved = 0, - response = {}, - } - return subid -end - -function CMD.logout(source) - local c = agent[source] - if c then - skynet.call(login_master, "lua", "logout", c.uid) - if c.fd then - gateserver.closeclient(c.fd) - end - end -end - -function CMD.kick(source, server, uid) - local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) - local u = users[username] - if u and u.agent then - skynet.call(u.agent, "logout") - skynet.kill(u.agent) - u.agent = nil - end -end - -function handler.command(cmd, source, ...) - local f = assert(CMD[cmd]) - return f(source, ...) -end - -gateserver.start(handler) diff --git a/lualib-src/lua-netpack.c b/lualib-src/lua-netpack.c index 432818e7..4012c9bc 100644 --- a/lualib-src/lua-netpack.c +++ b/lualib-src/lua-netpack.c @@ -457,9 +457,9 @@ lpack_padding(lua_State *L) { size_t content_sz; uint8_t *buffer; const char * ptr = tolstring(L, &content_sz, 2); - size_t header_sz = 0; - const char * header = luaL_checklstring(L,1,&header_sz); - size_t len = header_sz + content_sz; + size_t cookie_sz = 0; + const char * cookie = luaL_checklstring(L,1,&cookie_sz); + size_t len = cookie_sz + content_sz; if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); @@ -472,8 +472,8 @@ lpack_padding(lua_State *L) { } write_size(buffer, len); - memcpy(buffer+2, header, header_sz); - memcpy(buffer+2+header_sz, ptr, content_sz); + memcpy(buffer+2, ptr, content_sz); + memcpy(buffer+2+content_sz, cookie, cookie_sz); lua_pushlstring(L, (const char *)buffer, len+2); return 1; @@ -486,8 +486,19 @@ ltostring(lua_State *L) { if (ptr == NULL) { lua_pushliteral(L, ""); } else { - lua_pushlstring(L, (const char *)ptr, size); - skynet_free(ptr); + if (lua_isnumber(L, 3)) { + int offset = lua_tointeger(L, 3); + if (offset < 0) { + return luaL_error(L, "Invalid offset %d", offset); + } + if (offset > size) { + offset = size; + } + lua_pushlstring(L, (const char *)ptr + offset, size-offset); + } else { + lua_pushlstring(L, (const char *)ptr, size); + skynet_free(ptr); + } } return 1; } diff --git a/lualib/skynet.lua b/lualib/skynet.lua index 2b984e2a..5e8e95b0 100644 --- a/lualib/skynet.lua +++ b/lualib/skynet.lua @@ -127,7 +127,7 @@ function suspend(co, result, command, param, size) if not result then local session = session_coroutine_id[co] local addr = session_coroutine_address[co] - if session and session ~= 0 then + if session then c.send(addr, skynet.PTYPE_ERROR, session, "") end session_coroutine_id[co] = nil @@ -151,6 +151,9 @@ function suspend(co, result, command, param, size) -- coroutine exit session_coroutine_id[co] = nil session_coroutine_address[co] = nil + elseif command == "QUIT" then + -- service exit + return else error("Unknown command : " .. command .. "\n" .. debug.traceback(co)) end @@ -264,6 +267,8 @@ function skynet.exit() end end c.command("EXIT") + -- quit service + coroutine_yield "QUIT" end function skynet.kill(name) diff --git a/lualib/gamefw/gateserver.lua b/lualib/snax/gateserver.lua similarity index 96% rename from lualib/gamefw/gateserver.lua rename to lualib/snax/gateserver.lua index 84f33f88..fc019143 100644 --- a/lualib/gamefw/gateserver.lua +++ b/lualib/snax/gateserver.lua @@ -40,7 +40,7 @@ function gateserver.start(handler) socket = socketdriver.listen(address, port) socketdriver.start(socket) if handler.open then - handler.open(source, conf) + return handler.open(source, conf) end end @@ -88,8 +88,8 @@ function gateserver.start(handler) end function MSG.close(fd) - if handler.close then - handler.close(fd) + if handler.disconnect then + handler.disconnect(fd) end close_fd(fd) end diff --git a/lualib/gamefw/loginserver.lua b/lualib/snax/loginserver.lua similarity index 74% rename from lualib/gamefw/loginserver.lua rename to lualib/snax/loginserver.lua index a7384105..17cb131d 100644 --- a/lualib/gamefw/loginserver.lua +++ b/lualib/snax/loginserver.lua @@ -1,6 +1,35 @@ local skynet = require "skynet" local socket = require "socket" local crypt = require "crypt" +local table = table +local string = string +local assert = assert + +--[[ + +Protocol: + + line (\n) based text protocol + + 1. Server->Client : base64(8bytes random challenge) + 2. Client->Server : base64(8bytes handshake client key) + 3. Server: Gen a 8bytes handshake server key + 4. Server->Client : base64(DH-Exchange(server key)) + 5. Server/Client secret := DH-Secret(client key/server key) + 6. Client->Server : base64(HMAC(challenge, secret)) + 7. Client->Server : DES(secret, base64(token)) + 8. Server : call auth_handler(token) -> server, uid (A user defined method) + 9. Server : call login_handler(server, uid, secret) (A user defined method) + +Error Code: + 400 Bad Request . challenge failed + 401 Unauthorized . unauthorized by auth_handler + 403 Forbidden . login_handler failed + 406 Not Acceptable . already in login (disallow multi login) + +Success: + 200 base64(subid) +]] local function write(fd, text) assert(socket.write(fd, text), "socket error") @@ -51,6 +80,8 @@ local function launch_slave(auth_handler) end) end +local user_login = {} + local function accept(conf, s, fd, addr) -- call slave auth local ok, server, uid, secret = skynet.call(s, "lua", fd, addr) @@ -61,12 +92,24 @@ local function accept(conf, s, fd, addr) error(server) end + if not conf.multilogin then + if user_login[uid] then + write(fd, "406 Not Acceptable\n") + error(string.format("User %s is already login", uid)) + end + + user_login[uid] = true + end + local ok, err = pcall(conf.login_handler, server, uid, secret) + -- unlock login + user_login[uid] = nil + if ok then err = err or "" write(fd, "200 "..crypt.base64encode(err).."\n") else - write(fd, "406 Not Acceptable\n") + write(fd, "403 Forbidden\n") error(err) end end @@ -92,8 +135,8 @@ local function launch_master(conf) skynet.newservice(SERVICE_NAME) end - local id = socket.listen(host, port) skynet.error(string.format("login server listen at : %s %d", host, port)) + local id = socket.listen(host, port) socket.start(id , function(fd, addr) local s = slave[balance] balance = balance + 1 diff --git a/lualib/snax/msgserver.lua b/lualib/snax/msgserver.lua new file mode 100644 index 00000000..d1a84f3a --- /dev/null +++ b/lualib/snax/msgserver.lua @@ -0,0 +1,299 @@ +local skynet = require "skynet" +local gateserver = require "snax.gateserver" +local netpack = require "netpack" +local crypt = require "crypt" +local socketdriver = require "socketdriver" +local assert = assert +local b64encode = crypt.base64encode +local b64decode = crypt.base64decode + +--[[ + +Protocol: + + All the number type is big-endian + + Shakehands (The first package) + + Client -> Server : + + base64(uid)@base64(server)#base64(subid):index:base64(hmac) + + Server -> Client + + XXX ErrorCode + 404 User Not Found + 403 Index Expired + 401 Unauthorized + 400 Bad Request + 200 OK + + Req-Resp + + Client -> Server : Request + word size (Not include self) + string content (size-4) + dword session + + Server -> Client : Response + word size (Not include self) + string content (size-5) + byte ok (1 is ok, 0 is error) + dword session + +API: + server.userid(username) + return uid, subid, server + + server.username(uid, subid, server) + return username + + server.login(username, secret) + update user secret + + server.logout(username) + user logout + + server.ip(username) + return ip when connection establish, or nil + + server.start(conf) + start server + +Supported skynet command: + kick username (may used by loginserver) + login username secret (used by loginserver) + logout username (used by agent) + +Config for server.start: + conf.expired_number : the number of the response message cached after sending out (default is 128) + conf.login_handler(uid, secret) -> subid : the function when a new user login, alloc a subid for it. (may call by login server) + conf.logout_handler(uid, subid) : the functon when a user logout. (may call by agent) + conf.kick_handler(uid, subid) : the functon when a user logout. (may call by login server) + conf.request_handler(username, session, msg, sz) : the function when recv a new request. + conf.register_handler(servername) : call when gate open + conf.disconnect_handler(username) : call when a connection disconnect (afk) +]] + +local server = {} + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local user_online = {} +local handshake = {} +local connection = {} + +function server.userid(username) + -- base64(uid)@base64(server)#base64(subid) + local uid, servername, subid = username:match "([^@]*)@([^#]*)#(.*)" + return b64decode(uid), b64decode(subid), b64decode(servername) +end + +function server.username(uid, subid, servername) + return string.format("%s@%s#%s", b64encode(uid), b64encode(servername), b64encode(tostring(subid))) +end + +function server.logout(username) + local u = user_online[username] + user_online[username] = nil + if u.fd then + gateserver.closeclient(u.fd) + connection[u.fd] = nil + end +end + +function server.login(username, secret) + assert(user_online[username] == nil) + user_online[username] = { + secret = secret, + version = 0, + index = 0, + username = username, + response = {}, -- response cache + } +end + +function server.ip(username) + local u = user_online[username] + if u and u.fd then + return u.ip + end +end + +function server.start(conf) + local expired_number = conf.expired_number or 128 + + local handler = {} + + local CMD = { + login = assert(conf.login_handler), + logout = assert(conf.logout_handler), + kick = assert(conf.kick_handler), + } + + function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(...) + end + + function handler.open(source, gateconf) + local servername = assert(gateconf.servername) + return conf.register_handler(servername) + end + + function handler.connect(fd, addr) + handshake[fd] = addr + gateserver.openclient(fd) + end + + function handler.disconnect(fd) + handshake[fd] = nil + local c = connection[fd] + if c then + c.fd = nil + connection[fd] = nil + if conf.disconnect_handler then + conf.disconnect_handler(c.username) + end + end + end + + handler.error = handler.disconnect + + -- atomic , no yield + local function do_auth(fd, message, addr) + local username, index, hmac = string.match(message, "([^:]*):([^:]*):([^:]*)") + local u = user_online[username] + if u == nil then + return "404 User Not Found" + end + local idx = assert(tonumber(index)) + hmac = b64decode(hmac) + + if idx <= u.version then + return "403 Index Expired" + end + + local text = string.format("%s:%s", username, index) + local v = crypt.hmac64(crypt.hashkey(text), u.secret) + if v ~= hmac then + return "401 Unauthorized" + end + + u.version = idx + u.fd = fd + u.ip = addr + connection[fd] = u + end + + local function auth(fd, addr, msg, sz) + local message = netpack.tostring(msg, sz) + local ok, result = pcall(do_auth, fd, message, addr) + if not ok then + skynet.error(result) + result = "400 Bad Request" + end + + local close = result ~= nil + + if result == nil then + result = "200 OK" + end + + socketdriver.send(fd, netpack.pack(result)) + + if close then + gateserver.closeclient(fd) + end + end + + local request_handler = assert(conf.request_handler) + + -- u.response is a struct { message, version, index } + local function retire_response(u) + if u.index >= expired_number * 2 then + local max = 0 + local response = u.response + for k,p in pairs(response) do + if p[3] < expired_number then + response[k] = nil + else + p[3] = p[3] - expired_number + if p[3] > max then + max = p[3] + end + end + end + u.index = max + 1 + end + end + + local function do_request(fd, msg, sz) + local u = assert(connection[fd], "invalid fd") + local msg_sz = sz - 4 + local session = netpack.tostring(msg, sz, msg_sz) + local p = u.response[session] + if p then + -- session can be reuse in the same connection + if p[2] == u.version then + u.response[session] = nil + p = nil + end + end + + if p == nil then + local ok, result = pcall(conf.request_handler, u.username, msg, msg_sz) + -- NOTICE: YIELD here, socket may close. + if not ok then + skynet.error(result) + result = "\0" .. session + else + result = result .. '\1' .. session + end + + p = { netpack.pack_string(result), u.version, u.index } + if u.response[session] then + skynet.error(string.format("Conflict session %s", crypt.hexencode(session))) + end + u.response[session] = p + else + netpack.tostring(msg, sz) -- request before, so free msg + -- resend response, and update index p[3]. + p[3] = u.index + end + u.index = u.index + 1 + -- check connect again + if connection[fd] then + socketdriver.send(fd, p[1]) + end + retire_response(u) + end + + local function request(fd, msg, sz) + local ok, err = pcall(do_request, fd, msg, sz) + -- not atomic, may yield + if not ok then + skynet.error(string.format("Invalid package %s : %s", err, netpack.tostring(msg, sz))) + if connection[fd] then + gateserver.closeclient(fd) + end + end + end + + function handler.message(fd, msg, sz) + local addr = handshake[fd] + if addr then + auth(fd,addr,msg,sz) + handshake[fd] = nil + else + request(fd, msg, sz) + end + end + + return gateserver.start(handler) +end + +return server diff --git a/service/gate.lua b/service/gate.lua index e1f6f1d8..911a126a 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -1,5 +1,5 @@ local skynet = require "skynet" -local gateserver = require "gamefw.gateserver" +local gateserver = require "snax.gateserver" local netpack = require "netpack" local watchdog @@ -53,7 +53,7 @@ local function close_fd(fd) end end -function handler.close(fd) +function handler.disconnect(fd) close_fd(fd) skynet.send(watchdog, "lua", "socket", "close", fd) end @@ -89,15 +89,6 @@ function CMD.accept(source, fd) end function CMD.kick(source, fd) - local c - if fd then - c = connection[fd] - else - c = forwarding[source] - end - - assert(c) - gateserver.closeclient(fd) end