diff --git a/examples/login/client.lua b/examples/login/client.lua index 36ee3e2a..85a8ab36 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -1,5 +1,18 @@ 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 cjson = require "cjson" local crypt = require "crypt" @@ -43,6 +56,7 @@ local hmac = crypt.hmac64(challenge, secret) socket.writeline(fd, crypt.base64encode(hmac)) local token = { + server = "sample", user = "hello", pass = "password", } @@ -53,6 +67,49 @@ socket.writeline(fd, crypt.base64encode(etoken)) print(readline()) +socket.close(fd) + +----- connect to game server + +local input = {} +local fd = assert(socket.connect("127.0.0.1", 8888)) + +local function readpackage() + local line = table.remove(input, 1) + if line then + return line + end + + while true do + local status + status, last = socket.recv(fd, last, input) + if status == nil then + error "Server closed" + end + if not status then + socket.usleep(100) + else + local line = table.remove(input, 1) + if line then + return line + end + end + end +end + +local index = 0 +local request = 0 +local handshake = string.format("%s@%s:%d:%d", crypt.base64encode(token.user), 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()) + + diff --git a/examples/login/logind.lua b/examples/login/logind.lua index 39a60186..759a046e 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,6 +1,7 @@ local login = require "loginserver" local json = require "cjson" local crypt = require "crypt" +local skynet = require "skynet" local server = { host = "127.0.0.1", @@ -8,19 +9,40 @@ local server = { name = "login_master", } +local server_list = {} +local user_online = {} + function server.auth_handler(token) token = json.decode(token) assert(token.user) assert(token.pass == "password") - return "sample", token.user + return token.server, token.user 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 + local gameserver = server_list[u.server] + skynet.call(gameserver, "lua", "kick", server, uid) + end + local gameserver = assert(server_list[server]) + skynet.call(gameserver, "lua", "login", server, uid, secret) end -function server.logout_handler(server, uid) +local CMD = {} + +function CMD.register_gate(source, name) + server_list[name] = source +end + +function CMD.logout(source, uid, server) print(string.format("%s@%s is logout", uid, server)) end +function server.command_handler(command, source, ...) + local f = assert(CMD[command]) + return f(source, ...) +end + login(server) diff --git a/examples/login/main.lua b/examples/login/main.lua index 493b2af4..6359cb96 100644 --- a/examples/login/main.lua +++ b/examples/login/main.lua @@ -1,5 +1,12 @@ local skynet = require "skynet" skynet.start(function() - skynet.newservice "logind" + local loginserver = skynet.newservice "logind" + local gate = skynet.newservice "msggate" + 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 new file mode 100644 index 00000000..30545c68 --- /dev/null +++ b/examples/login/msgagent.lua @@ -0,0 +1,27 @@ +local skynet = require "skynet" + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, + unpack = skynet.tostring, +} + +local gate + +local CMD = {} + +function CMD.init(source , uid, server) + gate = source + skynet.error(string.format("%s is coming", uid)) +end + +skynet.start(function() + skynet.dispatch("lua", function(_, source, command, ...) + local f = assert(CMD[command]) + skynet.ret(skynet.pack(f(source, ...))) + end) + + skynet.dispatch("client", function(_,_, msg) + skynet.ret(msg) + end) +end) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua new file mode 100644 index 00000000..cdda670a --- /dev/null +++ b/examples/login/msggate.lua @@ -0,0 +1,187 @@ +local skynet = require "skynet" +local gateserver = require "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(base64(uid)@base64(server):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) +end + + +local CMD = {} + +function CMD.login(source, server, uid, secret) + local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) + users[username] = { + server = server, + uid = uid, + secret = secret, + index = 0, + request = 0, + reserved = 0, + response = {}, + } +end + +function CMD.logout(source) + local c = agent[source] + if c then + skynet.call(login_master, "lua", "logout", c.server, 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/gateserver.lua b/lualib/gateserver.lua new file mode 100644 index 00000000..84f33f88 --- /dev/null +++ b/lualib/gateserver.lua @@ -0,0 +1,130 @@ +local skynet = require "skynet" +local netpack = require "netpack" +local socketdriver = require "socketdriver" + +local gateserver = {} + +local socket -- listen socket +local queue -- message queue +local maxclient -- max client +local client_number = 0 +local CMD = setmetatable({}, { __gc = function() netpack.clear(queue) end }) +local nodelay = false + +local connection = {} + +function gateserver.openclient(fd) + if connection[fd] then + socketdriver.start(fd) + end +end + +function gateserver.closeclient(fd) + local c = connection[fd] + if c then + connection[fd] = false + socketdriver.close(fd) + end +end + +function gateserver.start(handler) + assert(handler.message) + assert(handler.connect) + + function CMD.open( source, conf ) + assert(not socket) + local address = conf.address or "0.0.0.0" + local port = assert(conf.port) + maxclient = conf.maxclient or 1024 + nodelay = conf.nodelay + socket = socketdriver.listen(address, port) + socketdriver.start(socket) + if handler.open then + handler.open(source, conf) + end + end + + function CMD.close() + assert(socket) + socketdriver.close(socket) + socket = nil + end + + local MSG = {} + + function MSG.data(fd, msg, sz) + if connection[fd] then + handler.message(fd, msg, sz) + end + end + + function MSG.more() + for fd, msg, sz in netpack.pop, queue do + if connection[fd] then + handler.message(fd, msg, sz) + end + end + end + + function MSG.open(fd, msg) + if client_number >= maxclient then + socketdriver.close(fd) + return + end + if nodelay then + socketdriver.nodelay(fd) + end + connection[fd] = true + client_number = client_number + 1 + handler.connect(fd, msg) + end + + local function close_fd(fd) + local c = connection[fd] + if c ~= nil then + connection[fd] = nil + client_number = client_number - 1 + end + end + + function MSG.close(fd) + if handler.close then + handler.close(fd) + end + close_fd(fd) + end + + function MSG.error(fd, msg) + if handler.error then + handler.error(fd) + end + close_fd(fd) + end + + skynet.register_protocol { + name = "socket", + id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6 + unpack = function ( msg, sz ) + return netpack.filter( queue, msg, sz) + end, + dispatch = function (_, _, q, type, ...) + queue = q + if type then + MSG[type](...) + end + end + } + + skynet.start(function() + skynet.dispatch("lua", function (_, address, cmd, ...) + local f = CMD[cmd] + if f then + skynet.ret(skynet.pack(f(address, ...))) + else + skynet.ret(skynet.pack(handler.command(cmd, address, ...))) + end + end) + end) +end + +return gateserver \ No newline at end of file diff --git a/lualib/loginserver.lua b/lualib/loginserver.lua index 1fff5a66..7f022508 100644 --- a/lualib/loginserver.lua +++ b/lualib/loginserver.lua @@ -1,7 +1,6 @@ local skynet = require "skynet" local socket = require "socket" local crypt = require "crypt" -local datacenter = require "datacenter" local function launch_slave(auth_handler) local cmd = {} @@ -75,32 +74,26 @@ local function launch_master(conf) local port = assert(tonumber(conf.port)) local slave = {} local balance = 1 - local cmd = {} - function cmd.logout(server, uid) - conf.logout_handler(server, uid) - end - - if conf.init then - conf.init() - conf.init = nil - end + skynet.dispatch("lua", function(_,source,command, ...) + if command == "register_slave" then + table.insert(slave, source) + skynet.ret(skynet.pack(nil)) + else + skynet.ret(skynet.pack(conf.command_handler(command, source, ...))) + end + end) for i=1,instance do - slave[i] = skynet.newservice(SERVICE_NAME) + skynet.newservice(SERVICE_NAME) end - skynet.dispatch("lua", function(_,_,command, ...) - local f = assert(cmd[command]) - skynet.ret(skynet.pack(f(...))) - end) - local id = socket.listen(host, port) skynet.error(string.format("login server listen at : %s %d", host, port)) socket.start(id , function(fd, addr) local s = slave[balance] balance = balance + 1 - if balance > instance then + if balance > #slave then balance = 1 end local ok, err = pcall(accept, conf, s, fd, addr) @@ -112,10 +105,11 @@ local function launch_master(conf) end local function login (conf) - local name = conf.name or "login_master" + local name = "." .. (conf.name or "login") skynet.start(function() - local loginmaster = datacenter.get(name) + local loginmaster = skynet.localname(name) if loginmaster then + skynet.call(loginmaster, "lua", "register_slave") local auth_handler = assert(conf.auth_handler) launch_master = nil conf = nil @@ -124,8 +118,8 @@ local function login (conf) launch_slave = nil conf.auth_handler = nil assert(conf.login_handler) - assert(conf.logout_handler) - datacenter.set("login_master", skynet.self()) + assert(conf.command_handler) + skynet.register(name) launch_master(conf) end end) diff --git a/service/gate.lua b/service/gate.lua index 7758175f..665be9d0 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -15,7 +15,6 @@ local handler = {} function handler.open(source, conf) watchdog = conf.watchdog or source - maxclient = conf.maxclient or 1024 end function handler.message(fd, msg, sz)