【Skynet 入门实战练习】实现网关服务 | 用户代理 | RPC 协议 | 客户端

文章目录

  • 前言
    • 网关服务
    • RPC 协议
    • 看门狗服务
    • 代理服务
    • 客户端
    • 逻辑梳理

前言

上两章学习了如何搭建一个项目,简单实现了几个基础模块。本章节会实现基本的客户端与服务端的通信,包括网关(gate)、看门狗(watchdog)、代理(agent)三个重要的服务,以及客户端的实现等。

网关服务

参考:websocket-gate 实现网关服务

一般客户端连接服务器选用长链接模式,skynet 支持 TCPwebsocket,我们采用 websocket 的连接方式。

网关负责客户端的网络连接,通过 websocket 和客户端交换数据。我们可以通过普通服务创建方式来创建一个 gate 服务,但这个服务启动后,并不是马上开始工作,需要发一个 lua 消息 open,告诉 gate 监听的端口、最大连接数、延时等信息。

网关服务 service/ws_gate.lua 需要的基本接口:

local CMD = {} -- gate 服务接口
local handler = {} -- websocket 操作接口function handler.connect(fd)
end function handler.handshake(fd, header, url) 
end function handler.message(fd, msg)
end function handler.ping(fd)
endfunction handler.pong(fd)
endfunction handler.close(fd, code, reason)
endfunction handler.error(fd)
endfunction handler.warning(fd, size)
endfunction CMD.open(source, conf) 
end skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,
}skynet.start(function()skynet.dispatch("lua", function(session, source, cmd, ...)local f = CMD[cmd]if not f then skynet.ret(skynet.pack({ok=false}))return end if session == 0 then f(source, ...)else skynet.ret(skynet.pack(f(source, ...)))end end)skynet.register(".ws_gate")
end)

CMD 是一个服务的 lua 消息回调函数表,gate 服务会注册 (dispatch) 相关的 lua 消息,其他服务与 gate 通信,那么就会去到 CMD 中查找相关的处理函数,并根据调用方式 skynet.callskynet.send 做相关的数据返回。例如 if session == 0 then 即判断是 skynet.send 调用就只需要执行即可无需返回。而是 skynet.call 调用方式,通过 skynet.ret(skynet.pack()) 进行消息打包返回给调用方。

来看具体的 CMD.open 实现:

-- call by ws_watchdog(start)
function CMD.open(source, conf) WATCHDOG = conf.watchdog or source MAXCLIENT = conf.maxclient or 1024nodelay = conf.nodelaylocal protocol = conf.protocol or "ws"local port = assert(conf.port)local address = conf.address or "0.0.0.0"local fd = socket.listen(address, port)logger.info(SERVICE_NAME, string.format("Listen websocket port: %s protocol: %s", port, protocol))socket.start(fd, function(fd, addr) logger.info(SERVICE_NAME, string.format("accept client socket_fd: %s addr: %s", fd, addr))websocket.accept(fd, handler, protocol, addr)end)
end 

该方法是由 ws_watchdog 服务调用,方法中我们会获取到 watchdog 的地址,最大客户端连接数,TCP 是否延迟,通信协议 protocol,以及网关需要监听的地址 address 端口 port

-- main.lua
-- 通知 ws_watchdog 启动服务
skynet.call(ws_watchdog, "lua", "start", {port = watchdog_port, maxclient = max_online_client,nodelay = true, protocol = ws_protocol,
})-- ws_watchdog.lua
function CMD.start(conf)-- 开启 gate 服务skynet.call(GATE, "lua", "open", conf)
end

对于服务器,通常我们需要监听一个端口,并转发某个接入连接的处理权。那么可以用如下 API :

socket.listen(address, port) 监听一个端口,返回一个 id ,供 start 使用。
socket.start(id , accept) accept 是一个函数。每当一个监听的 id 对应的 socket 上有连接接入的时候,都会调用 accept 函数。这个函数会得到接入连接的 id 以及 ip 地址。你可以做后续操作。

socket 的 id 对于整个 skynet 节点都是公开的。也就是说,你可以把 id 这个数字通过消息发送给其它服务,其他服务也可以去操作它。任何一个服务只有在调用 socket.start(id) 之后,才可以收到这个 socket 上的数据。

handler 是一个 websocket 协议的接口表,需要有 connect / handshake / message / ping / pong / close / error这些接口方法实现。使用 websocket.accept 监听端口时,需要传入这个表,以供对上行的 socket 消息进行分别处理。该方法的源码地址:websocket.accept,每来一个连接执行一次 accept,内部使用 socket.start 获取客户端上行数据,并且执行 xpcall(resolve_accept, ...) 调用。在函数 resolve_accept 中,会先执行 connecthandshake 这两个注册在 handler 表中的方法,然后循环去读取上行的数据,对应执行其他的方法。

在这里插入图片描述

如上图,一个客户端连入,网关会监听到,并执行 handler.connecthandler.handshake 这两个方法。

来看一下 handler.connect

function handler.connect(fd)logger.debug(SERVICE_NAME, "ws connect from: ", tostring(fd))if client_number >= MAXCLIENT then socketdriver.close(fd)return end if nodelay then socketdriver.nodelay(fd)end client_number = client_number + 1local addr = websocket.addrinfo(fd)local c = {fd = fd,ip = addr, }connection[fd] = cskynet.send(WATCHDOG, "lua", "socket", "open", fd, addr)
end 

网关会控制当前连入的客户端数量,超过就不再授入连接,并且会对每个连接都设置 nodelay 属性,确保数据以小包的形式实时发送,在服务端无需额外对上行的数据进行 TCP 的拆包操作。详细的 nodelay 知识补充参考:浅谈tcp_nodelay的作用

网关服务还应该维护着客户端的连接,connection 表通过 fd 映射每一个客户端连接,同时在客户端下线,也应该清理对应的连接。

连接处理函数的最后一行还执行了一次向 ws_watchdog 服务发送的 socket消息,通知看门狗当前这个新连接的连入做相应处理。因为网关服务只是做一个通信层面的,负责客户端上行数据的转发,不做太多的逻辑处理。而客户端上行数据的逻辑处理主要就交给代理服务 ws_agent。本项目没有单独开一个登陆注册的服务,对这方面没有太复杂的需求,所以简单的登录注册逻辑就交给看门狗服务了。


客户端通信的逻辑:

client -> websocket.connect -> gate(handler.connect) -> watchdog(SOCKET.data) -> agent(login)

客户端通过 websocket.connect 连接 gategate 通知(openwatchdog 为该连接设置定时器 timer,在时限之内,客户端需要发送登录请求,消息被 gatehandler.message 收到,发现该连接没有绑定代理 agent,消息就发往 watchdog,执行登录流程,验证成功则调用 agentlogin 方法,通知网关绑定上代理。否则消息发往 agent,执行相应逻辑处理。

下面来看 handler.message 方法:

function handler.message(fd, msg)logger.debug(SERVICE_NAME, "ws message from: ", tostring(fd), ", msg: ", msg)-- recv a package, forward itlocal c = connection[fd]local agent = c and c.agent if agent then -- msg is stringskynet.redirect(agent, c.client, "client", fd, msg)elseskynet.send(WATCHDOG, "lua", "socket", "data", fd, msg)end 
end 

客户端通过 websocket 发送上来的数据 msg 是一个 lua 字符串,在 message 中进行消息分发。如果连接绑定了代理就以 client 消息类型转发 redirect 到代理处理,否则发送 socket 类型消息给看门狗。

skynet.redirect(addr, source, type, ...):伪装成 source 地址,向 addr 发送一个消息。只有注册了 client 消息,才能使用 skynet.redirect 来发送 client 消息。

我们在 gate 服务中注册了 client 消息,专门用来将接收到的网络数据转发给 agent。不需要解包,也不需要打包。

skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,
}

网关服务完整代码:ws_gate.lua


RPC 协议

客户端和服务器交互的协议采用 JSON 格式,第三方工具:lua-cjson。

客户端发给服务端协议名前缀 c2s,服务端返回给客户端协议名前缀 s2c。协议的 ID 使用字段 pid 表示对应要处理的协议函数名。

例如协议 login 和 协议 heartbeat

客户端:

{pid = "c2s_heartbeat",
}{pid = "c2s_login",token = "token",acc = "account",sign = "checksum",
}

服务端:

{pid = "s2c_login",uid = "user id",msg = "Login success"
}

本项目通信设计的 RPC 协议,需要 pid 和其他参数字段。


接下来需要设计服务端和用户端的通信模块,服务端逻辑处理模块都放在 module 文件夹下,ws_agent/mng.lua 对应代理的服务逻辑处理模块,ws_watchdog/mng.lua 对应看门狗服务对应的逻辑处理模块。

这里我们先关注这两个模块的 RPC 逻辑:

ws_watchdog/mng.lua

local _M = {} -- 模块接口
local RPC = {} -- RPC 协议接口local function check_sign(token, acc, sign)local checkstr = token .. acc local checksum = md5.sumhexa(checkstr)if checksum == sign then return true end return false 
end -- 登录协议处理函数
function RPC.c2s_login(req, fd)-- token 验证if not check_sign(req.token, req.acc, req.sign) then _M.close_fd(fd)return end -- 登录成功,分配代理,移除超时队列local res = skynet.call(AGENT, "lua", "login", req.acc, fd)noauth_fds[fd] = nil return res 
end-- 协议根据 pid 执行对应函数
function _M.handle_proto(req, fd)local f = RPC[req.pid]local res = f(req, fd)return res 
endreturn _M  

上面谈及,看门狗服务我们会用来处理用户的登录逻辑。模块中 handle_proto 函数作为 RPC 协议处理的入口,根据 req.pid 对应到服务端应该执行的客户端上行协议处理函数。简单阅读上述代码,登录判定就是用 md5token .. acc 进行编码与 sign 对比,成功后会通知 ws_agent 服务,并传入用户账号 acc 和客户端 fd,然后在网关绑定客户端和代理,客户端通信就移交给了代理服务。

ws_agent/mng.lua

local _M = {} 
local RPC = {} -- c2s_heartbeat
function RPC.c2s_heartbeat(req, fd, uid)local user = online_users[uid]if not user thenreturn end user.heartbeat = skynet.time()
end function _M.handle_proto(req, fd, uid)local f = RPC[req.pid]local res = f(req, fd, uid)return res
end return _M 

同理,在代理逻辑处理模块中,用于处理除了登录协议之外的其他协议,比如这里的 heartbeat。客户端登录成功,客户端设置了每 5 秒一个心跳包,服务端收到后,就会执行 user.heartbeat = skynet.time() ,维护在线用户表中用户的 heartbeat 字段。并且服务端会定时一分钟一次检测,超时踢出。

-- ws_agent/mng.luafunction _M.check_user_online(uid)local user = online_users[uid]if user then -- 心跳超时踢出if not user.heartbeat or skynet.time() - user.heartbeat >= user_alive_keep_time then _M.close_fd(user.fd)end end 
end function _M.login(acc, fd)local uid = 1 -- 数据库加载数据local user = {fd = fd, acc = acc,}online_users[uid] = user fd2uid[fd] = uid -- 通知 gate 消息由 agent 接管,绑定客户端和代理skynet.call(GATE, "lua", "forward", fd)-- 定时检查心跳local timerid = timer.timeout_repeat(60, _M.check_user_online, uid)user.timerid = timeridlocal res = {pid = "s2c_login",uid = uid, msg = "Login success",}return res
end 

至此,服务端是如何与客户端通信,如何实现用户的登录逻辑,我们就已经有了一个概念。其他服务及客户端的具体实现,且继续往下看。


看门狗服务

看门狗服务主要负责 gate 的创建,agent 的创建与退出。本项目只有一个 agent,所有客户端都会绑定这个代理。如果是一个客户端对应一个代理服务,那么看门狗就可以做一个代理池,进行代理分配,代理回收等。

在主服务 main.lua 中,启动看门狗服务:

-- 开启 ws_watchdog 服务
local ws_watchdog = skynet.newservice("ws_watchdog")-- 通知 ws_watchdog 启动服务
skynet.call(ws_watchdog, "lua", "start", {port = watchdog_port, maxclient = max_online_client,nodelay = true, protocol = ws_protocol,
})

ws_watchdog.lua

local skynet = require "skynet"
local mng = require "ws_watchdog.mng"
local cjson = require "cjson"local CMD = {} -- 服务操作接口
local SOCKET = {} -- socket 相关操作接口
local GATE -- gate 服务地址
local AGENT -- agent 服务地址function SOCKET.data(fd, msg)local req = cjson.decode(msg)if not req.pid then return end -- 判断客户端认证是否通过if not mng.check_auth(fd) then -- 没认证,且不是登录协议,踢下线if not mng.is_no_auth(req.pid) then mng.close_fd(fd)return end end -- 登录协议 or 其他协议处理local res = mng.handle_proto(req, fd)if res then skynet.call(GATE, "lua", "response", fd, cjson.encode(res))end 
endfunction CMD.start(conf)-- 开启 gate 服务skynet.call(GATE, "lua", "open", conf)
endfunction CMD.kick(fd)-- 踢客户端下线mng.close_fd(fd)
endskynet.start(function()skynet.dispatch("lua", function(session, source, cmd, subcmd, ...)if cmd == "socket" thenlocal f = SOCKET[subcmd]f(...)-- socket api don't need returnelselocal f = assert(CMD[cmd])skynet.ret(skynet.pack(f(subcmd, ...)))endend)GATE = skynet.newservice("ws_gate")AGENT = skynet.newservice("ws_agent")mng.init(GATE, AGENT)skynet.call(AGENT, "lua", "init", GATE, skynet.self())
end)

看门狗服务启动skynet.newservice("ws_watchdog"),会依次启动网关和代理服务。这里主要来看一下处理连接登录逻辑的具体步骤,先判断客户端认证是否通过(check_auth),没通过就需要限制当前看门狗服务能处理的逻辑,只能处理登录逻辑(is_no_auth),不是登录协议就不受理。

ws_watchdog/mng.lua

local skynet = require "skynet"
local timer = require "timer"
local md5 = require "md5"local _M = {} -- 模块接口
local GATE -- gate 服务地址
local AGENT -- agent 服务地址local noauth_fds = {} -- 未通过认证的服务端
local TIMEOUT_AUTH = tonumber(skynet.getenv("ws_watchdog_timeout_auth")) or 10-- 标记哪些协议不用登录就能访问
local no_auth_proto_list = {c2s_login = true, 
}function _M.is_no_auth(pid)return no_auth_proto_list[pid]
end -- 超时检测,踢掉没通过认证的客户端
local function timeout_auth(fd)local time = noauth_fds[fd]if not time then return end if skynet.time() - time < TIMEOUT_AUTH then return end _M.close_fd(fd)
endfunction _M.init(gate, agent)GATE = gate AGENT = agent
end function _M.open_fd(fd)noauth_fds[fd] = skynet.time()timer.timeout(TIMEOUT_AUTH + 1, timeout_auth, fd)
end function _M.close_fd(fd)skynet.send(GATE, "lua", "kick", fd)skynet.send(AGENT, "lua", "disconnect", fd)noauth_fds[fd] = nil 
endfunction _M.check_auth(fd)if noauth_fds[fd] then return falseend return true 
end return _M 

ws_watchdog/mng 模块中,维护了 noauth_fds 表,还未通过认证的客户端。设定了连接登录的超时时间 TIMEOUT_AUTH ,可在配置文件中修改。

完整代码:ws_watchdog.lua、ws_watchdog/mng.lua


代理服务

代理服务主要负责接受 gate 转发的请求,处理业务,然后直接把应答响应给 gate 发到客户端。

ws_agent.lua

skynet.register_protocol {name = "client",id = skynet.PTYPE_CLIENT,unpack = skynet.tostring, dispatch = function(fd, address, msg)skynet.ignoreret()  -- session is fd, don't call skynet.ret-- 解析消息,pid:协议idlocal req = cjson.decode(msg)if not req.pid then return end -- 登录成功会绑定 fd: uidlocal uid = mng.get_uid(fd)if not uid then mng.close_fd(fd) -- close_fd 应该实现给watchdog发消息关闭清空资源吧,end     local res = mng.handle_proto(req, fd, uid) if res then skynet.call(GATE, "lua", "response", fd, cjson.encode(res))end end
}skynet.start(function()skynet.dispatch("lua", function(_, _, command, ...)-- skynet.trace()local f = CMD[command]skynet.ret(skynet.pack(f(...)))end)skynet.register(".ws_agent")
end)

代理服务注册 skynet.PTYPE_CLIENT 类型消息,还记得网关也注册了吗?网关收到客户端网络消息,包装成 client 消息,直接发给代理,代理通过 unpack = skynet.tostring 解包消息,通过 dispatch 分发客户端上行的网络消息。

完整代码:ws_agent.lua、ws_agent/mng.lua


客户端

客户端我们也实现为一个 skynet 中的 lua 服务,需要指定配置文件启动 ./skynet/skynet etc/config.client

etc/config.client

include "config"thread = 2server_host = "127.0.0.1" bootstrap = "snlua bootstrap" 
start = "test/client"
logtag = "client"

主服务指定为 test/client,日志文件标识为 logtag = "client"

下面来看 test/client.lua

local ws_id -- websocket 连接 ID
local cmds = {} -- 命令模块,ws: ws.lua、gm: gm.lua-- 搜索加载命令模块
local function fetch_cmds()local t = utils_file.scandir("test/cmds")for _, v in pairs(t) do local cmd = utils_string.split(v, ".")[1] -- ws、gmlocal cmd_mod = "test.cmds." .. cmd cmds[cmd] = require(cmd_mod)end 
end skynet.start(function()dns.server() -- 初始化 dnsfetch_cmds()skynet.fork(websocket_main_loop)skynet.fork(console_main_loop)
end)

启动客户端,初始化设置 dns 服务器。cmds 作为命令模块,相关的 RPC 通信协议命令写在 ws.lua 模块中,GM 指令模块写在 gm.lua 模块中。fetch_cmds 会利用 filestring 工具搜索加载命令模块,存储在 cmds 中。

参考 官方 wiki skynet.dns:

在 skynet 的底层,当使用域名而不是 ip 时,由于调用了系统 api getaddrinfo ,有可能阻塞住整个 socket 线程(不仅仅是阻塞当前服务,而是阻塞整个 skynet 节点的网络消息处理)。虽然大多数情况下,我们并不需要向外主动建立连接。但如果你使用了类似 httpc 这样的模块以域名形式向外请求时,一定要关注这个问题。

skynet 暂时不打算在底层实现非阻塞的域名查询。但提供了一个上层模块来辅助你解决 dns 查询时造成的线程阻塞问题。

local dns = require "skynet.dns" 加载这个模块

dns.server(ip, port) : port 的默认值为 53 。如果不填写 ip 的话,将从 /etc/resolv.conf 中找到合适的 ip 。
dns.resolve(name, ipv6) : 查询 name 对应的 ip ,如果 ipv6 为 true 则查询 ipv6 地址,默认为 false 。如果查询失败将抛出异常,成功则返回 ip ,以及一张包含有所有 ip 的 table 。
dns.flush() : 默认情况下,模块会根据 TTL 值 cache 查询结果。在查询超时的情况下,也可能返回之前的结果。dns.flush() 可以用来清空 cache 。注意:cache 保存在调用者的服务中,并非针对整个 skynet 进程。所以,推荐写一个独立的 dns 查询服务统一处理 dns 查询。

上述代码还启用了两个协程,分别执行 websocket_main_loopconsole_main_loop

-- 网络循环
local function handle_resp(ws_id, res)for _, cmd_mod in pairs(cmds) do if cmd_mod.handle_res then cmd_mod.handle_res(ws_id, res)end end 
end local function websocket_main_loop()-- 连接服务器local ws_protocol = skynet.getenv("ws_watchdog_protocol")local ws_port = skynet.getenv("ws_watchdog_port")local server_host = skynet.getenv("server_host")local url = string.format("%s://%s:%s/client", ws_protocol, server_host, ws_port)ws_id = websocket.connect(url)while true do local res, close_reason = websocket.read(ws_id)local ok, err = xpcall(handle_resp, debug.traceback, ws_id, cjson.decode(res))websocket.ping(ws_id)end 
end 

网络循环中,连接服务器成功后会对服务器下行的数据进行读取处理,调度到模块中的 handle_res 分别找到不同的模块处理相应的网络协议。

-- 执行注册的命令
local function run_command(cmd, ...)local cmd_mod = cmds[cmd]if cmd_mod then cmd_mod.run_command(ws_id, ...) end 
end -- 命令交互
-- ws login acc
local function console_main_loop()local stdin = socket.stdin()while true do local cmdline = socket.readline(stdin, "\n")if cmdline ~= "" then local split = split_cmdline(cmdline)local cmd = split[1]local ok, err = xpcall(run_command, debug.traceback, cmd, select(2, table.unpack(split)))end end 
end 

命令的读取循环中,通过输入 ws login user_count 等指令,找到 ws 模块下的 login 回调方法,对应 c2s_login RPC 协议发送数据 websocket.write 给服务端。

完整代码:test/client.lua、test/cmds/ws.lua


逻辑梳理

通过上面几节描述,我们已经实现了自定义 RPC 协议,完成客户端与服务端的通信。

最后,我们再来梳理一下完整的服务端启动逻辑,客户端连入、退出等逻辑。

服务端启动:主服务 main.lua 启动看门狗服务,看门狗启动网关和代理服务,并且主服务发了一个 start 消息给看门狗,看门狗随即发了一个 open 消息给网关,网关则打开了监听端口,等待连接的接入。同时代理也注册好了 client 消息,等待客户端的数据交互。

客户端连接登录:客户端 websocket.connect(url) 成功连入服务器,网关 websocket.accept(fd, handler, protocol, addr) 感知到是一个连接消息,handler.connect(fd) 随即执行,设置好该连接相关属性后通知看门狗对连接进行处理,即设置一个连接超时定时器。然后,客户端输入登录命令,数据 websocket.write(ws_id, cjson.encode(req)) 上行到服务器,handler.message(fd, msg) 感知到来了一条客户端消息,对该连接判断是否绑定代理,未绑定代理则通知看门狗处理一下这条 socket 消息,仅限登录协议 c2s_login 消息能处理。随后,看门狗验证通过,通知代理授理登录消息。代理服务通知网关将这条连接绑定上代理。之后客户端上行的数据,网关通过 skynet.redirect(agent, c.client, "client", fd, msg) 直接转发给代理处理。

客户端退出:

  1. 客户端主动退出 CTRL + C:触发网关的 handler.error(fd),调用 close_fd 清空网关维护的该连接资源,并且通知看门狗这条 socketerror 消息,看门狗调用 close_fd 清空看门狗维护的该连接资源,并由看门狗通知代理 disconnect 该连接和网关 kick 该连接。代理清空维护的该客户端的资源,网关则执行 websocket.close(fd) 实际的关闭了连接,但由于客户端主动断开,未经 websocket 协议正常关闭流程,不触发 handler.close
  2. 客户端被动退出:连接超时,看门狗 timeout_auth 进行超时检测,调用 close_fd 同上述。但是在网关 websocket.close(fd) 执行后,服务端发送一个 websocket 关闭帧通知客户端并等待回应,所以会触发 handler.close(fd, code, reason),然后执行网关 close_fd ,并给看门狗发了一个 socketclose 消息,看门狗再一次 close_fd。编码保证了多次释放资源不会造成问题。
  3. 客户端被动退出:心跳超时,代理 check_user_online 进行心跳检测,调用 close_fd,会执行 disconnect 逻辑释放代理维护的资源,然后通知网关 kick 连接,同上述。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不适合当老师怎么转岗

作为一名老师&#xff0c;你需要耐心、热情、知识储备丰富&#xff0c;还要有一定的演讲技巧。但有时候&#xff0c;即使具备了这些条件&#xff0c;你仍然可能觉得自己的个性或能力并不适合这个职业。那么&#xff0c;该如何转岗呢&#xff1f;别担心&#xff0c;我们为你提供…

玉渊谭天对电影色彩分析的“蚊香图”复现-python

视频教程链接&#xff1a;https://www.bilibili.com/video/BV1Lu4y1t7FG/ 最终的实现效果如下&#xff1a; 前几天刷抖音刷到了玉渊谭天对于电影抽取画面制作“蚊香图”&#xff0c;相关视频片段如下。 这种制作”蚊香图“的特效当时有点触动到到我&#xff0c;根据色彩来分…

深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用30-深度学习中的GPU的基本架构原理与应用技巧,GPU是一种专门用于处理大量并行操作的硬件设备,它的架构设计主要是为了图形渲染。然而,由于其并行处理能力,现在广泛应用于深度学习、科学计算等领域。主要的GPU制造商…

autojs-练手-简单的视频号点赞(初阶版)

注释很详细&#xff0c;直接上代码&#xff08;简单的练手实践&#xff0c;仅供参考&#xff09; //设置点赞次数 var num50; //等待权限授予 auto.waitFor(); //进入点赞流程 while(num!0) {//先向下滑一个视频scrollDown();//使用auto.js找到点赞控件的id&#xff08;每个人不…

《软件方法》2023版第1章:1.1 利润=需求-设计,1.2 ABCD工作流

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 第1章 建模和UML 牵着你走进傍晚的风里&#xff0c;看见万家灯火下面平凡的秘密。 《情歌唱晚》&#xff1b;词&#xff1a;黄群&#xff0c;曲&#xff1a;黄群&#xff0c;唱&#…

复数的几何意义

1、复平面&#xff0c;复数的其它表示法 (1)几何表示法 直角平面坐标&#xff1a; 复平面 实轴&#xff0c;虚轴 (2)向量表示法 向量 模&#xff1a; 复数加减法可用向量的三角形法则或者平行四边形法则 (3)结论 (两边之和大于第三边) ((两边之差大于第三边)) *辐角&am…

【Web】/proc利用相关例题wp

先贴一篇文章一起学习一下 [CTF]proc目录的应用 - CodeAntenna ①[HDCTF 2023]YamiYami 点击Read somethings直接跳转到了百度 从url中发现存在任意文件读取&#xff0c;因为不知道flag在哪&#xff0c;所以考虑读环境变量 payload: ?urlfile:///proc/1/environ 拿到fla…

【Spring源码】Spring Event事件

目录 1、前言 2、什么是Spring Event&#xff1f; 3、基本使用 3.1、定义事件 3.2、发布事件 3.3、监听事件 3.3.1、继承ApplicationListener 3.3.2、使用EventListener注解 4、Spring Event是同步还是异步&#xff1f; 4.1、源码实现 4.2、如何实现异步 4.2.1、使用…

Redis与Mysql的数据强一致性方案

目的 Redis和Msql来保持数据同步&#xff0c;并且强一致&#xff0c;以此来提高对应接口的响应速度&#xff0c;刚开始考虑是用mybatis的二级缓存&#xff0c;发现坑不少&#xff0c;于是决定自己搞 要关注的问题点 操作数据必须是唯一索引 如果更新数据不是唯一索引&#…

5种主流API网关技术选型,yyds!

API网关是微服务项目的重要组成部分&#xff0c;今天来聊聊API网关的技术选型&#xff0c;有理论&#xff0c;有实战。 不 BB&#xff0c;上文章目录&#xff1a; 1 API网关基础 1.1 什么是API网关 API网关是一个服务器&#xff0c;是系统的唯一入口。 从面向对象设计的角度…

docker介绍、部署与常用命令

一、docker 介绍 1、容器&#xff08;Container&#xff09;&#xff1a; (1) 概念&#xff1a; 容器是一种用于运行和部署应用程序的技术。它将应用程序及其所有依赖项&#xff08;例如代码、运行时、系统工具、系统库等&#xff09;打包在一个独立的、可移植的运行环境中&…

Facebook的特点优势

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也是最受欢迎的社交网站之一&#xff0c;Facebook具有许多独特的特点和优势。本文小编将说一些关于Facebook的特点及优势。 1、全球化 Facebook拥有数十亿的全球用户&#xff0c;覆盖了几乎所有国家和地区。这使得人们…

【深度学习笔记】05 线性回归

线性回归 线性回归基于几个简单的假设&#xff1a; 首先&#xff0c;假设自变量 x \mathbf{x} x和因变量 y y y之间的关系是线性的&#xff0c; 即 y y y可以表示为 x \mathbf{x} x中元素的加权和&#xff0c;这里通常允许包含观测值的一些噪声&#xff1b; 其次&#xff0c;我…

MQTT客户端MQTT.fx 1.7.1下载、安装和界面介绍

MQTT.fx是一款基于Eclipse Paho&#xff0c;使用Java语言编写的MQTT客户端工具。支持通过Topic订阅和发布消息&#xff0c;用来前期和物理云平台调试非常方便。 1.下载 1.1.访问官方下载地址下载&#xff0c;但是下载不到1.7.1版本 1.2.在连接网页末尾点击立即下载&#xff0c;…

R语言如何实现多元线性回归

输入数据 先把数据用excel保存为csv格式放在”我的文档”文件夹 打开R软件,不用新建,直接写 回归计算 求三个平方和 置信区间(95%)

STL: 容器适配器stack 与 queue

目录 1.容器适配器 1.1 STL标准库中stack和queue的底层结构 1.2 deque的简单介绍(了解) 1.2.1 deque的原理介绍 1.2.2 deque的缺陷 1.2.3 为什么选择deque作为stack和queue的底层默认容器 2. stack的介绍和使用 2.1 stack的介绍 2.2 stack的使用 2.3 利用deque模拟实现…

【算法】FFT-1(递归实现)(不包括IFFT)

FFT 多项式多项式乘法复数及运算导数泰勒公式及展开式欧拉公式单位根 FFTCode IFFT 多项式 我们从课本中可以知道&#xff0c;一个 n − 1 n-1 n−1 次的多项式可以写成 a 0 a 1 x a 2 x 2 a 3 x 3 ⋯ a n − 1 x n − 1 a_{0}a_{1}xa_{2}x^2a_{3}x^3\dotsa_{n-1}x^{n-…

【挑战业余一周拿证】二、在云中计算 - 第 2 节 - Amazon EC2 实例类型

第 2 节 - Amazon EC2 实例类型 如果我们想让企业尽可能高效地运作&#xff0c;那就一定要确保员工的技能组合适合他们的角色&#xff0c;就 像我们的咖啡店有不同类型的员工一样&#xff0c;亚马逊云科技也有不同类型的 EC2 实例。每种实例类型 都归属于一个实例系列&#x…

BUUCTF刷题之路-web-[GXYCTF2019]Ping Ping Ping1

启动环境后&#xff0c;是一个简简单单的页面&#xff1a; 看样子是能够触发远程执行漏洞的。尝试下ping 127.0.0.1&#xff0c;如果有回显说明我们的想法是对的。 最近才学习的nc反弹shell。想着是否能用nc反弹shell的办法。控制服务器然后输出flag呢&#xff1f;于是我测试下…

如何通过nginx进行服务的负载均衡

简单介绍 随着互联网的发展&#xff0c;业务流量越来越大并且业务逻辑也越来越复杂&#xff0c;单台服务器的性能及单点故障问题就凸显出来了&#xff0c;因此需要多台服务器组成应用集群&#xff0c;进行性能的水平扩展以及避免单点故障的出现。应用集群是将同一应用部署到多台…